permahost.io permanent file hosting for ai agents

MCP tool

The MCP server at https://mcp.permahost.io/mcp has one tool, file. Its action argument picks what it does: upload, download, get, url or list. Folders are passed as id.

When to use MCP

Every byte of a file passes through the model's context as a tool argument or result. If the agent can run shell commands, the CLI uploads from disk instead and keeps large files out of the context. Use MCP when there is no shell, or for small files the agent has just written.

For text files (.html, .css, .js, .md, .json, .svg, .txt) set format to textContent and send the text as is. It is a third smaller than base64 and stays readable. Use base64Content only for binary files.

Connect

The server speaks streamable HTTP and is stateless. Add it to any MCP client; clients that support OAuth open the browser to sign in through app.permahost.io (Google or GitHub) the first time they connect. Every call needs that sign-in: without a token the server answers 401 and points to its OAuth metadata.

Its manifest, the MCP Registry server.json named io.github.tmphost/permahost, is at https://permahost.io/.well-known/mcp.json.

MCP client config (JSON)
{
  "mcpServers": {
    "permahost": {
      "type": "http",
      "url": "https://mcp.permahost.io/mcp"
    }
  }
}
Claude Code
$ claude mcp add --transport http permahost https://mcp.permahost.io/mcp

The plugins for Claude Code, Codex, Cursor and Gemini CLI add the same server plus instructions on when and how to use it.

Arguments

Each action reads only the arguments it needs and fills only its own result fields; empty fields are left out.

ArgumentDescription
actionRequired. upload, download, get, url or list.
idFolder id. An upload without it creates (and pays for) a new folder; with it, the file goes into that existing folder at no folder cost. Required by download, get and url.
fileNameFile name, without directories, such as index.html.
pathDirectory inside the folder, such as site/css, at most 5 levels. Empty for the folder root.
formatupload and download only: which field carries the content, base64Content (default) or textContent.
textContentUTF-8 text content, as is, with format: "textContent".
base64ContentBase64 content, with format: "base64Content" or no format.
Result fieldDescription
idThe folder id. Keep it to add more files to the folder.
urlPublic URL of the file (upload, url) or folder (get). Give it to the user.
fileName, path, sizeThe file's name, directory and size in bytes.
creditsSpentUpload only: the credits the call spent. Often 0 when adding to an existing folder.
base64Content, textContentDownload only: the file's content, in the field format names.
filesGet only: the folder's files, each with fileName, path, size and url.
foldersList only: your folders, newest first, each with id, title, url, sizeBytes and createdAt. Left out when there are none.

The ids and URLs in the examples below are examples; take the real ones from each result.

upload

Stores one file and returns its public URL. Without id it creates a new folder; with id, the file goes into that folder, and a file already at the same path is replaced in place (an update: its URL stays the same).

Publish an HTML page as text, in a new folder

Call file
{
  "action": "upload",
  "fileName": "index.html",
  "format": "textContent",
  "textContent": "<!doctype html><title>Report</title><link rel=\"stylesheet\" href=\"css/style.css\"><h1>Q3 report</h1>"
}
Result
{
  "id": "k7q2m9xa3bd",
  "url": "https://k7q2m9xa3bd.file.permahost.io/index.html",
  "fileName": "index.html",
  "size": 98,
  "creditsSpent": 1000
}

The new folder costs 500 credits, and this was the account's first file this month, so it started a 25 MB storage block for another 500. Because index.html is at the root, the folder URL https://k7q2m9xa3bd.file.permahost.io/ serves the page too.

Add a stylesheet to the same folder, in a directory

Call file
{
  "action": "upload",
  "id": "k7q2m9xa3bd",
  "path": "css",
  "fileName": "style.css",
  "format": "textContent",
  "textContent": "body { font-family: system-ui; margin: 2rem; }"
}
Result
{
  "id": "k7q2m9xa3bd",
  "url": "https://k7q2m9xa3bd.file.permahost.io/css/style.css",
  "fileName": "style.css",
  "path": "css",
  "size": 47
}

No creditsSpent: the folder is paid for and the account's storage stays inside a block already paid for this month.

Upload an image as base64

Call file
{
  "action": "upload",
  "id": "k7q2m9xa3bd",
  "path": "img",
  "fileName": "chart.png",
  "base64Content": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg=="
}

download

Reads a file back from one of your folders. Free of credits. Pass format: "textContent" to get UTF-8 text as is; a file that isn't UTF-8 text then fails, so read it as base64Content instead.

Call file
{
  "action": "download",
  "id": "k7q2m9xa3bd",
  "path": "css",
  "fileName": "style.css",
  "format": "textContent"
}
Result
{
  "id": "k7q2m9xa3bd",
  "fileName": "style.css",
  "path": "css",
  "size": 47,
  "textContent": "body { font-family: system-ui; margin: 2rem; }"
}

get

Lists a folder's files with their URLs. Free.

Call file
{ "action": "get", "id": "k7q2m9xa3bd" }
Result
{
  "id": "k7q2m9xa3bd",
  "url": "https://k7q2m9xa3bd.file.permahost.io/",
  "files": [
    { "fileName": "index.html", "path": "", "size": 98, "url": "https://k7q2m9xa3bd.file.permahost.io/index.html" },
    { "fileName": "style.css", "path": "css", "size": 47, "url": "https://k7q2m9xa3bd.file.permahost.io/css/style.css" }
  ]
}

url

Builds the public URL of a folder (id), a directory (id, path) or a file (id, fileName, optional path) without uploading anything. Free.

Call file
{ "action": "url", "id": "k7q2m9xa3bd", "path": "css", "fileName": "style.css" }
Result
{
  "id": "k7q2m9xa3bd",
  "url": "https://k7q2m9xa3bd.file.permahost.io/css/style.css",
  "fileName": "style.css",
  "path": "css"
}

list

Lists your folders, newest first. Free. Use it to find the id of a folder published earlier before adding to it or updating it. title is the folder's title, else the name of its only file. No folders field means there are none.

Call file
{ "action": "list" }
Result
{
  "folders": [
    {
      "id": "k7q2m9xa3bd",
      "title": "Q3 report",
      "url": "https://k7q2m9xa3bd.file.permahost.io/",
      "sizeBytes": 145,
      "createdAt": "2026-11-02T08:14:00Z"
    }
  ]
}

Credits

Credits are monthly and charged upfront when a cost starts. Only upload spends them; the other actions are free.

  • A new folder costs 500 credits a month. Only upload without id creates one.
  • Uploading with id into an existing folder never costs folder credits again, however many files it adds or replaces.
  • Storage costs 500 credits a month per started 25 MB the whole account stores. An upload pays only when it pushes the total into a block not paid for this month, so most uploads cost 0; replacing a file counts only the size difference.

So to publish a site: upload the first file without id, keep the id it returns, and pass it for every other file. Never upload without id just to add files. See credits for renewals and custom domains.

Errors

A failed call returns a tool error with a message the agent can act on:

MessageWhat to do
insufficient creditsThe account can't pay for a new folder or storage block this month. Add to an existing folder, delete what is no longer needed, or wait for the renewal.
… (folder <id> was created for 500 credits: pass it as id to retry)The folder was made and paid for, but the file failed. Retry with that id instead of creating another folder.
not foundNo folder or file of yours has that id or path. Check it with list or get.
invalid file pathThe path is deeper than 5 levels, or has ., .. or backslashes.
upload too largeThe file is over the upload limit. Use the CLI for large files, or split them.
textContent is required with format textContentThe content was sent in the other field than format names.
the file is not UTF-8 text: download it with format base64ContentDownload it again with format: "base64Content".