# 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`.

HTML version: https://permahost.io/docs/mcp/ · All docs: https://permahost.io/docs.md

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](https://permahost.io/docs/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](https://permahost.io/docs/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.

| Argument | Description |
| --- | --- |
| action | Required. `upload`, `download`, `get`, `url` or `list`. |
| id | Folder 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`. |
| fileName | File name, without directories, such as `index.html`. |
| path | Directory inside the folder, such as `site/css`, at most 5 levels. Empty for the folder root. |
| format | `upload` and `download` only: which field carries the content, `base64Content` (default) or `textContent`. |
| textContent | UTF-8 text content, as is, with `format: "textContent"`. |
| base64Content | Base64 content, with `format: "base64Content"` or no `format`. |

| Result field | Description |
| --- | --- |
| id | The folder id. Keep it to add more files to the folder. |
| url | Public URL of the file (upload, url) or folder (get). Give it to the user. |
| fileName, path, size | The file's name, directory and size in bytes. |
| creditsSpent | Upload only: the credits the call spent. Often 0 when adding to an existing folder. |
| base64Content, textContent | Download only: the file's content, in the field `format` names. |
| files | Get only: the folder's files, each with `fileName`, `path`, `size` and `url`. |
| folders | List 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](https://permahost.io/docs/#credits) for renewals and custom domains.

## Errors

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

| Message | What to do |
| --- | --- |
| insufficient credits | The 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 found | No folder or file of yours has that id or path. Check it with `list` or `get`. |
| invalid file path | The path is deeper than 5 levels, or has `.`, `..` or backslashes. |
| upload too large | The file is over the upload limit. Use the [CLI](https://permahost.io/docs/cli/) for large files, or split them. |
| textContent is required with format textContent | The content was sent in the other field than `format` names. |
| the file is not UTF-8 text: download it with format base64Content | Download it again with `format: "base64Content"`. |
