feat(api): remote MCP server at /api/mcp + keyless key-management routes#224
Conversation
Add a stateless Streamable HTTP MCP server (spec 2025-06-18) at POST /api/mcp so agents can connect over the network with an AgentState API key or an OAuth/capability access token, instead of running the local stdio bridge. - middleware/mcp-auth.ts: accepts `as_live_` keys and `as_cap_` capability tokens (honoring expiry/revocation), sets projectId + capabilityScopes, and returns 401 with `WWW-Authenticate: Bearer resource_metadata="..."` pointing at the OAuth protected-resource document. Normalizes legacy capability scopes (lease:write/claim:write) to their API form (leases:write/claims:write) so a capability token can satisfy the lease/claim tools it was minted for. - routes/mcp: JSON-RPC dispatcher (initialize / tools/list / tools/call / ping) + a 15-tool registry calling the conversation/state/lease/claim/key services directly. Per-tool scope enforcement; tool failures returned as isError results, protocol errors as JSON-RPC errors. Tools reuse the stdio MCP's tool names, descriptions, and zod input shapes (zod-to-json-schema for tools/list). New tools: create_api_key / list_api_keys / revoke_api_key with the same subset-delegation rule as the dashboard key routes. - routes/v1-keys.ts: keyless POST/GET/DELETE /api/v1/keys so SDK/stdio clients can manage keys using only their API key (project from auth context). - Mount both routers in index.ts. Tests: test/mcp.test.ts covers initialize, tools/list, tools/call, scope enforcement, capability-token lease delegation, protocol-version negotiation, 401 challenge, and 405 on GET. Full suite: 302 passing. Co-Authored-By: Duyet Le <[email protected]> Co-Authored-By: duyetbot <[email protected]>
|
Warning Review limit reached
More reviews will be available in 49 minutes and 56 seconds. Learn how PR review limits work. Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file). ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits. 🚦 How do rate limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (7)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Code Review
This pull request introduces a remote Model Context Protocol (MCP) server over a stateless Streamable HTTP transport at /api/mcp, along with keyless API-key management at /api/v1/keys for non-dashboard clients. It adds custom authentication middleware (mcpAuth) to support both regular API keys and capability tokens, registers a comprehensive suite of tools (for conversations, state, leases, claims, and keys), and includes extensive integration tests. The review feedback highlights critical improvement opportunities in the tool handlers: specifically, the create_claim and mint_capability_token handlers should check for domain errors and throw a ToolError rather than returning service results directly, and the revoke_api_key handler should use the shared invalidateAuthCacheEntries helper to keep cache invalidation consistent and DRY.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| return claimsService.createClaim(c.get("db"), { | ||
| projectId: c.get("projectId"), | ||
| subjectType: args.subject_type, | ||
| subjectId: args.subject_id, | ||
| statement: args.statement, | ||
| evidence: args.evidence.map(mapClaimEvidence), | ||
| }); | ||
| }, | ||
| }, |
There was a problem hiding this comment.
The create_claim tool handler returns the result of claimsService.createClaim directly without checking for a domain error. In this codebase, services typically return an object containing an optional error property on failure. If createClaim fails and returns { error }, the MCP client will receive a successful JSON-RPC response containing the error object instead of a proper JSON-RPC error with isError: true.\n\nWe should check result.error and throw a ToolError to ensure correct error propagation, matching the pattern used in other tool handlers (e.g., store_conversation, upsert_state).
handler: async (c, args: z.infer<typeof createClaimSchema>) => {\n const result = await claimsService.createClaim(c.get('db'), {\n projectId: c.get('projectId'),\n subjectType: args.subject_type,\n subjectId: args.subject_id,\n statement: args.statement,\n evidence: args.evidence.map(mapClaimEvidence),\n });\n if (result.error) throw new ToolError(result.error.code, result.error.message);\n return result;\n },| return capabilityTokensService.createCapabilityToken(c.get("db"), c.get("projectId"), { | ||
| name: args.name, | ||
| scopes: args.scopes, | ||
| expires_at: args.expires_at, | ||
| }); |
There was a problem hiding this comment.
Similar to create_claim, the mint_capability_token tool handler returns the result of capabilityTokensService.createCapabilityToken directly without checking for a domain error. If the service fails and returns { error }, the tool will return a successful JSON-RPC response containing the error object instead of throwing a ToolError with isError: true. We should check result.error and throw a ToolError to ensure correct error propagation.
const result = await capabilityTokensService.createCapabilityToken(c.get('db'), c.get('projectId'), {\n name: args.name,\n scopes: args.scopes,\n expires_at: args.expires_at,\n });\n if (result.error) throw new ToolError(result.error.code, result.error.message);\n return result;| const cache = c.env.AUTH_CACHE; | ||
| if (cache) { | ||
| c.executionCtx.waitUntil(cache.delete(`auth:hash:${revokedHash}`)); | ||
| } |
There was a problem hiding this comment.
To keep cache invalidation consistent and DRY, use the shared invalidateAuthCacheEntries helper instead of manually deleting the cache key. This ensures that if the cache key structure or invalidation logic changes in the future, it is updated in one place and prevents potential authorization bypasses due to stale cache.\n\nNote: You will need to import invalidateAuthCacheEntries from ../../lib/helpers at the top of the file.
| const cache = c.env.AUTH_CACHE; | |
| if (cache) { | |
| c.executionCtx.waitUntil(cache.delete(`auth:hash:${revokedHash}`)); | |
| } | |
| invalidateAuthCacheEntries(c, [revokedHash]); |
# Conflicts: # packages/api/src/index.ts
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
What
Adds a remote MCP server at
POST /api/mcp(MCP spec 2025-06-18, stateless Streamable HTTP) so agents can connect over the network with an AgentState API key or an OAuth/capability access token — no local stdio bridge required. Also adds keyless API-key management REST routes so non-dashboard clients (SDK / stdio MCP) can manage keys with just their API key.This is unit U1 of the larger remote-MCP + OAuth + scoped-keys feature. It composes with U2 (OAuth): the
401challenge points at/.well-known/oauth-protected-resource, which U2's discovery router serves.Changes
middleware/mcp-auth.ts— acceptsas_live_API keys andas_cap_capability tokens (honoring expiry/revocation). SetsprojectId+capabilityScopes. On missing/invalid token returns401withWWW-Authenticate: Bearer resource_metadata="<origin>/.well-known/oauth-protected-resource". Keeps the 300ms constant-time failure delay. Normalizes legacy capability scopes (lease:write/claim:write) to API form (leases:write/claims:write) so capability tokens can satisfy the lease/claim tools they were minted for.routes/mcp/index.ts— JSON-RPC dispatcher:initialize(with protocol-version negotiation),tools/list,tools/call,ping, notifications→202, unknown method→-32601,GET→405. Origin check for DNS-rebinding protection. Tool failures returned asisErrorresults; unexpected errors logged server-side + generic message returned (no internal leak).routes/mcp/tools.ts— 15-tool registry calling the conversation / state / lease / claim / key services directly (project from auth context). Reuses the stdio MCP's tool names, descriptions, and zod input shapes;zod-to-json-schemagenerates the JSON Schemas fortools/list. New tools:create_api_key/list_api_keys/revoke_api_keywith the same subset-delegation rule as the dashboard key routes.routes/v1-keys.ts— keylessPOST/GET/DELETE/api/v1/keys(project from auth context, never another project's keys).index.ts— mounts both routers.zod-to-json-schemadependency.Tests
test/mcp.test.ts(12 tests) covers:initializeserverInfo + version negotiation,tools/list,tools/call(list + store→recall round-trip), scope enforcement (scoped key out-of-scope →isError), capability-token lease delegation (singular→plural scope normalization),401+WWW-Authenticate,GET→405. Full API suite: 302 passing. Typecheck + biome clean. Verified end-to-end againstwrangler dev.Review
Ran
/code-review(xhigh). Fixed the one high-severity finding it surfaced: capability tokens carry singular scope forms but lease/claim tools required the plural API forms, so capability-token delegation for leases/claims would always 403 — fixed by normalizing at the auth boundary (now covered by a test). Also addressed protocol-version negotiation and internal-error logging/leak.Follow-up (not in this PR): the child-scope delegation block is now duplicated across
routes/keys.ts,routes/v1-keys.ts, androutes/mcp/tools.ts. Consider extracting a sharedresolveChildScopes()helper — kept consistent-with-existing rather than refactoring the foundation here.Co-Authored-By: Duyet Le [email protected]
Co-Authored-By: duyetbot [email protected]