工具格式适配
MCP 模块需要将 MCP 服务器提供的工具转换为不同 LLM 提供商所需的格式。这个过程由两个组件负责:
- ToolsLoader — 将 MCPTool 转换为 OpenAI/Anthropic/Gemini 等提供商的工具定义格式
- McpToolAdapter — 将 MCPTool 封装为 TinyElf 引擎的 ITool 接口实现
ToolsLoader
文件路径:packages/desktop/app/main/services/capabilities/tools/mcp-users/ToolsLoader.ts
职责
ToolsLoader 提供了完整的 MCP 工具加载和格式转换流程:
graph LR
fetchMcpTools --> MCPTool_Array["MCPTool[]"]
MCPTool_Array --> convertOpenAI["convertMcpToolsToOpenAI"]
MCPTool_Array --> convertAnthropic["convertMcpToolsToAnthropic"]
MCPTool_Array --> convertGemini["convertMcpToolsToGemini"]
convertOpenAI --> OpenAI_Format["OpenAITool[]"]
convertAnthropic --> Anthropic_Format["AnthropicTool[]"]
convertGemini --> Gemini_Format["GeminiTools"]
入口函数:setupMcpTools
async function setupMcpTools(
mcpService: McpService,
config?: {
enabled?: boolean;
mode?: 'auto' | 'manual';
selectedServers?: string[];
},
apiFormat?: 'openai' | 'anthropic' | 'google'
): Promise<{ tools: OpenAITool[] | AnthropicTool[] | GeminiTools; mcpTools: MCPTool[] } | undefined>
执行流程:
- 调用
fetchMcpTools()获取原始 MCPTool 列表 - 如果没有可用工具,返回
undefined - 按服务器分组打印日志
- 根据
apiFormat调用对应的转换函数 - 返回转换后的工具定义和原始 MCPTool 列表
fetchMcpTools — 获取工具
async function fetchMcpTools(
mcpService: McpService,
config?: { enabled?: boolean; mode?: 'auto' | 'manual'; selectedServers?: string[] }
): Promise<MCPTool[]>
根据配置决定加载策略:
| 条件 | 行为 |
|---|---|
enabled 为 false 或未设置 | 返回空数组 |
mode 为 auto 或未设置 | 调用 mcpService.listAllActiveServerTools() |
mode 为 manual | 遍历 selectedServers,逐个调用 mcpService.listServerTools() |
输出格式对比
OpenAI 格式
interface OpenAITool {
type: 'function';
function: {
name: string; // MCPTool.id
description: string; // MCPTool.description
parameters: { // 处理后的 JSON Schema
type: 'object';
properties: Record<string, any>;
required: string[];
};
};
}
Anthropic 格式
interface AnthropicTool {
name: string; // MCPTool.id
description: string; // MCPTool.description
input_schema: { // 处理后的 JSON Schema
type: 'object';
properties: Record<string, any>;
required: string[];
};
}
Gemini 格式
type GeminiTools = Array<{
functionDeclarations: Array<{
name: string; // MCPTool.id
description: string; // MCPTool.description
parameters: { // 处理后的 JSON Schema
type: 'object';
properties: Record<string, any>;
required: string[];
};
}>;
}>;
Gemini 格式特点:所有工具包装在一个 functionDeclarations 数组中,外层再包一层数组。
processJsonSchema — Schema 处理
function processJsonSchema(schema: any): any
确保 JSON Schema 与各 LLM API 兼容:
- 如果 schema 为空或不是对象,返回默认的空 object schema
- 如果已有
type: 'object',提取properties、required、description - 否则包装为 object 类型
McpToolAdapter
文件路径:packages/desktop/app/main/services/agent-core/engine/tinyelf/tools/McpToolAdapter.ts
职责
将 MCPTool 封装为 TinyElf 引擎的 ITool 接口实现,使 TinyElf 可以直接调用 MCP 工具。
ITool 接口映射
class McpToolAdapter implements ITool {
readonly name: string; // ← MCPTool.id
readonly description: string; // ← MCPTool.description
readonly parameters: JsonSchema; // ← MCPTool.inputSchema
constructor(
private mcpTool: MCPTool,
private mcpService: McpService
);
async execute(params: Record<string, unknown>): Promise<string>;
}
execute 方法
sequenceDiagram
participant TinyElf
participant Adapter as McpToolAdapter
participant Service as McpService
participant Server as MCP Server
TinyElf->>Adapter: execute(params)
Adapter->>Adapter: 生成 callId
Adapter->>Service: callTool(serverId, toolName, args, callId)
Note over Adapter,Service: Promise.race([callTool, timeout(120s)])
Service->>Server: client.callTool()
Server-->>Service: MCPCallToolResponse
Service-->>Adapter: { isError, content[] }
Adapter->>Adapter: flattenContent(content)
Adapter-->>TinyElf: 字符串结果
超时处理:使用 Promise.race() 实现 120 秒超时,与 ShellTool 超时保持一致。
flattenContent — 内容扁平化
将 MCPToolResponseContent[] 转换为单个字符串:
function flattenContent(content: MCPToolResponseContent[]): string
| 内容类型 | 转换方式 |
|---|---|
text | 直接使用 c.text |
image | 输出 [Image: ${mimeType}] 占位符 |
audio | 输出 [Audio: ${mimeType}] 占位符 |
多个内容项用 \n 连接。
loadMcpTools — 批量加载
async function loadMcpTools(
mcpService: McpService,
serverIds: string[]
): Promise<McpToolAdapter[]>
遍历服务器 ID 列表,对每个服务器调用 listServerTools(),将每个 MCPTool 封装为 McpToolAdapter。单个服务器失败不影响其他服务器。
DirectMcpToolAdapter
用途:用于内置 MCP 服务器(如 Agent 预设的本地 MCP 服务),绕过 McpService 直接持有 Client 引用。
与 McpToolAdapter 的区别
| 特性 | McpToolAdapter | DirectMcpToolAdapter |
|---|---|---|
| 连接管理 | 委托给 McpService | 直接持有 Client |
| 适用场景 | 用户配置的 MCP 服务器 | 内置/预设的 MCP 服务器 |
| 生命周期 | 跟随 McpService | 跟随会话(session-level) |
| 工具名格式 | mcp__serverName__toolName | serverName__toolName |
| 进程追踪 | 不需要 | 通过 McpProcessTracker |
loadDirectMcpTools — 直连加载
async function loadDirectMcpTools(
servers: ResolvedMcpServer[]
): Promise<{
tools: DirectMcpToolAdapter[];
connections: DirectMcpConnection[];
}>
执行流程:
- 对每个服务器创建
StdioClientTransport - 创建 MCP
Client并连接 - 注册到
McpProcessTracker进行 PID 追踪 - 发现工具并创建
DirectMcpToolAdapter实例 - 返回工具列表和连接句柄(用于会话结束时清理)
McpProcessTracker
文件路径:packages/desktop/app/main/services/agent-core/engine/tinyelf/tools/McpProcessTracker.ts
职责
全局单例,追踪 DirectMcpToolAdapter 创建的 stdio 子进程 PID,确保异常退出时也能清理。
安全层
| 层级 | 方法 | 说明 |
|---|---|---|
| 正常清理 | closeConnection() | 优雅关闭(3 秒超时)→ 强制 kill |
| 批量清理 | closeAll() | 关闭所有追踪的连接 |
| 安全网 | process.on('exit') | 进程退出时同步强制 kill 所有 PID |
使用方式
// 追踪
McpProcessTracker.getInstance().track(connection);
// 清理单个
await McpProcessTracker.getInstance().closeConnection(connection);
// 清理全部
await McpProcessTracker.getInstance().closeAll();
集成点
CompletionService 集成
CompletionService 通过 setupMcpTools() 在每次聊天请求前加载 MCP 工具:
const mcpResult = await setupMcpTools(
this.mcpService,
{ enabled: true, mode: 'auto' },
'openai' // 根据当前提供商选择
);
if (mcpResult) {
requestPayload.tools = mcpResult.tools;
}
TinyElf 引擎集成
TinyElf 引擎通过 loadMcpTools() 和 loadDirectMcpTools() 加载工具:
// 用户配置的 MCP 服务器
const mcpTools = await loadMcpTools(mcpService, serverIds);
// 内置 MCP 服务器
const { tools: directTools, connections } = await loadDirectMcpTools(builtinServers);
// 注册到 ToolRegistry
for (const tool of [...mcpTools, ...directTools]) {
toolRegistry.register(tool);
}