Skip to main content
Glama
mcp-client.mjs4.32 kB
/** * MCP Client Helper for Testing * Provides a simplified interface to interact with the MCP server for testing */ import { spawn } from 'child_process'; import { EventEmitter } from 'events'; export class McpClient extends EventEmitter { constructor(serverProcess) { super(); this.process = serverProcess; this.requestId = 1; this.pendingRequests = new Map(); this.isConnected = false; this.setupProcessHandlers(); } setupProcessHandlers() { let buffer = ''; this.process.stdout.on('data', (data) => { buffer += data.toString(); // Process complete JSON-RPC messages const lines = buffer.split('\n'); buffer = lines.pop() || ''; // Keep incomplete line in buffer for (const line of lines) { if (line.trim()) { try { const message = JSON.parse(line.trim()); this.handleMessage(message); } catch (error) { console.error('Failed to parse message:', line, error.message); } } } }); this.process.stderr.on('data', (data) => { // Server logs go to stderr, we can ignore or log them const logLine = data.toString(); if (process.env.DEBUG_MCP) { console.error('[MCP-SERVER]', logLine); } }); this.process.on('exit', (code) => { this.isConnected = false; console.error(`MCP server exited with code ${code}`); }); } handleMessage(message) { if (message.id && this.pendingRequests.has(message.id)) { const { resolve, reject } = this.pendingRequests.get(message.id); this.pendingRequests.delete(message.id); if (message.error) { reject(new Error(`MCP Error: ${message.error.message || JSON.stringify(message.error)}`)); } else { resolve(message.result); } } } async sendRequest(method, params = {}) { return new Promise((resolve, reject) => { const id = this.requestId++; const request = { jsonrpc: '2.0', id, method, params }; this.pendingRequests.set(id, { resolve, reject }); // Send request this.process.stdin.write(JSON.stringify(request) + '\n'); // Set timeout setTimeout(() => { if (this.pendingRequests.has(id)) { this.pendingRequests.delete(id); reject(new Error(`Request timeout for ${method}`)); } }, 30000); // 30 second timeout }); } async initialize() { try { await this.sendRequest('initialize', { protocolVersion: '2024-11-05', capabilities: { roots: { listChanged: true }, sampling: {} }, clientInfo: { name: 'test-client', version: '1.0.0' } }); await this.sendRequest('initialized'); this.isConnected = true; return true; } catch (error) { console.error('Failed to initialize MCP client:', error.message); return false; } } async listTools() { try { const result = await this.sendRequest('tools/list'); return result.tools || []; } catch (error) { console.error('Failed to list tools:', error.message); return []; } } async callTool(name, arguments_ = {}) { try { return await this.sendRequest('tools/call', { name, arguments: arguments_ }); } catch (error) { console.error(`Failed to call tool ${name}:`, error.message); throw error; } } async close() { if (this.process && !this.process.killed) { this.process.kill(); } } } export async function createMcpClient() { // Start the MCP server process const serverProcess = spawn('node', ['dist/index.js'], { cwd: process.cwd(), stdio: ['pipe', 'pipe', 'pipe'], env: { ...process.env, NODE_ENV: 'test' } }); const client = new McpClient(serverProcess); // Wait a bit for the server to start await new Promise(resolve => setTimeout(resolve, 1000)); // Initialize the connection const initialized = await client.initialize(); if (!initialized) { throw new Error('Failed to initialize MCP client'); } return client; } export default McpClient;

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/daviddraiumbrella/invoice-monitoring'

If you have feedback or need assistance with the MCP directory API, please join our Discord server