Request path
Every HTTP request to serve goes through the same steps, in this order. A tool handler runs only after all checks pass.
- Route.
GET /healthzis answered at once (see below). Any path other than/mcpgets 404. - Authenticate. The
Authorization: Bearer <token>header is verified: known token, not revoked, not expired, principal not disabled. Otherwise 401 withWWW-Authenticate: Bearer realm="session-bus". - Read the body (POST). Over 64 KiB: 413. Not JSON: 400. A JSON-RPC batch (an array): 400.
- Authorize. The JSON-RPC method must be in the allowlist, and for
tools/callthe tool must be in the scope map and the token must hold its scope. Otherwise 403. - Handle. Only now is an MCP server object created for this request, with the caller's principal fixed. It is discarded when the response closes.
Other HTTP methods than GET, POST and DELETE on /mcp get 405.
Method allowlist
| Method | Allowed for |
|---|---|
initialize, notifications/initialized, ping, tools/list | any valid token |
tools/call | a token with the tool's scope |
| anything else | nobody (403) |
Scope map
| Tool | Scope |
|---|---|
inbox, list_sessions | bus:read |
send, broadcast, register, ack, unregister | bus:write |
Health check
GET /healthz is the only unauthenticated route. It runs a write probe inside a transaction that is always rolled back, then returns 200 {"ok":true,"schema":1}, or 503 {"ok":false} if the database cannot be written. The body is constant and carries no session data.
Stateless MCP
The server does not use MCP session ids. Each request is independent. This keeps one code path for authorization and lets the bus restart without clients holding stale MCP sessions.
Tool errors
Bus-level errors are returned as an MCP tool result with isError: true and text <CODE>: <message>, for example NAME_TAKEN: NAME_TAKEN or NOT_FOUND: no such session server-ci. See MCP tools.
Logging
One line per request on stderr:
ts=... method=POST path=/mcp status=200 principal=me token_id=<uuid> tool=send session=laptop-api latency_ms=3 ts_login=- ts_caps=-
- Caller-supplied values are percent-encoded.
- Raw tokens are redacted from every log line. Only the token id is logged.
- Successful
inboxcalls are logged only withBUS_LOG_LEVEL=debug, because push shims poll every 5 seconds. ts_loginandts_capscome from Tailscale headers. They are logged only when bound to loopback (behindtailscale serve), for attribution. They are not verified and never used for authorization.