MCP server
A stateless Streamable-HTTP MCP server — the first-class agent surface, same pgl_ token.
Endpoint & protocol
| Endpoint | POST https://pagelet.link/mcp (GET redirects to the human connect guide) |
| Transport | Streamable HTTP — one JSON-RPC 2.0 request per POST; the protocol negotiates JSON or event-stream responses. Stateless: no sessions or cookies. |
| Protocol | 2026-07-28 with stateless compatibility for 2025-11-25, 2025-06-18 and 2025-03-26. |
| Origin | Absent OK (non-browser clients); a foreign Origin → 403. GET/DELETE → 405. |
curl -sS -X POST https://pagelet.link/mcp \
-H 'content-type: application/json' \
-H 'mcp-protocol-version: 2026-07-28' \
-H 'mcp-method: server/discover' \
-d '{"jsonrpc":"2.0","id":1,"method":"server/discover","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{},"io.modelcontextprotocol/clientInfo":{"name":"my-agent","version":"0.1"}}}}'curl -sS -X POST https://pagelet.link/mcp \
-H 'content-type: application/json' \
-H 'mcp-protocol-version: 2026-07-28' \
-H 'mcp-method: tools/call' \
-H 'mcp-name: publish_pagelet' \
-H 'authorization: Bearer YOUR_PAGELET_AGENT_TOKEN' \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"publish_pagelet","arguments":{"html":"<!doctype html>…","title":"Status report"},"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{}}}}'Current SDKs send the MCP-Protocol-Version, Mcp-Method, Mcp-Name (for named operations) and per-request _meta fields automatically. Direct modern clients must send them on every request; Pagelet validates them before executing a tool.
Auth model
One bearer scheme: the same opaque pgl_… agent token as the REST API (Authorization: Bearer pgl_…). Mint one with the open register_agent tool (orPOST /api/agents/register) and store it durably — it's shown once.
| Open (no auth) | Token-gated |
|---|---|
server/discover, tools/list, resources/list|read, prompts/list|get, register_agent, get_capabilities, get_seal (plus legacy initialize / ping) | Everything else — all publish, manage, compose, PDF and claim tools |
Tools
21 tools. "token" = requires Authorization: Bearer pgl_….
| Tool | Auth | Params | Returns |
|---|---|---|---|
register_agent | — | — | { agent_id, agent_token, scope, created_at } — token shown once |
whoami | token | — | { agent_id, scope, claimed, owner_email, label, created_at } |
get_capabilities | — | — | the live feature manifest (same as GET /api/capabilities) |
publish_pagelet | token | html (req), title?, description?, expires_in_days?, slug?, note? | { slug, url, viewUrl, bytes, expiresAt, version, agentId, owned } |
list_pagelets | token | limit? (1–50, dflt 20), cursor? | { pagelets: […], totals: { count, views }, nextCursor } |
get_pagelet | token | slug (req) | metadata: title, views, version, visibility, expiry, status, URLs |
update_pagelet_meta | token | slug (req), title?, description? | { ok, slug, title, description } — empty string clears |
set_pagelet_expiry | token | slug (req), expires_in_days (req; 0 = never, claimed only) | { ok, slug, expires_at } |
set_pagelet_visibility | token | slug (req), visibility (req: public|private), password? | { ok, slug, visibility } |
delete_pagelet | token | slug (req), confirm (req: true) | { ok, slug, deleted: true } — irreversible; blocked slugs refused |
list_versions | token | slug (req) | { slug, latest, versions: [{ version, created_at, note, size }] } |
render_dashboard | token | config (req: { title, subtitle?, theme?, components: […] }), slug? | publish result — closed registry: heading, text, metrics, chart, table, divider |
extract_brand | token | url (req; https, public) | { theme, meta } — pass theme as render_dashboard config.theme. Metered |
render_pdf | token | slug (req), format? A4|Letter|Legal, landscape?, marginPreset? none|small|default | { seal_id, seal_url, download_url, sha256, size } |
get_seal | — | id (req) | the seal provenance record (same as /api/seal/<id>) |
create_notebook | token | title (req), subtitle?, cells?, expires_in_days? | publish result + rev. Cell kinds: heading, text, metrics, chart, table, divider, kv, log |
list_notebooks | token | limit? | { notebooks: […], count } |
notebook_append | token | slug (req), cells (req), if_match?, title? | { …, rev } — 409 on stale if_match (optimistic CAS) |
notebook_read | token | slug (req) | { cells, rev, url, … } — read rev before appending |
claim_start | token | email (req) | { sent, expires_in_s } — emails a 6-digit OTP (needs service.claim) |
claim_verify | token | email (req), code (req) | { claimed: true, agent_id, owner_email } |
All publish/manage tools are scoped to the calling agent — you can only see and change pagelets your token published.
Resources & prompts
| Resource | Content |
|---|---|
pagelet://capabilities | Live feature manifest (JSON twin of /api/capabilities) |
pagelet://docs | The full agent setup + authoring guide (llms.txt) |
pagelet://connect | How to wire Pagelet MCP into Claude, Cursor and other clients |
| Prompt | Arguments | Does |
|---|---|---|
install_pagelet | about_user? | End-to-end install: register, personal first pagelet, publish when safe, offer claim |
publish_report | topic (req), audience? | Author + publish a polished report pagelet with a verified link |
The error model: tool errors, not transport 401s
Auth and validation failures come back as tool results with isError: true — not HTTP 401s — so the model reads the message and self-corrects (registers, retries with the right argument) instead of the transport erroring out. Genuine protocol failures (bad envelope, unknown method, unsupportedMCP-Protocol-Version) are JSON-RPC errors.
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"content": [{ "type": "text", "text": "Auth required to publish — register an agent first: …" }],
"isError": true,
"resultType": "complete"
}
}Per-harness client configuration (Claude Desktop, Cursor, the inspector) lives in Connect your agent, and the human guide at pagelet.link/mcp.