본문으로 건너뛰기

툴 포맷 어댑터

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>

실행 흐름:

  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[]>

설정에 따라 로딩 전략을 결정합니다.

조건동작
enabledfalse이거나 설정되지 않음빈 배열 반환
modeauto이거나 설정되지 않음mcpService.listAllActiveServerTools() 호출
modemanualselectedServers를 순회하며 각각 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

다양한 LLM API와의 JSON Schema 호환성을 보장합니다.

  • 스키마가 비어 있거나 객체가 아니면 기본 빈 객체 스키마 반환
  • 이미 type: 'object'가 있으면 properties, required, description 추출
  • 그 외의 경우 객체 타입으로 래핑

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
콘텐츠 타입변환 방식
textc.text 직접 사용
image[Image: ${mimeType}] 플레이스홀더 출력
audio[Audio: ${mimeType}] 플레이스홀더 출력

여러 콘텐츠 항목은 \n으로 연결됩니다.

loadMcpTools — 일괄 로딩

async function loadMcpTools(
mcpService: McpService,
serverIds: string[]
): Promise<McpToolAdapter[]>

서버 ID 목록을 순회하며 각 서버에 대해 listServerTools()를 호출하고, 각 MCPTool을 McpToolAdapter로 래핑합니다. 개별 서버 실패는 다른 서버에 영향을 주지 않습니다.

DirectMcpToolAdapter

목적: Agent 프리셋의 로컬 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. PID 추적을 위해 McpProcessTracker에 등록
  4. 툴을 탐색하고 DirectMcpToolAdapter 인스턴스 생성
  5. 툴 목록과 연결 핸들(세션 종료 시 정리용) 반환

McpProcessTracker

파일 경로: packages/desktop/app/main/services/agent-core/engine/tinyelf/tools/McpProcessTracker.ts

역할

DirectMcpToolAdapter가 생성한 stdio 자식 프로세스 PID를 추적하는 글로벌 싱글턴으로, 비정상 종료 시에도 정리를 보장합니다.

안전 계층

계층메서드설명
정상 정리closeConnection()정상 종료(3초 타임아웃) → 강제 종료
일괄 정리closeAll()추적 중인 모든 연결 종료
안전망process.on('exit')프로세스 종료 시 모든 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);
}

관련 파일