add-mcp-tool
Add a tool to one of the MCP servers MCP clients (Claude, IDEs, agents) connect to - the api's (todos, at /api/mcp) or the AI service's (documents, at /ai/mcp). Use when the user wants an AI client to
SKILL.md.claude/skills/add-mcp-tool/SKILL.md
GitHubAdd an MCP tool
Both servers expose only allowlisted tools, each behind a scope the user approved for one workspace on the consent page. Tools act as the user, in that workspace, under row-level security, like any request. docs/auth.md, "OAuth for MCP clients", has the whole flow.
- Scope. Reuse one from
MCP_SCOPESinpackages/contracts/src/mcp.ts, or add one there and toMCP_SERVER_SCOPESfor the server that serves it. A new scope needs its consent text,oauth.scopes.<scope>, in everypackages/i18n/messages/*.json, and the api's resource policy inapps/api/src/auth/auth-plugins.ts(themcpplugin'sresources) picks it up fromMCP_SERVER_SCOPES. - The tool, in the api (
apps/api/src/mcp/mcp.server.ts): register it increateMcpServerbehind its scope, as an explicit adapter over a service method, with the contract's own input schema (packages/contracts/src/api). Errors areAppErrors, whichrun()turns into a tool error in the caller's language. A service the tool needs goes intoMcpServerDependencies. - The tool, in the AI service (
apps/ai/app/mcp_server.py): a@server.toolincreate_mcp_server, starting withguard()for the caller, throughtenant(org_id)for data, returning a Pydantic model fromapp/schemas.py. - Tests. api:
apps/api/test/mcp.integration.test.ts(real OAuth tokens through the official MCP client): the tool is listed only with its scope, it acts in the approved workspace only, and a revoked grant stops it. ai:apps/ai/tests/test_mcp.py. - Update the tools table in
docs/auth.md.
Done when
bun run test:integrationpasses (the MCP suites), andbun run check-types.- The
security-revieweragent (a new capability for outside clients) reports nothing blocking; for the AI service, thepython-revieweragent too. - By hand, once: a real MCP client connected to the local site lists and calls it (docs/testing.md, "Not tested automatically").