Адаптеры форматов инструментов
Модуль MCP (Model Context Protocol) должен преобразовывать инструменты, предоставляемые MCP-серверами, в форматы, требуемые различными LLM-провайдерами. Этим занимаются два компонента:
- ToolsLoader — преобразует MCPTool в форматы определений инструментов для OpenAI, Anthropic, Gemini и других провайдеров
- McpToolAdapter — оборачивает MCPTool как реализацию интерфейса ITool для движка TinyElf
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 — обработка схемы
function processJsonSchema(schema: any): any
Обеспечивает совместимость JSON Schema с различными LLM API:
- Если схема пустая или не является объектом — вернуть схему пустого объекта по умолчанию
- Если уже содержит
type: 'object'— извлечьproperties,required,description - Иначе — обернуть как тип объекта
McpToolAdapter
Путь к файлу: packages/desktop/app/main/services/agent-core/engine/tinyelf/tools/McpToolAdapter.ts
Обязанности
Оборачивает MCPTool как реализацию интерфейса ITool для движка TinyElf, позволяя 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: Generate 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: String result
Обработка тайм-аута: используется 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[]>
Перебирает список идентификаторов серверов, вызывает listServerTools() для каждого сервера, оборачивает каждый MCPTool в McpToolAdapter. Сбой на отдельном сервере не влияет на остальные.
DirectMcpToolAdapter
Назначение: используется для встроенных MCP-серверов (например, локальных MCP-сервисов в пресетах агентов), минуя McpService и напрямую удерживая ссылку на Client.
Отличия от McpToolAdapter
| Характеристика | McpToolAdapter | DirectMcpToolAdapter |
|---|---|---|
| Управление подключением | Делегируется McpService | Прямая ссылка на Client |
| Область применения | Настраиваемые пользователем MCP-серверы | Встроенные/пресетные MCP-серверы |
| Жизненный цикл | Следует за McpService | Следует за сессией |
| Формат имени инструмента | 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
Обязанности
Глобальный синглтон, отслеживающий PID дочерних stdio-процессов, созданных DirectMcpToolAdapter, и обеспечивающий их завершение даже при аварийном выходе.
Уровни защиты
| Уровень | Метод | Описание |
|---|---|---|
| Корректное завершение | closeConnection() | Плавное закрытие (тайм-аут 3 секунды) → принудительное завершение |
| Пакетное завершение | closeAll() | Закрыть все отслеживаемые подключения |
| Страховочная сеть | process.on('exit') | Синхронное принудительное завершение всех PID при выходе процесса |
Использование
// Добавить в отслеживание
McpProcessTracker.getInstance().track(connection);
// Завершить одно подключение
await McpProcessTracker.getInstance().closeConnection(connection);
// Завершить все
await McpProcessTracker.getInstance().closeAll();
Точки интеграции
Интеграция с CompletionService
CompletionService загружает инструменты MCP через setupMcpTools() перед каждым запросом к чату:
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);
}
Связанные файлы
- Управление пулом подключений — подробности о подключениях McpService
- Архитектура Worker — архитектура воркеров
- Обзор модуля — общая архитектура