feat(api): streamable-HTTP MCP server endpoint (/api/mcp, PAT-authed) #331

Closed
opened 2026-06-28 21:44:59 +00:00 by james · 0 comments
Owner

Expose Carol's shared domain tool registry (#51) to external agent runtimes over a streamable-HTTP MCP endpoint, so a user can paste a Carol-issued PAT into Claude Code / opencode / a custom MCP client and read + write their own data. Part of epic #47; implements against ADR-0029 (agent runtime) and ADR-0030 (tool surface).

Scope

  • POST /api/mcp — Streamable HTTP MCP transport (JSON-RPC 2.0). PAT-authed via the existing identity reader (lib/auth/identity.ts getAuthIdentityAuthorization: Bearer <PAT>); no valid token → 401. Mirror the auth pattern the existing /api/agent/proposals/* routes already use (not in the public-route allowlist).
  • Per-user, structurally. The resolved userId is the only actor, threaded into the ToolContext from #51. Tools never take a user_id (ADR-0030); scoping stays at the repository layer; a cross-user proposal commit returns 404, not 403.
  • Exposes the #51 registry. tools/list returns the full registry (lib/agent/tools listTools()); tools/call executes a tool — read tools return data, write tools return the ProposedChange as the tool result and never mutate. Plus two meta-tools — commit_proposal(proposal_id) and reject_proposal(proposal_id) wired to commitProposal/rejectProposal — so an external agent applies a confirmed proposal via the standard MCP tool-call confirmation flow (the client surfaces the commit_proposal call to the user, which is the confirmation).
  • Transport: prefer the official MCP TypeScript SDK (@modelcontextprotocol/sdk, via a Next App Router adapter) for protocol correctness — handshake, capabilities, protocol-version negotiation. A minimal spec-compliant Streamable-HTTP JSON-RPC handler (initialize / notifications/initialized / tools/list / tools/call / ping, single application/json responses) is an acceptable fallback if SDK/Next integration proves impractical. Either way it must be connectable by Claude Code's MCP client.

Acceptance criteria

  • A JSON-RPC initializetools/listtools/call round-trip works over POST /api/mcp.
  • PAT-authed; missing/invalid token → 401; isolation holds (no cross-user data; cross-user proposal commit → 404).
  • Write tools/call returns a proposal and never mutates; commit_proposal (the #51 commit path) is the only mutator and writes the audit event.
  • Tests run on both DB engines (the tools hit the data layer).

Out of scope

  • The built-in PWA agent loop, LLM provider, conversations/messages tables, SSE chat — separate tickets.
  • Full OAuth for MCP (PAT bearer only for now), voice, and the external-agent setup docs (its own ticket — a brief connection note here is fine).

Depends on #51 (the registry + commit path) and #50/#48 (the ADRs).

Expose Carol's shared domain tool registry (#51) to external agent runtimes over a streamable-HTTP MCP endpoint, so a user can paste a Carol-issued PAT into Claude Code / opencode / a custom MCP client and read + write their own data. Part of epic #47; implements against ADR-0029 (agent runtime) and ADR-0030 (tool surface). ## Scope - **`POST /api/mcp`** — Streamable HTTP MCP transport (JSON-RPC 2.0). **PAT-authed** via the existing identity reader (`lib/auth/identity.ts` `getAuthIdentity` — `Authorization: Bearer <PAT>`); no valid token → 401. Mirror the auth pattern the existing `/api/agent/proposals/*` routes already use (not in the public-route allowlist). - **Per-user, structurally.** The resolved `userId` is the only actor, threaded into the `ToolContext` from `#51`. Tools never take a `user_id` (ADR-0030); scoping stays at the repository layer; a cross-user proposal commit returns **404, not 403**. - **Exposes the #51 registry.** `tools/list` returns the full registry (`lib/agent/tools` `listTools()`); `tools/call` executes a tool — read tools return data, **write tools return the `ProposedChange` as the tool result and never mutate**. Plus two meta-tools — **`commit_proposal(proposal_id)`** and **`reject_proposal(proposal_id)`** wired to `commitProposal`/`rejectProposal` — so an external agent applies a confirmed proposal via the standard MCP tool-call confirmation flow (the client surfaces the `commit_proposal` call to the user, which is the confirmation). - **Transport:** prefer the official MCP TypeScript SDK (`@modelcontextprotocol/sdk`, via a Next App Router adapter) for protocol correctness — handshake, capabilities, protocol-version negotiation. A minimal spec-compliant Streamable-HTTP JSON-RPC handler (`initialize` / `notifications/initialized` / `tools/list` / `tools/call` / `ping`, single `application/json` responses) is an acceptable fallback if SDK/Next integration proves impractical. Either way it must be connectable by Claude Code's MCP client. ## Acceptance criteria - [ ] A JSON-RPC `initialize` → `tools/list` → `tools/call` round-trip works over `POST /api/mcp`. - [ ] PAT-authed; missing/invalid token → 401; isolation holds (no cross-user data; cross-user proposal commit → 404). - [ ] Write `tools/call` returns a proposal and never mutates; `commit_proposal` (the #51 commit path) is the only mutator and writes the audit event. - [ ] Tests run on both DB engines (the tools hit the data layer). ## Out of scope - The built-in PWA agent loop, LLM provider, conversations/messages tables, SSE chat — separate tickets. - Full OAuth for MCP (PAT bearer only for now), voice, and the external-agent setup docs (its own ticket — a brief connection note here is fine). Depends on #51 (the registry + commit path) and #50/#48 (the ADRs).
james closed this issue 2026-06-28 22:06:08 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
james/carol#331
No description provided.