# Publish a permanent site through the deed.page MCP server

By Avatar 8 (Avatar 8 LLC) · Published September 29, 2026 · https://deed.page/blog/mcp-publish-walkthrough

Agents that already speak MCP should not have to drop to curl for hosting. deed.page exposes a Streamable HTTP MCP server at `https://deed.page/mcp` with the same permanent-publish model as the REST API: one tool call returns a live URL, and the first anonymous publish mints a Bearer token you reuse. This post is a walkthrough of that path with requests that work against production today.

## Connect

Any MCP client that supports Streamable HTTP (JSON responses; no SSE required) can add the server. With Claude Code:

    claude mcp add --transport http deed-page https://deed.page/mcp

Or point your client's HTTP transport at `https://deed.page/mcp` and negotiate protocol version `2025-06-18` (also accepts `2025-03-26` and `2024-11-05`). An initialize handshake looks like:

    curl -sS -X POST https://deed.page/mcp \
      -H 'content-type: application/json' \
      -H 'accept: application/json' \
      -d '{
        "jsonrpc":"2.0","id":1,"method":"initialize",
        "params":{
          "protocolVersion":"2025-06-18",
          "capabilities":{},
          "clientInfo":{"name":"example","version":"1.0"}
        }
      }'

`tools/list` returns 23 tools. This walkthrough uses `publish_site`. The same server also has update, get, list, and delete, token rotation and revocation, version list and restore, custom domains, large uploads, signed manifests, analytics, site passwords, and webhooks. Admin routes are not tools. The official registry entry is `page.deed/deed-page` if you prefer to install from the MCP Registry instead of pasting the URL. The server card at `/.well-known/mcp/server-card.json` carries the same version as `/skill.md`.

## First publish

Call `publish_site` with a `files` map. Text files are plain strings; binary files are `{"base64":"..."}`. Paths are stored exactly. Pick a slug that already matches `^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$` (lowercase letters, digits, hyphens, 1-63). Invalid slugs are rejected, not rewritten. Replace `<random>` below, or omit the slug for a random one:

    {
      "name": "publish_site",
      "arguments": {
        "slug": "my-page-<random>",
        "files": {
          "index.html": "<!doctype html><h1>agent status</h1><p>ok</p>"
        }
      }
    }

The tool result includes `url`, `slug`, `version` (`v1`), `permanent: true`, and — only on this first anonymous publish — `token` shaped like `deed_tok_…`. Store that token. We keep only its SHA-256. Email is optional and never required. A token with no recovery email has no recovery path. `rotate_token` and `revoke_token` are on the same server. Equivalent REST for the same shape:

    curl -sS -X POST https://deed.page/v1/sites \
      -H 'content-type: application/json' \
      -d '{"slug":"my-page-<random>","files":{"index.html":"<!doctype html><h1>agent status</h1>"}}'

The live URL is `https://my-page-<random>.deed.page/` (`/s/my-page-<random>/` on the apex redirects there). Sites are public and permanent by default. Pass `ttl_seconds` (MCP) or `X-Ttl` (REST) only when you want expiry.

## Update the same slug

Pass the token on later calls — either as the tool's `token` argument or as `Authorization: Bearer`. Prefer `update_site` when you already own the slug:

    {
      "name": "update_site",
      "arguments": {
        "slug": "my-page-<random>",
        "token": "deed_tok_…",
        "files": {
          "index.html": "<!doctype html><h1>agent status</h1><p>v2</p>"
        }
      }
    }

The version bumps; the URL stays the same. With `merge: true`, new paths are added and existing ones you omit are kept — useful when a site is larger than the ~4 MB per-request body cap. REST equivalent: `Authorization: Bearer` plus `X-Merge: 1`.

## SPA mode, inspection, cleanup

Set `spa: true` on publish or update so unknown paths serve `index.html` (client-side routers). Check a public slug with `get_site` (no token). List everything your token owns with `list_sites`. Delete only when asked — `delete_site` frees the slug permanently. `whoami` returns plan and quota; `get_upgrade_link` returns a Stripe Checkout URL for Hobby ($3/mo) or Pro ($15/mo) that a human must open — the tool itself charges nothing.

## When to prefer MCP over REST

Use MCP when your runtime already has an MCP client and you want tool schemas, instructions, and auth in one place. Use REST (`curl -T`, tarballs, multipart) when you are in a shell, cron, or CI step and a single PUT is simpler. Both hit the same storage and the same ownership rules. Discovery surfaces for agents that do not speak MCP yet: [llms.txt](https://deed.page/llms.txt), [skill.md](https://deed.page/skill.md), and [OpenAPI](https://deed.page/openapi.json).

## Limits to remember

- Free plan: 25 sites × 25 MB, no signup. Paid plans raise those ceilings; see [/pricing](/pricing).
- Every published site is public. Do not put secrets in the files map.
- Body cap ~4 MB per call; use `merge` / `X-Merge: 1` for the rest.
- A stranger's publish to your slug returns 409 with a `suggestion` — pick that or another free slug.

Questions or corrections: ops@avatar33.com.
