MCP runtime — transports, trust, and attribution [Flow]
Source path: knowledge-base/diagrams/flows/mcp-runtime.md
# MCP runtime — transports, trust, and attribution `[Flow]`
How local and remote clients reach one generated tool catalog, and where authority becomes an
attributed record write. Capability navigation is documented separately in [MCP action chains](mcp-chains.md).
```mermaid
flowchart LR
LocalClient(["Local AI client<br/>Claude Code · Codex · IDE"])
RemoteClient(["Remote AI client<br/>Claude · ChatGPT · IDE"])
LocalClient -->|"stdio · machine boundary"| Stdio["mcp/server.ts<br/>role pinned at startup"]
RemoteClient -->|"Streamable HTTP<br/>expiring Bearer + session id"| Http["Gateway /api/mcp<br/>verify token + entitlement<br/>bind session to Director"]
Stdio --> Catalog["shared mcp/catalog.ts<br/>manifest → first-class tools"]
Http --> Catalog
Stdio -->|"loopback /api/local/*"| Daemon
Http -->|"outbound SSE relay"| Instance["Director's instance<br/>admit signed-in Director"]
Instance -->|"resolve canonical Director<br/>redact People read"| Daemon["daemon query · control · mutate · op"]
Daemon --> Reads["Tier 0<br/>pure queries"]
Daemon --> Control["Control plane<br/>Exchange · Account · Platform admin"]
Control --> Scope{"declared scope<br/>director / admin / owner"}
Scope -->|"resolved from the cached<br/>platform verdict"| Central[("central plane<br/>Supabase · Stripe")]
Daemon --> Ops["Tier 2<br/>effectful operations"]
Daemon --> Writer["Tier 1 terminus<br/>applyLocalMutation"]
Ops -->|"typed actions"| Writer
Writer --> Gate["gate → reduce → persist<br/>→ attribute → echo"]
Gate --> Record[("canonical on-disk record")]
Gate --> Activity[("activity journal<br/>Director id · name · role")]
Ops --> Usage[("provider-usage receipts<br/>same Director actor")]
```
**Trust invariants**
- Remote requests re-check token expiry and entitlement; an MCP session cannot be reused by another Director.
- **Key rotation never strands live tokens.** An instance verifies a bearer against a SET of gateway public keys (`current,previous`, selected by `kid`, via `verifyMcpTokenAny`), so rotating the gateway signing key stays invisible to holders; `MCP_JWT_PUBLIC_KEY` accepts the comma-separated set and a redeploy re-bakes it. An **admin cross-instance lane** (owner > admin > director) exists for fleet diagnosis, with every access written to an admin-access trail on the instance. Fleet-side runbook: [FLEET_OPS.md](../../knowledge/operations/FLEET_OPS.md).
- A control action declares its `scope`, checked **locally before the call leaves the process** — so a Director calling an admin tool learns *why*, not just that it failed. The remote route stays the authority and re-checks; the local gate is fail-fast, never the decision. See [platform model](../concepts/platform-model.md#knowing-the-platform-role).
- Authed gateway calls take one redirect **manually**: `fetch` strips `Authorization` across origins, so a gateway URL that canonicalises (apex → `www`) would otherwise arrive with no credential and 401. The Bearer is re-attached only for a same-site hop over an equally-secure scheme (`lib/core/access/gateway-redirect.ts`).
- The relay ignores `body.actor`. Authority comes from the verified connection; identity comes from the instance People registry.
- Provider receipts use per-operation async context, so concurrent work on different domains cannot swap Director or scope attribution.
- Remote permission assignment receives only member id, name, and role. Credentials and contact fields stay local.
- HTTP focus is session memory. A process restart requires a new initialize call; it cannot lose canonical domain state.
- Long operations resume identically on both transports: one shared `OperationRuntime` + operation lane, with only the event stream differing (stdio drains the daemon's SSE; remote runs the op in-process). A long op returns `{ status: "running", operationId }` before the client deadline, and `meridians_op_status`/`meridians_op_cancel` poll or stop it either way — a cancel aborts the underlying op, re-checked at every emit/commit boundary. Operation ids are process-local: a restart invalidates them, which is a prompt to reconcile the record, never to blind-retry (see `mcp/README.md`).
- Per-actor throttle: `/api/mcp` meters every authenticated actor (`mcpSessionActorKey`) through a process-local token bucket and refuses an exhausted one with a JSON-RPC error at HTTP 429. It is a spend backstop on one box, not a platform-wide allowance ([SECURITY.md](../../knowledge/governance/SECURITY.md)).
Open on GitHubRaw Markdown source