// have to import with extension since the exports map is ./* -> ./dist/cjs/* import { Client } from '@modelcontextprotocol/sdk/client/index.js'; import { EventSource } from 'eventsource'; import { ILogger } from '@opensumi/ide-core-common'; import pkg from '../../package.json'; import { IMCPServer } from '../common/mcp-server-manager'; import { SSEClientTransportOptions } from '../common/types'; import { toClaudeToolName } from '../common/utils'; global.EventSource = EventSource as any; export class SSEMCPServer implements IMCPServer { private name: string; public url: string; private transportOptions?: SSEClientTransportOptions; private client: Client; private started: boolean = false; private toolNameMap: Map = new Map(); // Map sanitized tool names to original names constructor( name: string, url: string, private readonly logger?: ILogger, private readonly options?: SSEClientTransportOptions, ) { this.name = name; this.url = url; this.transportOptions = options; } isStarted(): boolean { return this.started; } getServerName(): string { return this.name; } getClient(): Client | null { return this.client; } async start(): Promise { if (this.started) { return; } this.logger?.log(`Starting server "${this.name}" with url: ${this.url}`); const SSEClientTransport = (await import('@modelcontextprotocol/sdk/client/sse.js')).SSEClientTransport; const transport = new SSEClientTransport(new URL(this.url), this.transportOptions); transport.onerror = (error) => { this.logger?.error('Transport Error:', error); }; this.client = new Client( { name: 'sumi-ide-sse-mcp-client', version: pkg.version, }, { capabilities: {}, }, ); this.client.onerror = (error) => { this.logger?.error('Error in MCP client:', error); }; try { await this.client.connect(transport); this.started = true; } catch (error) { this.logger?.error(`Error in startServer for ${this.name}:`, error); throw error; } } async callTool(toolName: string, toolCallId: string, arg_string: string) { let args; try { args = JSON.parse(arg_string); } catch (error) { this.logger?.error( `Failed to parse arguments for calling tool "${toolName}" in MCP server "${this.name}" with url "${this.url}". Invalid JSON: ${arg_string}`, error, ); } // Convert sanitized tool name back to original name if it exists in the map const originalToolName = this.toolNameMap.get(toolName) || toolName; const params = { name: originalToolName, arguments: args, toolCallId, }; return this.client.callTool(params); } async getTools() { const originalTools = await this.client.listTools(); this.toolNameMap.clear(); const toolsArray = originalTools.tools || []; const sanitizedToolsArray = toolsArray.map((tool) => { const originalName = tool.name; const sanitizedName = toClaudeToolName(originalName); if (sanitizedName !== originalName) { this.toolNameMap.set(sanitizedName, originalName); return { ...tool, name: sanitizedName }; } return tool; }); const sanitizedTools = { ...originalTools, tools: sanitizedToolsArray, }; this.logger?.log(`Got tools from MCP server "${this.name}" with url "${this.url}":`, sanitizedTools); this.logger?.log('Tool name mapping: ', Object.fromEntries(this.toolNameMap)); return sanitizedTools; } update(url: string): void { this.url = url; } async stop(): Promise { if (!this.started || !this.client) { return; } this.logger?.log(`Stopping MCP server "${this.name}"`); try { await this.client.close(); } catch (error) { this.logger?.error(`Failed to stop MCP server "${this.name}":`, error); } this.logger?.log(`MCP server "${this.name}" stopped`); this.started = false; } }