Admin MCP server
/api/mcp/admin exposes the To-Dos, Triage and Issues sections of
admin.gevell.com to Claude (Claude Code, Claude Desktop, claude.ai) as MCP
tools. It is a separate server from the public toolchain MCP at /api/mcp
(pdf_*, url_*, acr_*) and never shares tools with it.
Tracking issue: #4097.
Auth
Authorization: Bearer <api-key>, the same platform API keys as/api/mcp(api_keystable, shared rate-limit bucket).- Plus an admin check on every RPC (
isUserAdmin, i.e. an admin email orprofiles.role = 'admin'). Non-admin keys get403even forinitializeandtools/list, so they canβt see the tool names. - Writes are attributed to the key ownerβs email in the issue timeline and alerts, exactly as if theyβd used the admin UI.
Tools
| Family | Tools | Notes |
|---|---|---|
| To-dos (CRM tasks) | todos_list, todos_get, todos_create, todos_update, todos_listNotes, todos_addNote | todos_update with status: "done" completes a to-do |
| Triage | triage_listThreads, triage_getThread, triage_stats | Read-only |
| Issues | issues_list, issues_get, issues_create, issues_update, issues_addNote, issues_listLabels | resolved/closed require close_reason |
Deliberately not exposed (admin UI only): deletes, approving or rejecting triage actions, posting triage messages (that impersonates the submitter and starts a paid agent turn), reordering, label management and attachments.
How it works
Claude ββJSON-RPCβββΆ /api/mcp/admin (createMcpRoutes + adminRegistry) β requireBearerAuth β requireAdminKey βΌ mcp/admin/tools.ts ββcallAdmin()βββΆ in-process Hono app ββ /crm β adminCrm ββ /issues β adminIssues ββ /triage β adminTriage- Tools donβt re-implement anything:
callAdmin(mcp/admin/dispatch.ts) invokes the same route sub-apps the admin UI calls, so validation, state rules, the activity log and assignment/comment alerts stay in one place. - Those sub-apps start with
requireAuth, which expects a Supabase JWT. The dispatcher instead attaches the authenticated actor to a copy ofenvunder a module-privateSymbol(middleware/trusted-actor.ts). Runtime bindings and HTTP input canβt create symbol keys, so this canβt be forged from outside the process.requireAuthstill runs the deleted-account check, and each sub-appβs ownrequireAdminstill runs. - The transport (
mcp/transports/mcp-http.ts) is a factory,createMcpRoutes({ registry, serverName, resourcePath, instructions, authorize })./api/mcpand/api/mcp/adminare two instances of it.
Adding a tool
- Register it in
workers/api/src/mcp/admin/tools.tsonadminRegistry, delegating to an existing admin route viacallAdmin. - If the route isnβt mounted in the dispatcherβs inner app yet, add it in
mcp/admin/dispatch.ts. - Add a case to
src/__tests__/mcp/admin-mcp.test.ts.
Keep destructive or paid operations out unless thereβs an explicit decision to expose them.
Connecting Claude Code
Create an API key for an admin account in the dashboard, then:
claude mcp add --transport http gevell-admin https://api.gevell.com/api/mcp/admin --header "Authorization: Bearer <admin-api-key>"