#!/usr/bin/env node /** * MCP Client - Core client for interacting with MCP servers */ import { Client } from '@modelcontextprotocol/sdk/client/index.js'; import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'; import { readFile } from 'fs/promises'; import { resolve } from 'path'; interface MCPConfig { mcpServers: { [key: string]: { command: string; args: string[]; env?: Record; }; }; } interface ToolInfo { serverName: string; name: string; description: string; inputSchema: any; outputSchema?: any; } interface PromptInfo { serverName: string; name: string; description: string; arguments?: any[]; } interface ResourceInfo { serverName: string; uri: string; name: string; description?: string; mimeType?: string; } export class MCPClientManager { private config: MCPConfig | null = null; private clients: Map = new Map(); async loadConfig(configPath: string = '.claude/.mcp.json'): Promise { const fullPath = resolve(process.cwd(), configPath); const content = await readFile(fullPath, 'utf-8'); const config = JSON.parse(content) as MCPConfig; this.config = config; return config; } async connectToServer(serverName: string): Promise { if (!this.config?.mcpServers[serverName]) { throw new Error(`Server ${serverName} not found in config`); } const serverConfig = this.config.mcpServers[serverName]; const transport = new StdioClientTransport({ command: serverConfig.command, args: serverConfig.args, env: serverConfig.env }); const client = new Client({ name: `mcp-manager-${serverName}`, version: '1.0.0' }, { capabilities: {} }); await client.connect(transport); this.clients.set(serverName, client); return client; } async connectAll(): Promise { if (!this.config) { throw new Error('Config not loaded. Call loadConfig() first.'); } const connections = Object.keys(this.config.mcpServers).map(name => this.connectToServer(name) ); await Promise.all(connections); } async getAllTools(): Promise { const allTools: ToolInfo[] = []; for (const [serverName, client] of this.clients.entries()) { const response = await client.listTools({}, { timeout: 300000 }); for (const tool of response.tools) { allTools.push({ serverName, name: tool.name, description: tool.description || '', inputSchema: tool.inputSchema, outputSchema: (tool as any).outputSchema }); } } return allTools; } async getAllPrompts(): Promise { const allPrompts: PromptInfo[] = []; for (const [serverName, client] of this.clients.entries()) { const response = await client.listPrompts({}, { timeout: 300000 }); for (const prompt of response.prompts) { allPrompts.push({ serverName, name: prompt.name, description: prompt.description || '', arguments: prompt.arguments }); } } return allPrompts; } async getAllResources(): Promise { const allResources: ResourceInfo[] = []; for (const [serverName, client] of this.clients.entries()) { const response = await client.listResources({}, { timeout: 300000 }); for (const resource of response.resources) { allResources.push({ serverName, uri: resource.uri, name: resource.name, description: resource.description, mimeType: resource.mimeType }); } } return allResources; } async callTool(serverName: string, toolName: string, args: any): Promise { const client = this.clients.get(serverName); if (!client) throw new Error(`Not connected to server: ${serverName}`); return await client.callTool({ name: toolName, arguments: args }, { timeout: 300000 }); } async getPrompt(serverName: string, promptName: string, args?: any): Promise { const client = this.clients.get(serverName); if (!client) throw new Error(`Not connected to server: ${serverName}`); return await client.getPrompt({ name: promptName, arguments: args }, { timeout: 300000 }); } async readResource(serverName: string, uri: string): Promise { const client = this.clients.get(serverName); if (!client) throw new Error(`Not connected to server: ${serverName}`); return await client.readResource({ uri }, { timeout: 300000 }); } async cleanup(): Promise { for (const client of this.clients.values()) { await client.close(); } this.clients.clear(); } }