Tool Format Adapters
The MCP module needs to convert tools provided by MCP servers to the formats required by different LLM providers. This process is handled by two components:
- ToolsLoader — Converts MCPTool to tool definition formats for OpenAI/Anthropic/Gemini and other providers
- McpToolAdapter — Wraps MCPTool as an ITool interface implementation for the TinyElf engine
ToolsLoader
File path: packages/desktop/app/main/services/capabilities/tools/mcp-users/ToolsLoader.ts
Responsibilities
ToolsLoader provides a complete MCP tool loading and format conversion pipeline:
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"]
Entry Function: 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>
Execution flow:
- Call
fetchMcpTools()to get raw MCPTool list - If no tools available, return
undefined - Log grouped by server
- Call corresponding conversion function based on
apiFormat - Return converted tool definitions and raw MCPTool list
fetchMcpTools — Get Tools
async function fetchMcpTools(
mcpService: McpService,
config?: { enabled?: boolean; mode?: 'auto' | 'manual'; selectedServers?: string[] }
): Promise<MCPTool[]>
Determines loading strategy based on configuration:
| Condition | Behavior |
|---|---|
enabled is false or not set | Return empty array |
mode is auto or not set | Call mcpService.listAllActiveServerTools() |
mode is manual | Iterate selectedServers, call mcpService.listServerTools() for each |
Output Format Comparison
OpenAI Format
interface OpenAITool {
type: 'function';
function: {
name: string; // MCPTool.id
description: string; // MCPTool.description
parameters: { // Processed JSON Schema
type: 'object';
properties: Record<string, any>;
required: string[];
};
};
}
Anthropic Format
interface AnthropicTool {
name: string; // MCPTool.id
description: string; // MCPTool.description
input_schema: { // Processed JSON Schema
type: 'object';
properties: Record<string, any>;
required: string[];
};
}
Gemini Format
type GeminiTools = Array<{
functionDeclarations: Array<{
name: string; // MCPTool.id
description: string; // MCPTool.description
parameters: { // Processed JSON Schema
type: 'object';
properties: Record<string, any>;
required: string[];
};
}>;
}>;
Gemini Format Feature: All tools are wrapped in a functionDeclarations array, with the array wrapped in another outer array.
processJsonSchema — Schema Processing
function processJsonSchema(schema: any): any
Ensures JSON Schema compatibility with various LLM APIs:
- If schema is empty or not an object, return default empty object schema
- If already has
type: 'object', extractproperties,required,description - Otherwise wrap as object type
McpToolAdapter
File path: packages/desktop/app/main/services/agent-core/engine/tinyelf/tools/McpToolAdapter.ts
Responsibilities
Wrap MCPTool as an ITool interface implementation for the TinyElf engine, allowing TinyElf to directly call MCP tools.
ITool Interface Mapping
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 Method
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
Timeout handling: Use Promise.race() to implement 120-second timeout, consistent with ShellTool timeout.
flattenContent — Content Flattening
Convert MCPToolResponseContent[] to a single string:
function flattenContent(content: MCPToolResponseContent[]): string
| Content Type | Conversion |
|---|---|
text | Use c.text directly |
image | Output [Image: ${mimeType}] placeholder |
audio | Output [Audio: ${mimeType}] placeholder |
Multiple content items are joined with \n.
loadMcpTools — Batch Loading
async function loadMcpTools(
mcpService: McpService,
serverIds: string[]
): Promise<McpToolAdapter[]>
Iterate through server ID list, call listServerTools() for each server, wrap each MCPTool as McpToolAdapter. Failure on individual server doesn't affect others.
DirectMcpToolAdapter
Purpose: Used for built-in MCP servers (like local MCP services in Agent presets), bypassing McpService by directly holding a Client reference.
Differences from McpToolAdapter
| Feature | McpToolAdapter | DirectMcpToolAdapter |
|---|---|---|
| Connection Management | Delegated to McpService | Direct Client reference |
| Use Case | User-configured MCP servers | Built-in/preset MCP servers |
| Lifecycle | Follows McpService | Follows session (session-level) |
| Tool Name Format | mcp__serverName__toolName | serverName__toolName |
| Process Tracking | Not needed | Through McpProcessTracker |
loadDirectMcpTools — Direct Connection Loading
async function loadDirectMcpTools(
servers: ResolvedMcpServer[]
): Promise<{
tools: DirectMcpToolAdapter[];
connections: DirectMcpConnection[];
}>
Execution flow:
- Create
StdioClientTransportfor each server - Create MCP
Clientand connect - Register with
McpProcessTrackerfor PID tracking - Discover tools and create
DirectMcpToolAdapterinstances - Return tools list and connection handles (for cleanup at session end)
McpProcessTracker
File path: packages/desktop/app/main/services/agent-core/engine/tinyelf/tools/McpProcessTracker.ts
Responsibilities
Global singleton tracking stdio child process PIDs created by DirectMcpToolAdapter, ensuring cleanup even on abnormal exit.
Safety Layers
| Layer | Method | Description |
|---|---|---|
| Graceful cleanup | closeConnection() | Graceful close (3 second timeout) → force kill |
| Batch cleanup | closeAll() | Close all tracked connections |
| Safety net | process.on('exit') | Synchronously force kill all PIDs on process exit |
Usage
// Track
McpProcessTracker.getInstance().track(connection);
// Cleanup single
await McpProcessTracker.getInstance().closeConnection(connection);
// Cleanup all
await McpProcessTracker.getInstance().closeAll();
Integration Points
CompletionService Integration
CompletionService loads MCP tools via setupMcpTools() before each chat request:
const mcpResult = await setupMcpTools(
this.mcpService,
{ enabled: true, mode: 'auto' },
'openai' // Choose based on current provider
);
if (mcpResult) {
requestPayload.tools = mcpResult.tools;
}
TinyElf Engine Integration
TinyElf engine loads tools via loadMcpTools() and loadDirectMcpTools():
// User-configured MCP servers
const mcpTools = await loadMcpTools(mcpService, serverIds);
// Built-in MCP servers
const { tools: directTools, connections } = await loadDirectMcpTools(builtinServers);
// Register with ToolRegistry
for (const tool of [...mcpTools, ...directTools]) {
toolRegistry.register(tool);
}
Related Files
- Connection Pool Management — McpService connection details
- Worker Architecture — Worker architecture
- Module Overview — Overall architecture