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>
81 lines
3.9 KiB
Markdown
81 lines
3.9 KiB
Markdown
# 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-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/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
|