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/mcpOr 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 then returns seven tools: publish_site, update_site, get_site, list_sites, delete_site, whoami, and get_upgrade_link. The official registry entry is page.deed/deed-page if you prefer to install from the MCP Registry instead of pasting the URL.
First publish
Call publish_site with a files map. Text files are plain strings; binary files are {"base64":"..."}. Pick a slug (lowercase letters, digits, hyphens, up to 63 characters) or omit it for a random one:
{
"name": "publish_site",
"arguments": {
"slug": "agent-status",
"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; there is no email recovery path. Equivalent REST for the same shape:
curl -sS -X POST https://deed.page/v1/sites \
-H 'content-type: application/json' \
-d '{"slug":"agent-status","files":{"index.html":"<!doctype html><h1>agent status</h1>"}}'The live URL is https://agent-status.deed.page/ (path fallback /s/agent-status/ also works). 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": "agent-status",
"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, skill.md, and OpenAPI.
Limits to remember
- Free plan: 25 sites × 25 MB, no signup. Paid plans raise those ceilings; see /pricing.
- Every published site is public. Do not put secrets in the files map.
- Body cap ~4 MB per call; use
merge/X-Merge: 1for 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.