# 第七阶段 · 模块七 · 第三节:MCP 工具集成
MCP 工具如何注册为 Claude Code 工具?工具调用的参数和结果如何转换?MCP 工具的权限模型是什么?
Claude Code 全局架构
┌─────────────────────────────────────────────────────────────────────┐
│ MCP 系统 │
│ │
│ MCP 工具集成 ← 本节 │
│ ├── MCPTool ──> 工具包装器 │
│ ├── 工具注册 ──> 注册为 Claude Code 工具 │
│ └── 权限模型 ──> MCP 工具权限 │
└─────────────────────────────────────────────────────────────────────┘
源码位置:`src/tools/MCPTool/MCPTool.ts`
import { buildTool, type ToolDef } from '../../Tool.js';
export const MCPTool = buildTool({
isMcp: true,
name: 'mcp', // 会被 MCPConnectionManager 覆盖
maxResultSizeChars: 100_000,
async call() {
return { data: '' }; // 会被实际处理器覆盖
},
async checkPermissions(): Promise<PermissionResult> {
return {
behavior: 'passthrough',
message: 'MCPTool requires permission.',
};
},
} satisfies ToolDef<InputSchema, Output>);
// MCPTool 是一个通用包装器
// 具体的 MCP 工具由 MCPConnectionManager 动态创建
// 工具名格式:mcp__{server}__{tool}
// 例如:mcp__github__create_issue
问 1:为什么需要 MCPTool 包装器?
// 问题:MCP 工具有各种不同的输入输出
// 解决:统一的包装器处理
// MCPTool 的职责:
// 1. 统一的工具接口
// 2. 权限检查
// 3. 结果渲染
// 4. 错误处理
// MCPConnectionManager 负责:
// 1. 管理 MCP 连接
// 2. 注册 MCP 工具
class MCPConnectionManager {
private tools: Map<string, MCPTool> = new Map();
async connect(server: MCPServerConfig): Promise<void> {
// 1. 创建 MCP 客户端
const client = await this.createClient(server);
// 2. 发现工具
const { tools } = await client.request(
{ method: 'tools/list' },
ListToolsResultSchema
);
// 3. 注册每个工具
for (const tool of tools) {
await this.registerTool(server.name, tool);
}
}
}
async registerTool(
serverName: string,
mcpTool: { name: string; description: string; inputSchema: object }
): Promise<void> {
// 创建工具处理器
const toolHandler = async (input: unknown, context: ToolContext) => {
// 调用 MCP 服务器上的工具
const result = await this.callTool(serverName, mcpTool.name, input);
return result;
};
// 工具名:mcp__{server}__{tool}
const toolName = `mcp__${serverName}__${mcpTool.name}`;
// 注册
this.tools.set(toolName, {
name: toolName,
description: mcpTool.description,
inputSchema: mcpTool.inputSchema,
call: toolHandler,
});
}
问 1:工具名为什么用双下划线?
// 格式:mcp__{server}__{tool}
// 双下划线避免与普通工具名冲突
// 普通工具:Read, Write, Grep
// MCP 工具:mcp__github__create_issue, mcp__filesystem__read
┌─────────────────────────────────────────────────────────────────────┐
│ MCP 工具调用流程 │
│ │
│ 1. Claude Code 调用 mcp__github__create_issue │
│ │
│ 2. MCPTool.call(input) │
│ │
│ 3. MCPConnectionManager.callTool( │
│ serverName: 'github', │
│ toolName: 'create_issue', │
│ arguments: input │
│ ) │
│ │
│ 4. MCP Client 发送 tools/call 请求 │
│ │
│ 5. 返回结果 │
└─────────────────────────────────────────────────────────────────────┘
async callTool(
serverName: string,
toolName: string,
arguments: Record<string, unknown>
): Promise<string> {
// 1. 获取连接
const connection = await this.getConnection(serverName);
// 2. 调用工具
const result = await connection.client.request(
{
method: 'tools/call',
params: {
name: toolName,
arguments: arguments,
},
},
CallToolResultSchema
);
// 3. 格式化结果
if (isError(result)) {
throw new MCPToolError(result.content);
}
return formatResult(result);
}
问 1:MCP 工具的错误如何处理?
// MCP 工具可能返回错误内容
if (result.isError) {
throw new MCPToolError(result.content);
}
// MCPToolError 会被捕获并显示给用户
class MCPToolError extends Error {
constructor(public readonly content: string) {
super(content);
}
}
// MCP 工具需要单独授权
// 默认行为:需要用户授权
async checkPermissions(
input: unknown,
context: ToolUseContext
): Promise<PermissionResult> {
// 检查是否已授权该工具
const authorized = await this.isAuthorized(
context.getAppState().authorizedMcpTools,
this.name
);
if (authorized) {
return { behavior: 'allow' };
}
// 需要授权
return {
behavior: 'prompt',
message: `Authorize MCP tool: ${this.name}`,
};
}
// 授权存储在 AppState
interface AppState {
authorizedMcpTools: Set<string>; // 已授权的工具
}
// 添加授权
function authorizeTool(toolName: string): void {
const state = getAppState();
state.authorizedMcpTools.add(toolName);
}
问 1:MCP 工具和内置工具的权限区别?
// 内置工具:基于权限规则
// MCP 工具:基于授权列表
// 内置工具权限
if (permissionRules.allows(toolName)) {
return { behavior: 'allow' };
}
// MCP 工具权限
if (state.authorizedMcpTools.has(toolName)) {
return { behavior: 'allow' };
}
源码位置:`src/tools/MCPTool/UI.js`
// MCP 工具结果渲染
function renderToolResultMessage(output: string): RenderResult {
return {
title: 'MCP Tool Result',
content: output,
isError: output.includes('Error:'),
};
}
// 工具执行中的进度
function renderToolUseProgressMessage(progress: MCPProgress): RenderResult {
return {
title: `MCP: ${progress.tool}`,
content: progress.message,
partial: true,
};
}
答案:
// 1. 启用 verbose 模式
claude --verbose
// 2. 查看工具调用日志
console.log('Calling MCP tool:', toolName, arguments);
// 3. 测试 MCP 工具
claude mcp call github.create_issue --args '{"title": "Test"}'
答案:
// 有,maxResultSizeChars = 100,000
// 超出会被截断
MCPTool = buildTool({
maxResultSizeChars: 100_000,
// ...
});
// 如果截断,显示警告
if (output.length > maxResultSizeChars) {
return output.slice(0, maxResultSizeChars) + '\n[Truncated]';
}
答案:
// 1. 检查连接状态
claude mcp list
// 2. 重新连接
claude mcp remove <server>
claude mcp add <server>
// 3. 查看服务器日志
// MCP 服务器可能需要重启
| 文件 | 核心内容 |
| `src/tools/MCPTool/MCPTool.ts` | MCP 工具包装器 |
| `src/services/mcp/MCPConnectionManager.tsx` | 连接管理器 |
下一节我们将深入 MCP 服务器开发:
- 创建 MCP 服务器
- 实现工具端点
- 注册和发布服务器
*- 第一轮:□ 事实准确性*
*- 第二轮:□ 深度与洞见*
*- 第三轮:□ 可读性与价值*