MCP Base

  • client
// Generic MCP Client using the official MCP SDK import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js"; /** * Generic MCPClient * This client provides a base interface to interact with any MCP server */ class MCPClient { private client: Client; private connected: boolean = false; private serverUrl: string; /** * Creates a new instance of the MCP Client * @param options Configuration options for the client */ constructor( options: { serverUrl?: string; serverCommand?: string; serverArgs?: string[]; transportType?: "stdio" | "sse"; } = {} ) { // Default options const defaults = { serverUrl: process.env.CLIENT_SERVER_URL || "http://localhost:3000", transportType: "sse" as const, serverCommand: "node", serverArgs: ["server/server.js"], }; const config = { ...defaults, ...options }; this.serverUrl = config.serverUrl; // Create the MCP client this.client = new Client( { name: "MCP Client", version: "1.0.0", }, { capabilities: { resources: {}, tools: {}, prompts: {}, }, } ); // Set up connection based on transport type if (config.transportType === "stdio") { this.setupStdioTransport(config.serverCommand, config.serverArgs); } else { this.setupSSETransport(config.serverUrl); } } /** * Set up a stdio transport for the client */ private setupStdioTransport(command: string, args: string[]) { const transport = new StdioClientTransport({ command, args, }); // Connect to the transport when needed this.connectTransport = async () => { if (!this.connected) { await this.client.connect(transport); this.connected = true; } }; } /** * Set up an SSE transport for the client */ private setupSSETransport(serverUrl: string) { const sseUrl = new URL(`${serverUrl}/sse`); const transport = new SSEClientTransport(sseUrl); // Connect to the transport when needed this.connectTransport = async () => { if (!this.connected) { await this.client.connect(transport); this.connected = true; } }; } /** * Ensure the client is connected to the transport * This will be called before any operation that requires connection */ private connectTransport!: () => Promise<void>; /** * Disconnect the client from the server */ async disconnect() { if (this.connected) { await this.client.close(); this.connected = false; } } /** * Call a tool on the server * @param name Tool name * @param args Tool arguments * @returns Tool result */ async callTool(name: string, args: Record<string, any> = {}): Promise<any> { await this.connectTransport(); try { const result = await this.client.callTool({ name, arguments: args, }); // Parse the result content if it's JSON try { return JSON.parse((result as any).content[0].text); } catch (e) { // If parsing fails, return the raw text return (result as any).content[0].text; } } catch (error) { console.error(`Error calling tool ${name}:`, error); throw error; } } /** * Read a resource from the server * @param uri Resource URI * @returns Resource content */ async readResource(uri: string): Promise<any> { await this.connectTransport(); try { const resource: any = await this.client.readResource({ uri, }); // Parse the result content if it's JSON try { return JSON.parse(resource.contents[0].text); } catch (e) { // If parsing fails, return the raw text return resource.contents[0].text; } } catch (error) { console.error(`Error reading resource ${uri}:`, error); throw error; } } /** * Get a prompt from the server * @param name Prompt name * @param args Prompt arguments * @returns Prompt content */ async getPrompt(name: string, args: Record<string, any> = {}): Promise<any> { await this.connectTransport(); try { const prompt = await this.client.getPrompt({ name, arguments: args, }); return prompt; } catch (error) { console.error(`Error getting prompt ${name}:`, error); throw error; } } /** * List all available prompts from the server * @returns List of prompts */ async listPrompts(): Promise<any[]> { await this.connectTransport(); try { const prompts: any = await this.client.listPrompts(); return prompts; } catch (error) { console.error("Error listing prompts:", error); throw error; } } /** * Check if the server is alive * @returns True if the server is responsive */ async ping(): Promise<boolean> { try { // Use the health endpoint instead of the MCP protocol for ping const url = new URL("/health", this.serverUrl); const response = await fetch(url.toString()); if (!response.ok) { throw new Error(`Server responded with status: ${response.status}`); } const data = await response.json(); return data.status === "ok"; } catch (error) { console.error("Server ping failed:", error); return false; } } } export default MCPClient; // Helper types type CallToolResult = { content: { text: string; }[]; };