MCP internals
Takhtabush exposes a stateless MCP Streamable HTTP endpoint at /mcp. It is a
thin adapter over the same domain modules as the web app.
For the user-facing setup, see Use an AI assistant.
Architecture
packages/mcpimplements the JSON-RPC handling, tool dispatch, scope checks, and audit writes.- It depends on
@caly/sharedfor contracts and on an injectedMcpServicesinterface. It never imports D1 or R2. packages/api/src/services/mcp-services.tsbinds that interface to the authenticated user and the API services.packages/api/src/index.tsresolves the caller, builds the services, and delegates.
Authentication
The endpoint accepts three credential types:
- API key —
Authorization: Bearer <key>orx-api-key. Keys are hashed, scoped, and optionally expire. - Session cookie — the same
sidcookie as the web app. - OAuth bearer — issued by
@cloudflare/workers-oauth-providerfor ChatGPT and other OAuth clients.
API-key and session requests use the Hono /mcp route. OAuth bearers go through
the provider. /mcp/oauth is a path-descendant alias.
Scopes
| Scope | Allows |
|---|---|
decks:read |
List decks, read cards, fetch a study snapshot |
stats:read |
Read deck statistics and request a study plan |
decks:write |
Create, update, delete, and import decks and cards |
study:write |
Start sessions and record reviews |
media:write |
Request media upload intents |
study:render |
List and read the MCP Apps study widget resource |
tools/list returns only the tools covered by the presented grant. A tool call
outside the grant fails with -32003. An invalid x-mcp-scopes header narrows
to no scopes rather than widening to the key's full grant.
The resources/list and resources/read calls require study:render.
render_study_session also starts the session when it is given deckId instead
of sessionId. Creating a session is a write, so that path additionally requires
study:write. The tool is deliberately self-sufficient, because ChatGPT's mobile
clients drop a widget whose tool follows another tool call in the same turn: the
render tool must be the only tool the model invokes. Its text result always
contains an absolute /study/:sessionId link for hosts that drop the widget.
Widget CSP travels in the resource's _meta.ui.csp (resourceDomains = the
Worker origin). Hosts inject the resource HTML into their own sandboxed iframe
and never apply the content-security-policy header that /mcp-apps/* returns.
Tools
The curated tool surface is defined in packages/shared/src/api/mcp.ts and
generated into the MCP tool reference.
Adding a tool touches:
- the tool name schema,
- the scope map,
- the definitions array (description + input schema),
- the
McpServicesinterface, - the API binding,
- the dispatch switch,
- the write set and audit projection.
The server lists tools from the definitions and enforces the scope map. The
web app’s /mcp screen uses the same definitions.
Audit
Every mutating tool writes a row to mcp_audit_events:
- user id,
- tool name and action,
- bounded input summary,
- affected ids,
- request id,
- timestamp.
Large inputs are truncated before storage. The audit write happens after the mutation; a failed audit is reported, but the mutation is already applied.
OAuth and ChatGPT
@cloudflare/workers-oauth-provider handles discovery, token issuance, CIMD and
DCR, and audience validation. Grants, refresh tokens, and clients live in the
OAUTH_KV namespace.
/oauth/authorizerenders a consent page backed by the magic-link session.- Consent uses
__Host-cookies, so test the flow over HTTPS. - The issuer and audience follow the request origin. Connect clients to the canonical host.
Local development
wrangler.dev.jsonc sets MCP_DEV_NO_AUTH=true. With no credential present,
/mcp uses the owner account. Set MCP_DEV_USER_EMAIL to use another existing
user. A supplied bad key still fails, and the flag is ignored outside
ENVIRONMENT=development.
Code execution
There is no code tool and no codemode:execute scope in v0.1. The previous
implementation ran in the Worker module scope with normal globals reachable, so
it could not honestly be called a sandbox. Code Mode stays deferred until a real
isolation seam exists.
Tests
packages/mcp/src/server.test.ts exercises the dispatch surface, scope checks,
resource gating, and the widget metadata. packages/mcp/src/auth.test.ts
covers scope narrowing. The API’s /mcp route is covered in
packages/api/src/routes/api-keys.test.ts.