编码母题 / P1 / 检索与 Agent 协议 / 02
MCP工具适配器
先读清题意与边界,独立尝试,再展开思路并对照可运行的参考代码。
重要程度岗位专项难度中等预计完成30–40 分钟
题目是什么
把 MCP Client 的工具描述和调用适配成内部 Registry 能理解的接口;初始化、调用、错误映射和关闭都有明确状态边界。
输入输出示例
关闭后再调用工具应失败;MCP isError 结果应映射成内部错误。
练习起手式与验证
复制代码后补全空方法,再运行下方三组验证;复杂题所需的辅助类型与输入准备一并放在代码块中。
/*
* 题目:MCP工具适配器
* 要求:把 MCP Client 的工具描述和调用适配成内部 Registry 能理解的接口;
* 初始化、调用、错误映射和关闭都有明确状态边界。
* 示例:关闭后再调用工具应失败;MCP isError 结果应映射成内部错误。
*/
class MCPToolAdapter {
constructor(client) {}
async initialize() {}
list() {}
get(name) {}
async call(name, arguments_, signal) {}
async close() {}
}
(async () => {
const calls = [];
let closed = 0;
const client = {
listTools: async () => ({
tools: [
{
name: "search",
description: "搜索",
inputSchema: { type: "object" },
},
],
}),
callTool: async (request) => {
calls.push(request);
return { content: [{ type: "text", text: "ok" }] };
},
close: async () => {
closed += 1;
},
};
const adapter = await new MCPToolAdapter(client).initialize();
console.log(adapter.list()[0].name); // 预期:"search"
const result = await adapter.get("search").execute({ q: "x" });
console.log(result.content[0].text); // 预期:"ok"
console.log(calls[0]); // 预期:{ name: "search", arguments: { q: "x" } }
})().catch(console.error);
/*
* 题目:MCP工具适配器
* 要求:把 MCP Client 的工具描述和调用适配成内部 Registry 能理解的接口;
* 初始化、调用、错误映射和关闭都有明确状态边界。
* 示例:关闭后再调用工具应失败;MCP isError 结果应映射成内部错误。
*/
class MCPToolAdapter {
constructor(client) {}
async initialize() {}
list() {}
get(name) {}
async call(name, arguments_, signal) {}
async close() {}
}
(async () => {
const calls = [];
let closed = 0;
const client = {
listTools: async () => ({
tools: [
{
name: "search",
description: "搜索",
inputSchema: { type: "object" },
},
],
}),
callTool: async (request) => {
calls.push(request);
return { content: [{ type: "text", text: "ok" }] };
},
close: async () => {
closed += 1;
},
};
const adapter = await new MCPToolAdapter(client).initialize();
console.log(adapter.list()[0].name); // 预期:"search"
const result = await adapter.get("search").execute({ q: "x" });
console.log(result.content[0].text); // 预期:"ok"
console.log(calls[0]); // 预期:{ name: "search", arguments: { q: "x" } }
})().catch(console.error);解题思路
展开思路、不变量与常见边界
关键切入点:协议适配器隔离外部 Transport 与内部 Runtime,状态机限制未初始化或已关闭调用。
核心模型:协议适配层隔离外部 Transport 与内部 Runtime,不能让业务代码依赖 MCP 响应细节。不变量:初始化完成前不可调用;工具名冲突立即失败;关闭后不再 发请求;外部错误转换但保留可观测 cause。生产环境还需重连、能力变化通知、 Schema 验证、权限白名单和内容大小限制。
参考代码与验证
“关键机制”注释用于解释实现,下载链接提供未经页面注释修改的原始 JS 文件。
// 关键机制:协议适配器隔离外部 Transport 与内部 Runtime,状态机限制未初始化或已关闭调用。
class MCPToolAdapter {
constructor(client) {
this.client = client;
this.tools = new Map();
this.state = "new";
}
async initialize() {
if (this.state !== "new") throw new Error("adapter already initialized");
const response = await this.client.listTools();
for (const tool of response.tools || []) {
if (!tool?.name) throw new Error("invalid MCP tool");
if (this.tools.has(tool.name))
throw new Error(`duplicate MCP tool: ${tool.name}`);
this.tools.set(tool.name, {
name: tool.name,
description: tool.description,
schema: tool.inputSchema,
execute: (arguments_, context = {}) =>
this.call(tool.name, arguments_, context.signal),
});
}
this.state = "ready";
return this;
}
list() {
if (this.state !== "ready") throw new Error("adapter not ready");
return [...this.tools.values()].map(
({ execute, ...description }) => description,
);
}
get(name) {
if (this.state !== "ready") throw new Error("adapter not ready");
return this.tools.get(name);
}
async call(name, arguments_, signal) {
if (this.state !== "ready") throw new Error("adapter not ready");
if (signal?.aborted) throw signal.reason || new Error("aborted");
const response = await this.client.callTool(
{ name, arguments: arguments_ },
{ signal },
);
if (response.isError) {
const error = new Error("MCP tool execution failed");
error.code = "MCP_TOOL_ERROR";
error.content = response.content;
throw error;
}
return {
content: response.content || [],
structuredContent: response.structuredContent,
};
}
async close() {
if (this.state === "closed") return;
this.state = "closed";
this.tools.clear();
await this.client.close?.();
}
}
// 三组以上可直接运行的验证;异步题使用独立执行块。
(async () => {
const calls = [];
let closed = 0;
const client = {
listTools: async () => ({
tools: [
{
name: "search",
description: "搜索",
inputSchema: { type: "object" },
},
],
}),
callTool: async (request) => {
calls.push(request);
return { content: [{ type: "text", text: "ok" }] };
},
close: async () => {
closed += 1;
},
};
const adapter = await new MCPToolAdapter(client).initialize();
console.log(adapter.list()[0].name); // 预期:"search"
const result = await adapter.get("search").execute({ q: "x" });
console.log(result.content[0].text); // 预期:"ok"
console.log(calls[0]); // 预期:{ name: "search", arguments: { q: "x" } }
await adapter.close();
await adapter.close();
console.log(closed); // 预期:1
console.log((() => { try { (() => adapter.list())(); return false; } catch { return true; } })()); // 预期:true(抛出异常)
})().catch(console.error);
// 关键机制:协议适配器隔离外部 Transport 与内部 Runtime,状态机限制未初始化或已关闭调用。
class MCPToolAdapter {
constructor(client) {
this.client = client;
this.tools = new Map();
this.state = "new";
}
async initialize() {
if (this.state !== "new") throw new Error("adapter already initialized");
const response = await this.client.listTools();
for (const tool of response.tools || []) {
if (!tool?.name) throw new Error("invalid MCP tool");
if (this.tools.has(tool.name))
throw new Error(`duplicate MCP tool: ${tool.name}`);
this.tools.set(tool.name, {
name: tool.name,
description: tool.description,
schema: tool.inputSchema,
execute: (arguments_, context = {}) =>
this.call(tool.name, arguments_, context.signal),
});
}
this.state = "ready";
return this;
}
list() {
if (this.state !== "ready") throw new Error("adapter not ready");
return [...this.tools.values()].map(
({ execute, ...description }) => description,
);
}
get(name) {
if (this.state !== "ready") throw new Error("adapter not ready");
return this.tools.get(name);
}
async call(name, arguments_, signal) {
if (this.state !== "ready") throw new Error("adapter not ready");
if (signal?.aborted) throw signal.reason || new Error("aborted");
const response = await this.client.callTool(
{ name, arguments: arguments_ },
{ signal },
);
if (response.isError) {
const error = new Error("MCP tool execution failed");
error.code = "MCP_TOOL_ERROR";
error.content = response.content;
throw error;
}
return {
content: response.content || [],
structuredContent: response.structuredContent,
};
}
async close() {
if (this.state === "closed") return;
this.state = "closed";
this.tools.clear();
await this.client.close?.();
}
}
// 三组以上可直接运行的验证;异步题使用独立执行块。
(async () => {
const calls = [];
let closed = 0;
const client = {
listTools: async () => ({
tools: [
{
name: "search",
description: "搜索",
inputSchema: { type: "object" },
},
],
}),
callTool: async (request) => {
calls.push(request);
return { content: [{ type: "text", text: "ok" }] };
},
close: async () => {
closed += 1;
},
};
const adapter = await new MCPToolAdapter(client).initialize();
console.log(adapter.list()[0].name); // 预期:"search"
const result = await adapter.get("search").execute({ q: "x" });
console.log(result.content[0].text); // 预期:"ok"
console.log(calls[0]); // 预期:{ name: "search", arguments: { q: "x" } }
await adapter.close();
await adapter.close();
console.log(closed); // 预期:1
console.log((() => { try { (() => adapter.list())(); return false; } catch { return true; } })()); // 预期:true(抛出异常)
})().catch(console.error);
复杂度分析
时间复杂度:初始化 O(T),调用 O(1) 适配开销。初始化注册 T 个工具;单次调用只检查状态并转发。
空间复杂度:O(T)。Map 保留 T 个工具适配描述;外部传输缓冲不计。