Skip to main content

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:

  1. Call fetchMcpTools() to get raw MCPTool list
  2. If no tools available, return undefined
  3. Log grouped by server
  4. Call corresponding conversion function based on apiFormat
  5. 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:

ConditionBehavior
enabled is false or not setReturn empty array
mode is auto or not setCall mcpService.listAllActiveServerTools()
mode is manualIterate 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', extract properties, 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 TypeConversion
textUse c.text directly
imageOutput [Image: ${mimeType}] placeholder
audioOutput [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

FeatureMcpToolAdapterDirectMcpToolAdapter
Connection ManagementDelegated to McpServiceDirect Client reference
Use CaseUser-configured MCP serversBuilt-in/preset MCP servers
LifecycleFollows McpServiceFollows session (session-level)
Tool Name Formatmcp__serverName__toolNameserverName__toolName
Process TrackingNot neededThrough McpProcessTracker

loadDirectMcpTools — Direct Connection Loading

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

Execution flow:

  1. Create StdioClientTransport for each server
  2. Create MCP Client and connect
  3. Register with McpProcessTracker for PID tracking
  4. Discover tools and create DirectMcpToolAdapter instances
  5. 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

LayerMethodDescription
Graceful cleanupcloseConnection()Graceful close (3 second timeout) → force kill
Batch cleanupcloseAll()Close all tracked connections
Safety netprocess.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);
}