Files
boocode/openspec/changes/v1.14.1-mcp-poc/tasks.md
indifferentketchup 5692e99a5d v1.14.1-mcp-poc: single-server MCP client against Context7
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>
2026-05-23 21:58:09 +00:00

3.9 KiB

v1.14.1-mcp-poc tasks

B1 — Backups

  • apps/server/src/services/tools.ts
  • apps/server/src/config.ts
  • apps/server/src/index.ts

B2 — Install @modelcontextprotocol/sdk

  • pnpm -C apps/server add @modelcontextprotocol/sdk
  • Verify pnpm -C apps/server build still works after install
  • Note the installed version

B3 — Config extension

  • apps/server/src/config.ts — add MCP_CONTEXT7_URL (string, optional, default https://mcp.context7.com/mcp)
  • apps/server/src/config.ts — add MCP_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, StreamableHTTPClientTransport from @modelcontextprotocol/sdk/client
  • initialize(config, log) — connect to Context7, call tools/list, wrap each as ToolDef, apply read-only guard
  • callTool(name, args) — call MCP server tools/call, extract text content, return as string
  • getTools() — 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; use z.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 — convert ALL_TOOLS from 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_NAMES derived from the initialized list
  • Ensure all existing callers of ALL_TOOLS / TOOLS_BY_NAME still 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 — after applySchema(), before route registration:
    • If config.MCP_CONTEXT7_URL is set: await mcpClient.initialize(config, app.log)
    • appendMcpTools(mcpClient.getTools()) (or equivalent)
    • Log tool count
  • If URL not set: skip, log "mcp: Context7 not configured, skipping"

B7 — Verification

  • npx tsc --noEmit -p apps/server — 0 errors
  • pnpm -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: true or no annotations
  • Test: name prefixing — resolve-library-idcontext7_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/mcp to 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.md entry
  • boocode_roadmap.md retrospective bullet
  • CLAUDE.md — mention MCP client in the tools/services section
  • Commit, tag v1.14.1-mcp-poc, push, rebuild