Skip to main content
Glama

MCP Complete Implementation Guide

by saksham0712
README.md•23.1 kB
# Generic MCP Client Example This example shows how to create a generic client that can connect to any MCP server, regardless of the AI model being used. ## Overview This generic client implementation provides: - HTTP client for MCP servers with HTTP transport - WebSocket client for real-time communication - Command-line interface for testing - REST API wrapper for easy integration - Language-agnostic approach ## Client Implementations ### Node.js HTTP Client ```javascript // mcp-client.js const fetch = require('node-fetch'); const WebSocket = require('ws'); class MCPClient { constructor(serverUrl, transport = 'http') { this.serverUrl = serverUrl; this.transport = transport; this.ws = null; } // HTTP Transport async callTool(toolName, args) { if (this.transport === 'http') { const response = await fetch(`${this.serverUrl}/mcp/tools/${toolName}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ arguments: args }), }); return await response.json(); } // WebSocket transport would go here throw new Error('WebSocket transport not yet implemented'); } async listTools() { const response = await fetch(`${this.serverUrl}/mcp/tools`, { method: 'GET', }); return await response.json(); } async getServerInfo() { const response = await fetch(`${this.serverUrl}/health`); return await response.json(); } // WebSocket Transport connectWebSocket() { return new Promise((resolve, reject) => { this.ws = new WebSocket(this.serverUrl.replace('http', 'ws') + '/mcp'); this.ws.on('open', () => { console.log('WebSocket connected'); resolve(); }); this.ws.on('error', reject); this.ws.on('message', (data) => { const message = JSON.parse(data); this.handleMessage(message); }); }); } sendMessage(message) { if (this.ws && this.ws.readyState === WebSocket.OPEN) { this.ws.send(JSON.stringify(message)); } } handleMessage(message) { console.log('Received message:', message); // Handle incoming messages based on MCP protocol } disconnect() { if (this.ws) { this.ws.close(); } } } module.exports = MCPClient; ``` ### Python HTTP Client ```python # mcp_client.py import asyncio import json from typing import Dict, Any, List, Optional import httpx import websockets class MCPClient: def __init__(self, server_url: str, transport: str = 'http'): self.server_url = server_url self.transport = transport self.ws_connection = None # HTTP Transport async def call_tool(self, tool_name: str, args: Dict[str, Any]) -> Dict[str, Any]: """Call a tool on the MCP server""" if self.transport == 'http': async with httpx.AsyncClient() as client: response = await client.post( f"{self.server_url}/mcp/tools/{tool_name}", json={"arguments": args} ) return response.json() # WebSocket transport would go here raise NotImplementedError("WebSocket transport not yet implemented") async def list_tools(self) -> List[Dict[str, Any]]: """List available tools""" async with httpx.AsyncClient() as client: response = await client.get(f"{self.server_url}/mcp/tools") return response.json() async def get_server_info(self) -> Dict[str, Any]: """Get server health and info""" async with httpx.AsyncClient() as client: response = await client.get(f"{self.server_url}/health") return response.json() # WebSocket Transport async def connect_websocket(self): """Connect to MCP server via WebSocket""" ws_url = self.server_url.replace('http', 'ws') + '/mcp' self.ws_connection = await websockets.connect(ws_url) print(f"WebSocket connected to {ws_url}") async def send_message(self, message: Dict[str, Any]): """Send message via WebSocket""" if self.ws_connection: await self.ws_connection.send(json.dumps(message)) async def receive_messages(self): """Listen for incoming WebSocket messages""" if self.ws_connection: async for message in self.ws_connection: data = json.loads(message) await self.handle_message(data) async def handle_message(self, message: Dict[str, Any]): """Handle incoming WebSocket message""" print(f"Received message: {message}") # Handle based on MCP protocol async def disconnect(self): """Disconnect WebSocket""" if self.ws_connection: await self.ws_connection.close() # Usage example async def main(): client = MCPClient('http://localhost:3000') # Test server connection info = await client.get_server_info() print(f"Server info: {info}") # List available tools tools = await client.list_tools() print(f"Available tools: {tools}") # Call a tool result = await client.call_tool('get_system_info', {}) print(f"System info: {result}") if __name__ == "__main__": asyncio.run(main()) ``` ### Command Line Interface ```javascript // cli.js const MCPClient = require('./mcp-client'); const readline = require('readline'); const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); class MCPCLI { constructor(serverUrl) { this.client = new MCPClient(serverUrl); this.tools = []; } async init() { try { // Test connection const info = await this.client.getServerInfo(); console.log(`\n🟢 Connected to MCP Server`); console.log(` Status: ${info.status}`); console.log(` Timestamp: ${info.timestamp}`); // Load available tools this.tools = await this.client.listTools(); console.log(`\nšŸ“‹ Available Tools:`); this.tools.forEach((tool, index) => { console.log(` ${index + 1}. ${tool.name} - ${tool.description}`); }); console.log(`\nšŸ’” Type 'help' for commands or 'quit' to exit\n`); this.startPrompt(); } catch (error) { console.error(`āŒ Failed to connect: ${error.message}`); process.exit(1); } } startPrompt() { rl.question('mcp> ', async (input) => { await this.handleCommand(input.trim()); this.startPrompt(); }); } async handleCommand(input) { const [command, ...args] = input.split(' '); switch (command.toLowerCase()) { case 'help': this.showHelp(); break; case 'tools': case 'list': this.showTools(); break; case 'call': await this.callTool(args.join(' ')); break; case 'info': await this.showServerInfo(); break; case 'quit': case 'exit': console.log('šŸ‘‹ Goodbye!'); process.exit(0); break; default: if (input) { console.log(`āŒ Unknown command: ${command}. Type 'help' for available commands.`); } } } showHelp() { console.log(` šŸ“– Available Commands: `); console.log(` help - Show this help message`); console.log(` tools, list - List available tools`); console.log(` call <tool> - Call a tool (interactive mode)`); console.log(` info - Show server information`); console.log(` quit, exit - Exit the CLI`); console.log(``); } showTools() { console.log(`\nšŸ“‹ Available Tools:\n`); this.tools.forEach((tool, index) => { console.log(` ${index + 1}. ${tool.name}`); console.log(` Description: ${tool.description}`); console.log(` Parameters: ${JSON.stringify(tool.inputSchema?.properties || {}, null, 6)}`); console.log(''); }); } async callTool(toolName) { if (!toolName) { console.log('šŸ”§ Available tools:'); this.tools.forEach((tool, index) => { console.log(` ${index + 1}. ${tool.name}`); }); return new Promise((resolve) => { rl.question('Enter tool name or number: ', async (selection) => { const tool = isNaN(selection) ? this.tools.find(t => t.name === selection) : this.tools[parseInt(selection) - 1]; if (tool) { await this.executeTool(tool); } else { console.log('āŒ Tool not found'); } resolve(); }); }); } const tool = this.tools.find(t => t.name === toolName); if (tool) { await this.executeTool(tool); } else { console.log(`āŒ Tool '${toolName}' not found`); } } async executeTool(tool) { console.log(`\nšŸ”§ Calling tool: ${tool.name}`); const params = tool.inputSchema?.properties || {}; const required = tool.inputSchema?.required || []; const args = {}; // Collect parameters for (const [paramName, paramSchema] of Object.entries(params)) { await new Promise((resolve) => { const isRequired = required.includes(paramName); const prompt = `${paramName} (${paramSchema.type})${isRequired ? ' *' : ''}: `; rl.question(prompt, (value) => { if (value.trim() || !isRequired) { if (paramSchema.type === 'number') { args[paramName] = parseFloat(value) || undefined; } else if (paramSchema.type === 'boolean') { args[paramName] = value.toLowerCase() === 'true'; } else { args[paramName] = value || undefined; } } resolve(); }); }); } try { console.log('ā³ Executing...'); const result = await this.client.callTool(tool.name, args); console.log('\nāœ… Result:'); console.log(JSON.stringify(result, null, 2)); } catch (error) { console.log(`\nāŒ Error: ${error.message}`); } } async showServerInfo() { try { const info = await this.client.getServerInfo(); console.log('\nšŸ–„ļø Server Information:'); console.log(JSON.stringify(info, null, 2)); } catch (error) { console.log(`āŒ Error getting server info: ${error.message}`); } } } // Start CLI const serverUrl = process.argv[2] || 'http://localhost:3000'; const cli = new MCPCLI(serverUrl); cli.init(); ``` ## REST API Wrapper ### Express.js Wrapper ```javascript // rest-wrapper.js const express = require('express'); const cors = require('cors'); const MCPClient = require('./mcp-client'); class MCPRestWrapper { constructor(mcpServerUrl, port = 4000) { this.app = express(); this.client = new MCPClient(mcpServerUrl); this.port = port; this.setupMiddleware(); this.setupRoutes(); } setupMiddleware() { this.app.use(cors()); this.app.use(express.json()); // Logging middleware this.app.use((req, res, next) => { console.log(`${new Date().toISOString()} ${req.method} ${req.path}`); next(); }); } setupRoutes() { // Health check this.app.get('/health', async (req, res) => { try { const serverHealth = await this.client.getServerInfo(); res.json({ wrapper: 'healthy', mcpServer: serverHealth, timestamp: new Date().toISOString() }); } catch (error) { res.status(500).json({ wrapper: 'healthy', mcpServer: 'unreachable', error: error.message }); } }); // List tools this.app.get('/tools', async (req, res) => { try { const tools = await this.client.listTools(); res.json(tools); } catch (error) { res.status(500).json({ error: error.message }); } }); // Call tool this.app.post('/tools/:toolName', async (req, res) => { try { const { toolName } = req.params; const { arguments: args } = req.body; const result = await this.client.callTool(toolName, args || {}); res.json(result); } catch (error) { res.status(500).json({ error: error.message }); } }); // Generic tool calling endpoint this.app.post('/call', async (req, res) => { try { const { tool, arguments: args } = req.body; if (!tool) { return res.status(400).json({ error: 'Tool name required' }); } const result = await this.client.callTool(tool, args || {}); res.json(result); } catch (error) { res.status(500).json({ error: error.message }); } }); // Batch tool calling this.app.post('/batch', async (req, res) => { try { const { calls } = req.body; if (!Array.isArray(calls)) { return res.status(400).json({ error: 'Calls must be an array' }); } const results = []; for (const call of calls) { try { const result = await this.client.callTool(call.tool, call.arguments || {}); results.push({ success: true, result }); } catch (error) { results.push({ success: false, error: error.message }); } } res.json({ results }); } catch (error) { res.status(500).json({ error: error.message }); } }); } start() { this.app.listen(this.port, () => { console.log(`🌐 MCP REST Wrapper running on port ${this.port}`); console.log(`šŸ“‹ Available endpoints:`); console.log(` GET /health - Health check`); console.log(` GET /tools - List available tools`); console.log(` POST /tools/:toolName - Call a specific tool`); console.log(` POST /call - Generic tool calling`); console.log(` POST /batch - Batch tool calling`); }); } } // Start wrapper const mcpServerUrl = process.argv[2] || 'http://localhost:3000'; const wrapperPort = parseInt(process.argv[3]) || 4000; const wrapper = new MCPRestWrapper(mcpServerUrl, wrapperPort); wrapper.start(); ``` ## Usage Examples ### Using the CLI ```powershell # Start your MCP server node server.js # Start the CLI (in another terminal) node cli.js http://localhost:3000 ``` Example CLI session: ``` 🟢 Connected to MCP Server Status: healthy Timestamp: 2024-01-15T10:30:00.000Z šŸ“‹ Available Tools: 1. read_file - Read the contents of a file 2. write_file - Write content to a file 3. list_directory - List the contents of a directory 4. get_system_info - Get system information 5. execute_command - Execute a system command 6. fetch_url - Fetch content from a URL šŸ’” Type 'help' for commands or 'quit' to exit mcp> call get_system_info šŸ”§ Calling tool: get_system_info ā³ Executing... āœ… Result: { "content": [ { "type": "text", "text": "{\"platform\":\"win32\",\"arch\":\"x64\",\"hostname\":\"DESKTOP-ABC123\",...}" } ] } mcp> call read_file šŸ”§ Calling tool: read_file path (string) *: package.json ā³ Executing... āœ… Result: { "content": [ { "type": "text", "text": "{\n \"name\": \"mcp-implementation\",\n \"version\": \"1.0.0\",\n ..." } ] } ``` ### Using the REST Wrapper ```powershell # Start the REST wrapper node rest-wrapper.js http://localhost:3000 4000 ``` Then use HTTP requests: ```bash # List tools curl http://localhost:4000/tools # Call a tool curl -X POST http://localhost:4000/tools/get_system_info \ -H "Content-Type: application/json" \ -d '{}' # Generic call curl -X POST http://localhost:4000/call \ -H "Content-Type: application/json" \ -d '{ "tool": "read_file", "arguments": { "path": "README.md" } }' # Batch calls curl -X POST http://localhost:4000/batch \ -H "Content-Type: application/json" \ -d '{ "calls": [ { "tool": "get_system_info", "arguments": {} }, { "tool": "list_directory", "arguments": { "path": "." } } ] }' ``` ## Language Integration Examples ### cURL Examples ```bash # Health check curl http://localhost:3000/health # List directory curl -X POST http://localhost:3000/tools/list_directory \ -H "Content-Type: application/json" \ -d '{"arguments": {"path": "."}}' # Read file curl -X POST http://localhost:3000/tools/read_file \ -H "Content-Type: application/json" \ -d '{"arguments": {"path": "package.json"}}' ``` ### PowerShell Examples ```powershell # Health check Invoke-RestMethod -Uri "http://localhost:3000/health" -Method GET # Call tool $body = @{ arguments = @{ path = "." } } | ConvertTo-Json Invoke-RestMethod -Uri "http://localhost:3000/tools/list_directory" ` -Method POST ` -ContentType "application/json" ` -Body $body ``` ### Python Integration ```python import asyncio from mcp_client import MCPClient async def example_usage(): client = MCPClient('http://localhost:3000') # Get system info info = await client.call_tool('get_system_info', {}) print(f"System Info: {info}") # Read a file file_content = await client.call_tool('read_file', {'path': 'README.md'}) print(f"File Content: {file_content}") # List directory directory = await client.call_tool('list_directory', {'path': '.'}) print(f"Directory: {directory}") if __name__ == "__main__": asyncio.run(example_usage()) ``` ## Testing and Debugging ### Health Check Script ```javascript // health-check.js const MCPClient = require('./mcp-client'); async function healthCheck(serverUrl) { const client = new MCPClient(serverUrl); console.log(`šŸ” Testing MCP Server at ${serverUrl}\n`); try { // Test server health console.log('1. Testing server health...'); const health = await client.getServerInfo(); console.log(` āœ… Server is ${health.status}\n`); // Test tool listing console.log('2. Testing tool listing...'); const tools = await client.listTools(); console.log(` āœ… Found ${tools.length} tools\n`); // Test each tool (with safe parameters) console.log('3. Testing individual tools...'); // Test get_system_info (no parameters needed) try { const sysInfo = await client.callTool('get_system_info', {}); console.log(' āœ… get_system_info works'); } catch (e) { console.log(' āŒ get_system_info failed:', e.message); } // Test list_directory with safe path try { const dirList = await client.callTool('list_directory', { path: '.' }); console.log(' āœ… list_directory works'); } catch (e) { console.log(' āŒ list_directory failed:', e.message); } console.log('\nšŸŽ‰ Health check completed!'); } catch (error) { console.error(`\nāŒ Health check failed: ${error.message}`); process.exit(1); } } // Run health check const serverUrl = process.argv[2] || 'http://localhost:3000'; healthCheck(serverUrl); ``` ### Load Testing ```javascript // load-test.js const MCPClient = require('./mcp-client'); async function loadTest(serverUrl, concurrency = 10, requests = 100) { console.log(`šŸš€ Load testing ${serverUrl}`); console.log(` Concurrency: ${concurrency}`); console.log(` Total requests: ${requests}\n`); const clients = Array(concurrency).fill().map(() => new MCPClient(serverUrl)); const startTime = Date.now(); const results = { success: 0, errors: 0 }; const makeRequest = async (client, requestId) => { try { await client.callTool('get_system_info', {}); results.success++; } catch (error) { results.errors++; console.log(` āŒ Request ${requestId} failed: ${error.message}`); } }; const promises = []; for (let i = 0; i < requests; i++) { const client = clients[i % concurrency]; promises.push(makeRequest(client, i + 1)); // Add small delay to spread requests if (i % concurrency === 0) { await new Promise(resolve => setTimeout(resolve, 10)); } } await Promise.all(promises); const duration = Date.now() - startTime; const rps = Math.round((requests / duration) * 1000); console.log(`\nšŸ“Š Load test results:`); console.log(` Duration: ${duration}ms`); console.log(` Success: ${results.success}`); console.log(` Errors: ${results.errors}`); console.log(` Requests/sec: ${rps}`); } // Run load test const serverUrl = process.argv[2] || 'http://localhost:3000'; const concurrency = parseInt(process.argv[3]) || 10; const requests = parseInt(process.argv[4]) || 100; loadTest(serverUrl, concurrency, requests); ``` ## Error Handling ### Retry Logic ```javascript class MCPClientWithRetry extends MCPClient { async callToolWithRetry(toolName, args, maxRetries = 3, delay = 1000) { let lastError; for (let attempt = 1; attempt <= maxRetries; attempt++) { try { return await this.callTool(toolName, args); } catch (error) { lastError = error; if (attempt < maxRetries) { console.log(`Attempt ${attempt} failed, retrying in ${delay}ms...`); await new Promise(resolve => setTimeout(resolve, delay)); delay *= 2; // Exponential backoff } } } throw lastError; } } ``` ### Circuit Breaker ```javascript class CircuitBreaker { constructor(threshold = 5, timeout = 60000) { this.threshold = threshold; this.timeout = timeout; this.failures = 0; this.nextAttempt = Date.now(); this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN } async call(fn) { if (this.state === 'OPEN') { if (Date.now() < this.nextAttempt) { throw new Error('Circuit breaker is OPEN'); } this.state = 'HALF_OPEN'; } try { const result = await fn(); this.onSuccess(); return result; } catch (error) { this.onFailure(); throw error; } } onSuccess() { this.failures = 0; this.state = 'CLOSED'; } onFailure() { this.failures++; if (this.failures >= this.threshold) { this.state = 'OPEN'; this.nextAttempt = Date.now() + this.timeout; } } } ``` ## Next Steps 1. **Customize for Your Use Case**: Modify the client to match your specific requirements 2. **Add Authentication**: Implement proper authentication and authorization 3. **Monitoring**: Add logging, metrics, and monitoring capabilities 4. **Performance**: Optimize for your expected load and latency requirements 5. **Documentation**: Document your specific tools and usage patterns This generic client provides a solid foundation for integrating any AI model or application with your MCP server.

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/saksham0712/MCP'

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