03 · MCP 工具桥接
用 wrapMcpTool 把 MCP 工具定义转换为内部 Tool 接口——模型不需要知道一个工具是内置的还是外部的。
统一的工具接口
第 3 章定义了 Tool 接口:
interface Tool {
name: string;
description: string;
parameters: { type: "object"; properties: Record<string, unknown>; required: string[] };
execute: (args: Record<string, unknown>, state: AgentState) => Promise<string>;
}MCP server 返回的工具定义:
interface McpToolDefinition {
name: string;
description: string;
inputSchema: {
type: "object";
properties?: Record<string, unknown>;
required?: string[];
};
}两者高度相似:都有 name、description 和 JSON Schema 参数。唯一不同的是参数字段名(parameters vs inputSchema)和可选性(properties 和 required 在 MCP 中是可选的)。
wrapMcpTool 桥接函数
在 src/mcp/client.ts 中:
export function wrapMcpTool(
client: McpClient,
definition: McpToolDefinition,
): Tool {
return {
name: definition.name,
description: definition.description,
parameters: {
type: "object",
properties: definition.inputSchema.properties ?? {},
required: definition.inputSchema.required ?? [],
},
execute: async (args: Record<string, unknown>, _state: AgentState) => {
const result = await client.callTool(definition.name, args);
return result.content.map((c) => c.text).join("\n");
},
};
}这个函数做了三件事:
1. 字段映射。 inputSchema → parameters,可选字段用 ?? 提供默认值。
2. 执行桥接。 execute 调用 client.callTool(),把 MCP 的响应格式转换为简单的字符串。MCP 工具可能返回多段内容(content 数组),拼接为一个字符串返回。
3. 错误透传。 MCP 工具执行失败时,isError: true,错误信息作为字符串返回给模型。模型能看到"外部工具执行失败"并调整策略。
模型视角:统一的工具列表
桥接后,从模型的视角看,所有工具都是一样的:
内置工具:
search → Tool { name, description, parameters, execute }
read_file → Tool { name, description, parameters, execute }
MCP 工具(通过 wrapMcpTool 桥接):
search_docs → Tool { name, description, parameters, execute }
query_issues → Tool { name, description, parameters, execute }模型不需要知道一个工具是本地执行的还是远程调用的。它只看到统一的工具列表,按需调用。这是接口统一的威力——第 3 章设计的 Tool 接口恰好和 MCP 的工具定义高度兼容,桥接成本极低。
在 main.ts 中的集成
main.ts 中,MCP 工具在启动时加载并合并到工具列表:
let allTools: Tool[] = [...tools]; // 内置工具
const mcpServersJson = process.env.MCP_SERVERS;
if (mcpServersJson) {
const configs: McpServerConfig[] = JSON.parse(mcpServersJson);
for (const config of configs) {
const client = new McpClient(config);
await client.connect();
const mcpTools = await client.listTools();
const wrapped = mcpTools.map((def) => wrapMcpTool(client, def));
allTools = [...allTools, ...wrapped];
mcpClients.push(client);
}
}关键步骤:
- 从
MCP_SERVERS环境变量读取配置 - 为每个 server 创建
McpClient,连接并发现工具 - 用
wrapMcpTool把每个 MCP 工具包装为内部Tool - 合并到
allTools数组
然后 runAgent(state, model, allTools, ...) 使用合并后的工具列表。agent 循环不关心工具的来源——它只调用 tool.execute()。
清理
agent 退出时需要关闭 MCP 子进程:
rl.on("close", () => {
for (const client of mcpClients) {
client.disconnect();
}
// ...
});disconnect() 调用 process.kill() 关闭子进程。不留孤儿进程。
向后兼容
如果 MCP_SERVERS 环境变量不存在,allTools 就是内置工具,行为和第 8 章完全一样。MCP 是完全可选的扩展——不需要就不配置,零影响。
下一节讨论 MCP 的边界和设计取舍。
登录以继续阅读
解锁完整文档、代码示例及更多高级功能。