{"protocol":"clank-doc/1","frameworkVersion":"0.9.3","slug":"per-app-mcp","title":"The MCP server built into every app","description":"Every Clank app with a backend is also its own MCP server. The same typed queries and mutations used by the browser become tools that an authenticated agent can discover and call directly. You do not maintain a second agent API, generate an","group":{"id":"agents","title":"Agents and generation"},"url":"https://docs.clank.run/docs/per-app-mcp","source":"docs/per-app-mcp.md","headings":["One app, one contract, one data boundary","Queries and mutations become MCP tools","Connect an agent","Keep the UI and MCP contract synchronized","Verify an app's MCP surface"],"tableOfContents":[{"id":"one-app-one-contract-one-data-boundary","title":"One app, one contract, one data boundary","level":2},{"id":"queries-and-mutations-become-mcp-tools","title":"Queries and mutations become MCP tools","level":2},{"id":"connect-an-agent","title":"Connect an agent","level":2},{"id":"keep-the-ui-and-mcp-contract-synchronized","title":"Keep the UI and MCP contract synchronized","level":2},{"id":"verify-an-apps-mcp-surface","title":"Verify an app's MCP surface","level":2}],"markdown":"# The MCP server built into every app\n\nEvery Clank app with a backend is also its own MCP server. The same typed queries and mutations\nused by the browser become tools that an authenticated agent can discover and call directly. You\ndo not maintain a second agent API, generate an OpenAPI client, or run a separate MCP process.\n\nFor a deployed project, connect to:\n\n```text\nhttps://<project>.apps.clank.run/__clank/mcp\n```\n\nA custom domain uses the same path:\n\n```text\nhttps://app.example.com/__clank/mcp\n```\n\nThe generated starter mounts this endpoint automatically. During local development it is\n`http://127.0.0.1:3000/__clank/mcp` unless the application uses a different port.\n\n## One app, one contract, one data boundary\n\nEach application has its own MCP endpoint, server identity, OAuth clients, application users,\nresource-bound tokens, backend contract, and isolated database. There is no global MCP server\nthat can read every hosted project's data.\n\n```text\nsrc/backend.ts\n    ├── browser client ── query / mutation ─┐\n    └── MCP client ───── tool call ─────────┤\n                                            ▼\n                                  same handler, auth,\n                                  validation, transaction,\n                                  and application database\n```\n\nThis is the important rule: if a UI operation reads or changes server data, implement it as a\nbackend query or mutation. The browser and MCP then stay aligned because both invoke the same\nfunction. Do not add a separate UI-only persistence route.\n\n## Queries and mutations become MCP tools\n\nConsider a small authenticated Todo backend:\n\n```ts\nimport {\n  defineAuth,\n  defineBackend,\n  defineDatabase,\n  defineTable,\n  s,\n} from \"@clank.run/framework\";\n\nconst auth = defineAuth();\nconst schema = defineDatabase({\n  todos: defineTable({\n    title: s.string({ min: 1, max: 160 }),\n    done: s.boolean(),\n  }).owned(),\n});\n\nexport const backend = defineBackend({ schema, auth }).functions(\n  ({ query, mutation }) => ({\n    todos: {\n      list: query({\n        description: \"List the signed-in user's todos.\",\n        args: {},\n        handler: ({ db }) => db.table(\"todos\").collect(),\n      }),\n\n      add: mutation({\n        description: \"Create a todo for the signed-in user.\",\n        args: {\n          title: s.string({\n            min: 1,\n            max: 160,\n            description: \"Todo title\",\n          }),\n        },\n        agent: { destructive: false },\n        handler: ({ db }, { title }) =>\n          db.table(\"todos\").insert({ title, done: false }),\n      }),\n\n      remove: mutation({\n        description: \"Permanently delete a todo.\",\n        args: {\n          id: s.id(\"todos\"),\n          version: s.number({ integer: true, min: 1 }),\n        },\n        agent: { destructive: true },\n        handler: ({ db }, { id, version }) =>\n          db.table(\"todos\").delete(id, { ifVersion: version }),\n      }),\n    },\n  }),\n);\n```\n\nClank derives the agent contract directly from that function tree:\n\n| Backend function | MCP tool | Permission | Behavior |\n| --- | --- | --- | --- |\n| Query `todos.list` | `todos.list` | `agent:read` | Read-only and idempotent |\n| Mutation `todos.add` | `todos.add` | `agent:write` | Validated additive write |\n| Mutation `todos.remove` | `todos.remove` | `agent:write` | Validated destructive write |\n\nThe `args` validators become JSON Schema tool inputs. `description` explains the operation to\npeople and agents. The optional `agent` metadata describes whether a mutation is destructive,\nidempotent, or able to communicate outside the app. Set `agent: false` or\n`agent: { enabled: false }` only when a server function is intentionally internal.\n\nThe browser uses the same references:\n\n```ts\nconst client = createClient<typeof backend>();\nconst todos = client.live(client.api.todos.list);\n\nawait client.mutate(client.api.todos.add, {\n  title: \"Connect the app to an agent\",\n});\n```\n\nAn MCP client sees and invokes `todos.list` and `todos.add`; it does not automate those browser\ncontrols. Both paths reach the same handler and therefore share runtime validation, `.owned()`\nuser isolation, transaction rollback, optimistic concurrency, and live-update notifications.\n\n## Connect an agent\n\nGive the application MCP URL to any remote MCP client that supports Streamable HTTP and OAuth.\nFor Codex:\n\n```sh\ncodex mcp add my-app \\\n  --url https://my-app.apps.clank.run/__clank/mcp\ncodex mcp login my-app\n```\n\nThe browser opens the application's sign-in and consent page. After approval, it redirects to the\nMCP client's registered callback. The client receives a short-lived token restricted to this\napplication and exact MCP resource. Nothing needs to be copied back, and the person connecting\ndoes not need the Clank deployment CLI or access to the deployment account.\n\nThe agent never receives the user's password, browser session cookie, or CSRF token. A read grant\ncan list and call queries. A write grant can also call mutations. Normal application checks still\napply, including required login, verified email, roles, record ownership, argument validation,\nand document-version conflicts.\n\n## Keep the UI and MCP contract synchronized\n\nTreat `src/backend.ts` as the single source of truth:\n\n1. Add or change the backend query or mutation.\n2. Update its argument schema, `description`, and `agent` safety metadata.\n3. Make the UI call that same typed function reference.\n4. Build, test both paths, and deploy them together.\n\nClank fingerprints every agent-visible name, schema, description, scope, and annotation. A\ncontract change produces a new revision. Discovery responses require revalidation, tool lists\nhave a zero freshness lifetime, and a deployment invalidates existing MCP sessions so compliant\nclients initialize again and rediscover the current tools. An unknown stale tool also returns a\nstructured refresh hint.\n\nThis prevents the MCP action list from silently remaining on an older release while the UI moves\nahead. A client connected before session-aware revisions were introduced may need one manual\nreconnect; later deployments refresh automatically.\n\n## Verify an app's MCP surface\n\nBefore deployment, check that every server-backed UI operation has one matching backend function\nand that internal functions are intentionally hidden. After deployment:\n\n- open `https://<project>.apps.clank.run/.well-known/clank` to confirm the advertised endpoint\n  and contract revision;\n- compare the agent-enabled entries in `GET /__clank/manifest` with authenticated MCP\n  `tools/list`;\n- confirm every tool has a precise description, bounded input schema, correct read/write scope,\n  and honest destructive/idempotent annotations; and\n- call representative queries and mutations as two different users to prove owned data remains\n  isolated.\n\nContinue with [Agent protocol](agent-protocol.md) for the full MCP transport, discovery, OAuth,\nscope, freshness, and security contract. Read [Full-stack applications](full-stack.md) for backend\nimplementation details and [Authentication](auth.md) for application identity and authorization.\n"}