Validates the MCP-client loop end-to-end against one real MCP server before the full v1.15 port. New services/mcp-client.ts wraps @modelcontextprotocol/sdk v1.29.0 with Streamable HTTP transport. On startup (when MCP_CONTEXT7_URL is set), connects to Context7, discovers tools via tools/list, wraps each as a ToolDef prefixed context7_<name>, and appends to ALL_TOOLS via appendMcpTools. Read-only invariant guard rejects any tool with readOnlyHint: false. Tool dispatch is transparent — executeToolCall routes MCP calls through the ToolDef execute wrapper, which strips the prefix before calling the MCP server. Result size capped at 5MB with truncation. Graceful degradation: server down at startup → zero tools; server down mid-session → error result, model self-corrects. Adversarial review caught that a Zod .default() on the URL config made MCP always-on instead of opt-in — fixed by removing the default. MCP_CONTEXT7_URL must be explicitly set to enable. ALL_TOOLS changed from ReadonlyArray to mutable to support late-registration. appendMcpTools re-sorts and rebuilds TOOLS_BY_NAME after append. 348/348 server tests passing (16 new mcp-client tests). No schema changes, no frontend changes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3.9 KiB
3.9 KiB
v1.14.1-mcp-poc tasks
B1 — Backups
apps/server/src/services/tools.tsapps/server/src/config.tsapps/server/src/index.ts
B2 — Install @modelcontextprotocol/sdk
pnpm -C apps/server add @modelcontextprotocol/sdk- Verify
pnpm -C apps/server buildstill works after install - Note the installed version
B3 — Config extension
apps/server/src/config.ts— addMCP_CONTEXT7_URL(string, optional, defaulthttps://mcp.context7.com/mcp)apps/server/src/config.ts— addMCP_CONTEXT7_API_KEY(string, optional)- Both via Zod
.optional()with.default()for the URL
B4 — MCP client service
- NEW
apps/server/src/services/mcp-client.ts - Import
Client,StreamableHTTPClientTransportfrom@modelcontextprotocol/sdk/client initialize(config, log)— connect to Context7, calltools/list, wrap each as ToolDef, apply read-only guardcallTool(name, args)— call MCP servertools/call, extract text content, return as stringgetTools()— return wrapped ToolDef[]isInitialized()— boolean- Read-only guard: skip tools with
annotations?.readOnly === false; accept all others - Graceful degradation: catch connection errors, log warning, expose zero tools
- Tool name prefixing:
context7_<original_name> - ToolDef wrapping: map MCP inputSchema (JSONSchema) to ToolJsonSchema
function.parameters; usez.any()for Zod inputSchema (MCP already validated on the server side) - Execute wrapper: strip
context7_prefix before calling MCP, join result content blocks with\n
B5 — Tool registration (lazy-init)
apps/server/src/services/tools.ts— convertALL_TOOLSfrom a module-level constant to a lazy-initialized array- Add
initializeTools(mcpTools: ToolDef[])function that builds the final sorted list TOOLS_BY_NAME,READ_ONLY_TOOL_NAMESderived from the initialized list- Ensure all existing callers of
ALL_TOOLS/TOOLS_BY_NAMEstill work (they import from tools.ts — verify the export shape) - OR simpler: keep ALL_TOOLS as-is (native tools), add
appendMcpTools(tools)that mutates + re-sorts + rebuilds TOOLS_BY_NAME. Less clean but less invasive.
B6 — Startup wiring
apps/server/src/index.ts— afterapplySchema(), before route registration:- If
config.MCP_CONTEXT7_URLis set:await mcpClient.initialize(config, app.log) appendMcpTools(mcpClient.getTools())(or equivalent)- Log tool count
- If
- If URL not set: skip, log "mcp: Context7 not configured, skipping"
B7 — Verification
npx tsc --noEmit -p apps/server— 0 errorspnpm -C apps/server test— all existing tests pass (MCP client is startup-only; tests don't initialize it)pnpm -C apps/web build— green (no web changes)
B8 — Unit tests
- NEW
apps/server/src/services/__tests__/mcp-client.test.ts - Test: tool wrapping produces correct ToolDef shape (name, description, jsonSchema, execute fn)
- Test: read-only guard rejects tools with
readOnly: false - Test: read-only guard accepts tools with
readOnly: trueor no annotations - Test: name prefixing —
resolve-library-id→context7_resolve-library-id - Test: result extraction — single text content block → string; multiple → joined with
\n - Test: error result — MCP error →
{error: true, output: ...}shape
B9 — Deploy + smoke
- Add
MCP_CONTEXT7_URL=https://mcp.context7.com/mcpto docker-compose env (or .env) docker compose up --build -d- Check logs for MCP initialization message
- Live-smoke: send a chat asking about AI SDK docs via Context7
- Verify tool calls + results render normally
B10 — Docs + tag
CHANGELOG.mdentryboocode_roadmap.mdretrospective bulletCLAUDE.md— mention MCP client in the tools/services section- Commit, tag
v1.14.1-mcp-poc, push, rebuild