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>
This commit is contained in:
2026-05-23 21:58:09 +00:00
parent f4a97808ad
commit 5692e99a5d
11 changed files with 612 additions and 6 deletions

View File

@@ -24,6 +24,8 @@ import { listSkills } from './services/skills.js';
import * as compaction from './services/compaction.js';
import { configureModelContext } from './services/model-context.js';
import { cleanupTruncations } from './services/truncate.js';
import { initialize as initMcpClient, getTools as getMcpTools } from './services/mcp-client.js';
import { appendMcpTools } from './services/tools.js';
async function main() {
const config = loadConfig();
@@ -69,6 +71,22 @@ async function main() {
// default_generation_settings.n_ctx — the value persisted as messages.ctx_max.
configureModelContext({ llamaSwapUrl: config.LLAMA_SWAP_URL });
// v1.14.1-mcp-poc: connect to Context7 MCP server and register discovered
// tools into ALL_TOOLS. Runs before route registration so the tool list is
// complete when the first inference request arrives. Graceful degradation:
// if Context7 is unreachable, zero MCP tools are registered and BooCode
// functions normally with native tools.
if (config.MCP_CONTEXT7_URL) {
await initMcpClient(config, app.log);
const mcpTools = getMcpTools();
if (mcpTools.length > 0) {
appendMcpTools(mcpTools);
app.log.info({ count: mcpTools.length }, 'mcp: registered Context7 tools');
}
} else {
app.log.info('mcp: MCP_CONTEXT7_URL not configured, skipping');
}
await app.register(fastifyWebsocket);
app.get('/api/health', async () => {