Перейти к основному содержимому

Адаптеры форматов инструментов

Модуль 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>

Порядок выполнения:

  1. Вызвать fetchMcpTools() для получения необработанного списка MCPTool
  2. Если инструменты недоступны, вернуть undefined
  3. Записать лог, сгруппированный по серверам
  4. Вызвать соответствующую функцию преобразования на основе apiFormat
  5. Вернуть преобразованные определения инструментов и необработанный список 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

ХарактеристикаMcpToolAdapterDirectMcpToolAdapter
Управление подключениемДелегируется McpServiceПрямая ссылка на Client
Область примененияНастраиваемые пользователем MCP-серверыВстроенные/пресетные MCP-серверы
Жизненный циклСледует за McpServiceСледует за сессией
Формат имени инструментаmcp__serverName__toolNameserverName__toolName
Отслеживание процессовНе требуетсяЧерез McpProcessTracker

loadDirectMcpTools — загрузка прямого подключения

async function loadDirectMcpTools(
servers: ResolvedMcpServer[]
): Promise<{
tools: DirectMcpToolAdapter[];
connections: DirectMcpConnection[];
}>

Порядок выполнения:

  1. Создать StdioClientTransport для каждого сервера
  2. Создать MCP Client и подключиться
  3. Зарегистрировать в McpProcessTracker для отслеживания PID
  4. Обнаружить инструменты и создать экземпляры DirectMcpToolAdapter
  5. Вернуть список инструментов и дескрипторы подключений (для очистки по завершении сессии)

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);
}

Связанные файлы