13.3 工具适配器实现

适配器模式

MCP 工具和内置工具有不同的接口,适配器模式将 MCP 工具转换为 IToolPlugin 接口:

1
2
3
4
5
6
7
MCP Tool (外部)

MCPToolAdapter

IToolPlugin (内部)

ToolRegistry

MCPToolAdapter 类

类结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// src/mcp/adapter.ts

import type { IToolPlugin, ToolResult, ToolMetadata } from "../tools/types.js";
import type { MCPTool } from "./types.js";
import type { MCPManager } from "./manager.js";

export class MCPToolAdapter implements IToolPlugin {
readonly metadata: ToolMetadata;
private mcpManager: MCPManager;
private qualifiedName: string;

constructor(
serverName: string,
tool: MCPTool,
mcpManager: MCPManager
) {
// 创建限定名称(带命名空间)
this.qualifiedName = `${serverName}:${tool.name}`;
this.mcpManager = mcpManager;

// 转换为 ToolMetadata
this.metadata = {
name: this.qualifiedName,
description: tool.description || `MCP 工具: ${tool.name}`,
category: "mcp",
parameters: this.convertParameters(tool.inputSchema),
};
}
}

命名空间处理

MCP 工具使用 服务器名称 作为命名空间,避免工具名冲突:

1
2
3
4
5
6
7
// 限定名称格式
const qualifiedName = `${serverName}:${tool.name}`;

// 示例
"filesystem:read_file"
"github:create_issue"
"postgres:query"

为什么需要命名空间:

  • 不同服务器可能有同名工具
  • 例如:filesystem 和 s3 都有 read_file 工具
  • 使用命名空间可以区分:filesystem:read_file vs s3:read_file

参数转换

MCP 参数格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
type: "object",
properties: {
path: {
type: "string",
description: "文件路径"
},
encoding: {
type: "string",
description: "编码格式",
enum: ["utf8", "ascii"]
}
},
required: ["path"]
}

IToolPlugin 参数格式

1
2
3
4
5
6
7
8
9
{
path: {
type: "string",
description: "文件路径",
required: true,
enum: undefined,
default: undefined
}
}

convertParameters 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* 转换 MCP 工具参数为 ToolParameter 格式
*/
private convertParameters(schema: {
type: string;
properties?: any;
required?: string[];
}): { [name: string]: ToolParameter } {
const params: { [name: string]: ToolParameter } = {};

if (schema.properties) {
for (const [name, prop] of Object.entries(schema.properties)) {
const propAny = prop as any;
params[name] = {
type: propAny.type || "string",
description: propAny.description || "",
required: schema.required?.includes(name) || false,
enum: propAny.enum,
default: propAny.default,
};
}
}

return params;
}

execute 方法

实现逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/**
* 执行工具
*/
async execute(params: Record<string, any>, workspace: string): Promise<ToolResult> {
try {
// 1. 调用 MCP 工具
const result = await this.mcpManager.callTool(this.qualifiedName, params);

// 2. 检查是否出错
if (result.isError) {
return {
success: false,
output: "",
error: result.content.map((c) => c.text).join("\n") || "工具执行失败",
};
}

// 3. 提取文本内容
const output = result.content
.map((c) => {
if (c.type === "text") return c.text;
if (c.type === "resource") return `[Resource: ${c.data}]`;
if (c.type === "image") return `[Image: ${c.mimeType}]`;
return JSON.stringify(c);
})
.filter(Boolean)
.join("\n");

return {
success: true,
output,
};
} catch (error) {
return {
success: false,
output: "",
error: error instanceof Error ? error.message : String(error),
};
}
}

内容类型处理

MCP Content 类型 转换方式 输出
text 直接返回 文本内容
resource 格式化 [Resource: uri]
image 格式化 [Image: mimeType]

参数验证

validate 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
/**
* 验证参数
*/
validate(params: Record<string, any>): { valid: boolean; error?: string } {
// 1. 检查必需参数
for (const [name, param] of Object.entries(this.metadata.parameters)) {
if (param.required && !(name in params)) {
return {
valid: false,
error: `缺少必需参数: ${name}`,
};
}

// 2. 检查参数类型
if (name in params) {
const value = params[name];
const expectedType = param.type;

if (expectedType === "string" && typeof value !== "string") {
return {
valid: false,
error: `参数 ${name} 应为字符串类型`,
};
}

if (expectedType === "number" && typeof value !== "number") {
return {
valid: false,
error: `参数 ${name} 应为数字类型`,
};
}

if (expectedType === "boolean" && typeof value !== "boolean") {
return {
valid: false,
error: `参数 ${name} 应为布尔类型`,
};
}

if (expectedType === "array" && !Array.isArray(value)) {
return {
valid: false,
error: `参数 ${name} 应为数组类型`,
};
}

if (expectedType === "object" && typeof value !== "object") {
return {
valid: false,
error: `参数 ${name} 应为对象类型`,
};
}

// 3. 检查枚举值
if (param.enum && !param.enum.includes(value)) {
return {
valid: false,
error: `参数 ${name} 的值 "${value}" 不在允许的枚举值中: ${param.enum.join(", ")}`,
};
}
}
}

return { valid: true };
}

辅助方法

获取服务器名称

1
2
3
4
5
6
/**
* 获取服务器名称
*/
getServerName(): string {
return this.qualifiedName.split(":")[0];
}

获取原始工具名称

1
2
3
4
5
6
/**
* 获取原始工具名称
*/
getOriginalToolName(): string {
return this.qualifiedName.split(":")[1];
}

工具注册流程

注册到 ToolRegistry

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// src/mcp/index.ts

export async function registerMCPTools(
mcpManager: MCPManager,
toolRegistry: ToolRegistry
): Promise<void> {
// 获取所有 MCP 工具
const tools = mcpManager.getAllTools();

// 为每个工具创建适配器
for (const [qualifiedName, toolInfo] of tools.entries()) {
const adapter = new MCPToolAdapter(
toolInfo.server,
toolInfo.tool,
mcpManager
);

// 注册到工具注册表
toolRegistry.register(adapter);
}

console.log(`✓ 已注册 ${tools.size} 个 MCP 工具`);
}

完整流程

1
2
3
4
5
6
7
8
9
1. MCPManager 连接到所有服务器

2. 加载所有服务器的工具

3. 为每个工具创建适配器

4. 注册到 ToolRegistry

5. LLM 可以调用这些工具

使用示例

Agent 调用 MCP 工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 用户消息
用户: 读取 /tmp/config.json 文件

// LLM 决定调用工具
tool_calls: [{
id: "call_123",
function: {
name: "filesystem:read_file",
arguments: '{"path": "/tmp/config.json"}'
}
}]

// Agent 执行
const tool = toolRegistry.get("filesystem:read_file");
const result = await tool.execute(
JSON.parse('{"path": "/tmp/config.json"}'),
""
);

// 结果
{
success: true,
output: "{\n \"port\": 3000,\n \"host\": \"localhost\"\n}"
}

错误处理

1
2
3
4
5
6
7
8
9
10
11
// 工具不存在
toolRegistry.get("filesystem:nonexistent")
// → undefined

// 参数错误
tool.execute({ wrong: "param" }, "")
// → { success: false, error: "缺少必需参数: path" }

// MCP 服务器未连接
tool.execute({ path: "/tmp/file" }, "")
// → { success: false, error: "MCP 服务器 'filesystem' 未连接" }

小结

本节介绍了工具适配器的实现:

  • 适配器模式的作用
  • MCPToolAdapter 类设计
  • 命名空间处理
  • 参数转换
  • execute 方法实现
  • 参数验证
  • 工具注册流程

导航

上一篇: 13.2 MCP 服务器连接

下一篇: 13.4 常用 MCP 服务器