Model Context Protocol

Outpost ships an MCP server so AI clients (Claude Code, ChatGPT MCP, custom agents) can list, create, update, and delete pages on a user's behalf. Two transports are supported: SSE at GET /mcp and Streamable HTTP at POST /mcp.

Authentication for both transports is the standard Authorization: Bearer header. Either an API token (op_…) or a JWT works, but API tokens are strongly recommended for non-interactive clients.

Quickest setup (Claude Code)

Add this to your ~/.claude.json under mcpServers:

"outpost": {
  "type": "http",
  "url": "https://outpost.click/mcp",
  "headers": {
    "Authorization": "Bearer op_YOUR_TOKEN"
  }
}

See Claude Code integration for the full walkthrough.

POST /mcp (Streamable HTTP)

A single round-trip JSON-RPC endpoint — best for clients that don't want to maintain an SSE connection. Send a JSON-RPC request body, receive a JSON-RPC response body.

Initialize

curl -X POST https://outpost.click/mcp \
  -H "Authorization: Bearer op_YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}'
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2024-11-05",
    "capabilities": { "tools": {} },
    "serverInfo": { "name": "outpost", "version": "1.0.0" }
  }
}

List tools

curl -X POST https://outpost.click/mcp \
  -H "Authorization: Bearer op_YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'

Call a tool

curl -X POST https://outpost.click/mcp \
  -H "Authorization: Bearer op_YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"outpost_list","arguments":{}}}'

Tool results are wrapped in MCP's standard content array, with the JSON encoded as text:

{
  "jsonrpc": "2.0",
  "id": 3,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\n  \"pages\": [...],\n  \"count\": 3\n}"
      }
    ]
  }
}

Errors

  • 401 Missing or invalid Authorization header
  • 401 Invalid token
  • 400 Invalid JSON
  • JSON-RPC -32601 Method not found for unknown methods.

GET /mcp + POST /mcp/message (SSE)

The Streaming HTTP transport defined by MCP spec 2024-11-05. The client opens a long-lived SSE connection on GET /mcp; the server immediately emits an endpoint event whose data is the URL the client should POST messages to. The server publishes responses on the SSE channel.

1. Open the SSE channel

curl -N https://outpost.click/mcp \
  -H "Authorization: Bearer op_YOUR_TOKEN"
# event: endpoint
# data: https://outpost.click/mcp/message?session_id=<uuid>
#
# event: ping
# data:
# ...

2. POST messages to the endpoint URL

curl -X POST "https://outpost.click/mcp/message?session_id=<uuid>" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize"}'
# 200 OK
# {"status":"accepted"}

# Response arrives on the open SSE stream:
# event: message
# data: {"jsonrpc":"2.0","id":1,"result":{...}}

Session lifecycle

  • Each GET /mcp creates a new session keyed by a UUID.
  • The session holds an asyncio.Queue for outbound messages and the authenticated user record.
  • If no message is queued for 30 seconds, the server emits an event: ping keepalive.
  • When the SSE connection closes, the session is removed from the in-memory map (_mcp_sessions).

Errors

  • 401 Missing or invalid Authorization header on GET /mcp.
  • 401 Invalid token
  • 404 Session not found on POST /mcp/message if the SSE connection has dropped.
  • 400 Invalid JSON on a malformed POST body.

Tool reference

Eight tools are exposed. outpost_create and outpost_update_from_url create/replace page content directly (URL source only). outpost_upload_url and outpost_update_files return curl recipes for when you need to upload a local file (MCP can't ship binary uploads through a tool call).

ToolWhat it doesTouches files?
outpost_listList your pages.No
outpost_getGet one page (incl. passcodes & allowed emails).No
outpost_createCreate a new page from a source_url, with passcodes/allowed_emails.Yes (URL fetch)
outpost_updateUpdate name, visibility, passcodes, allowed_emails.No
outpost_update_from_urlReplace files on an existing page from a source_url.Yes (URL fetch)
outpost_deleteDelete a page and its files.Yes
outpost_upload_urlReturn curl recipes for creating from a local file. Includes a follow-up PUT recipe if you ask for passcodes/allowed_emails.No (recipe only)
outpost_update_filesReturn curl recipes for replacing files on an existing page from a local file.No (recipe only)

outpost_list

List all pages owned by the authenticated user, newest first.

{ "jsonrpc": "2.0", "id": 1, "method": "tools/call",
  "params": { "name": "outpost_list", "arguments": {} } }

Returns:

{
  "pages": [
    {
      "id": "Ab12Cd34",
      "name": "My Site",
      "visibility": "public",
      "default_file": "index.html",
      "url": "https://outpost.click/p/Ab12Cd34",
      "has_passcode": false,
      "allowed_emails": [],
      "created_at": "2026-05-03T17:42:11",
      "updated_at": "2026-05-03T17:42:11"
    }
  ],
  "count": 1
}

outpost_get

Get a single page including its passcodes (decrypted) and allowed emails.

{
  "name": "outpost_get",
  "arguments": { "page_id": "Ab12Cd34" }
}

outpost_create

Create a new page directly from a URL. Outpost fetches the page and its same-domain assets, rewrites references to local copies, and stores the result. Supports passcodes and allowed_emails at create time — no follow-up call needed.

{
  "name": "outpost_create",
  "arguments": {
    "name": "Vendor docs",
    "source_url": "https://vendor.example.com/onboarding",
    "visibility": "shared",
    "passcodes": ["pilot-2026"],
    "allowed_emails": ["alice@acme.com", "bob@acme.com"]
  }
}

Returns:

{
  "id": "Mn7Pq2Rs",
  "name": "Vendor docs",
  "visibility": "shared",
  "passcodes": ["pilot-2026"],
  "allowed_emails": ["alice@acme.com", "bob@acme.com"],
  "default_file": "index.html",
  "url": "https://outpost.click/p/Mn7Pq2Rs",
  "created_at": "2026-05-03T22:44:38",
  "message": "Page created from source_url"
}
For local files use outpost_upload_url — MCP can't carry binary content through a tool call, so the upload has to shell out to curl.

outpost_update

Update any subset of name, visibility, passcodes, and allowed_emails. Files are not touched — use outpost_update_from_url or outpost_update_files for content.

{
  "name": "outpost_update",
  "arguments": {
    "page_id": "Mn7Pq2Rs",
    "name": "Renamed",
    "visibility": "shared",
    "passcodes": ["one", "two"],
    "allowed_emails": ["alice@acme.com", "carol@acme.com"]
  }
}

Pass an empty array to clear a list:

{
  "name": "outpost_update",
  "arguments": { "page_id": "Mn7Pq2Rs", "allowed_emails": [] }
}

outpost_update_from_url

Replace the content of an existing page by re-fetching from a URL. The page id and URL stay the same.

{
  "name": "outpost_update_from_url",
  "arguments": {
    "page_id": "Mn7Pq2Rs",
    "source_url": "https://vendor.example.com/onboarding"
  }
}

Returns:

{
  "id": "Mn7Pq2Rs",
  "name": "Vendor docs",
  "visibility": "shared",
  "default_file": "index.html",
  "url": "https://outpost.click/p/Mn7Pq2Rs",
  "updated_at": "2026-05-03T22:44:52",
  "message": "Page files replaced from source_url"
}

outpost_delete

{
  "name": "outpost_delete",
  "arguments": { "page_id": "Ab12Cd34" }
}

outpost_upload_url

Returns curl recipes for creating a new page from a local file (ZIP or HTML). MCP can't carry binary content, so the upload itself has to shell out. If you pass passcodes or allowed_emails, the recipe also includes the follow-up PUT to set them after the page exists.

{
  "name": "outpost_upload_url",
  "arguments": {
    "name": "Team wiki",
    "visibility": "shared",
    "passcodes": ["letmein"],
    "allowed_emails": ["alice@acme.com", "bob@acme.com"]
  }
}

Result (abbreviated):

{
  "note": "Creates a NEW page from a local file. For URL-based creation use outpost_create instead.",
  "upload_file": {
    "endpoint": "POST https://outpost.click/api/pages",
    "example": "curl -X POST \"https://outpost.click/api/pages\" -H \"Authorization: Bearer op_YOUR_TOKEN\" -F \"name=Team wiki\" -F \"visibility=shared\" -F \"file=@./my_file.zip\""
  },
  "after_upload": [
    {
      "endpoint": "PUT https://outpost.click/pages/<NEW_PAGE_ID>",
      "example": "curl -X PUT \"https://outpost.click/pages/<NEW_PAGE_ID>\" -H \"Authorization: Bearer op_YOUR_TOKEN\" -F \"passcodes=letmein\" -F \"allowed_emails=alice@acme.com,bob@acme.com\""
    }
  ]
}

outpost_update_files

Returns curl recipes for replacing files on an existing page from a local file. The page URL stays the same. For URL-based updates prefer outpost_update_from_url — no curl needed.

{
  "name": "outpost_update_files",
  "arguments": { "page_id": "Ab12Cd34" }
}

Result includes POST https://outpost.click/api/pages/Ab12Cd34/files recipes for both file=@… and source_url=… forms, plus a hint to use outpost_update for metadata changes.

Error shape

Recoverable tool errors are returned inside the tool result (so the AI client sees them) rather than as JSON-RPC errors. Examples:

{ "error": "page_id is required" }
{ "error": "Invalid visibility" }
{ "error": "Page not found. Use outpost_list to see your pages." }

Transport-level problems (auth, malformed JSON, unknown methods) come back as proper HTTP / JSON-RPC errors.

Quick Python client

import json, requests

URL = "https://outpost.click/mcp"
TOKEN = "op_YOUR_TOKEN"

def rpc(method, params=None, id_=1):
    r = requests.post(
        URL,
        headers={"Authorization": f"Bearer {TOKEN}"},
        json={"jsonrpc": "2.0", "id": id_, "method": method, "params": params or {}},
        timeout=30,
    )
    r.raise_for_status()
    return r.json()

print(rpc("initialize"))
print(rpc("tools/list", id_=2))

resp = rpc("tools/call",
           {"name": "outpost_list", "arguments": {}}, id_=3)
payload = json.loads(resp["result"]["content"][0]["text"])
print(payload["count"], "pages")