mcp-client.ts•3.67 kB
import { ChildProcess } from 'child_process';
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
import { Tool } from '@modelcontextprotocol/sdk/resources/tools/index.js';
export class MCPClient {
  private mcp: Client;
  private transport: StdioClientTransport | null = null;
  private tools: Tool[] = [];
  private serverProcess: ChildProcess | null = null;
  constructor() {
    this.mcp = new Client({ name: 'mcp-test-client', version: '1.0.0' });
  }
  /**
   * Connects to an MCP server
   * @param serverScriptPath Path to the server script
   * @returns Promise that resolves when connected
   */
  async connectToServer(serverScriptPath: string): Promise<Tool[]> {
    try {
      console.log(`MCP Client: Connecting to server at ${serverScriptPath}`);
      const isJs = serverScriptPath.endsWith('.js');
      const isPy = serverScriptPath.endsWith('.py');
      if (!isJs && !isPy) {
        throw new Error('Server script must be a .js or .py file');
      }
      const command = isPy
        ? process.platform === 'win32'
          ? 'python'
          : 'python3'
        : process.execPath;
      console.log(`MCP Client: Using command: ${command} ${serverScriptPath}`);
      // Create transport that communicates with the server process
      console.log('MCP Client: Creating StdioClientTransport');
      this.transport = new StdioClientTransport({
        command,
        args: [serverScriptPath],
      });
      // Connect the client to the transport
      console.log('MCP Client: Connecting client to transport');
      this.mcp.connect(this.transport);
      console.log('MCP Client: Connected to transport');
      // List available tools
      console.log('MCP Client: Requesting list of available tools');
      const toolsResult = await this.mcp.listTools();
      console.log(
        `MCP Client: Received ${toolsResult.tools.length} tools from server`,
      );
      this.tools = toolsResult.tools;
      // Log the names of available tools
      console.log(
        'MCP Client: Available tools:',
        this.tools.map((t) => t.name).join(', '),
      );
      return this.tools;
    } catch (e) {
      console.error('MCP Client ERROR: Failed to connect to MCP server: ', e);
      throw e;
    }
  }
  /**
   * Disconnects from the server and cleans up resources
   */
  async disconnect(): Promise<void> {
    try {
      // The StdioClientTransport doesn't have a disconnect method,
      // but when the process exits, it will clean up resources
      this.tools = [];
      this.transport = null;
    } catch (error) {
      console.error('Error during disconnect:', error);
    }
  }
  /**
   * Gets the list of available tools
   */
  getTools(): Tool[] {
    return this.tools;
  }
  /**
   * Calls a tool on the server
   * @param toolName Name of the tool to call
   * @param parameters Parameters to pass to the tool
   * @returns Result from the tool
   */
  async callTool(
    toolName: string,
    parameters: Record<string, any>,
  ): Promise<any> {
    if (!this.transport) {
      throw new Error('Not connected to server');
    }
    console.log(
      `MCP Client: Calling tool '${toolName}' with parameters:`,
      JSON.stringify(parameters),
    );
    try {
      const result = await this.mcp.callTool(toolName, parameters);
      console.log(`MCP Client: Received response from tool '${toolName}'`);
      return result;
    } catch (error) {
      console.error(
        `MCP Client ERROR: Failed to call tool '${toolName}':`,
        error,
      );
      throw error;
    }
  }
}