Skip to main content
Glama
http-proxy.ts12.3 kB
#!/usr/bin/env node import express from 'express'; import cors from 'cors'; import { spawn, ChildProcess } from 'child_process'; import { program } from 'commander'; import { join, dirname } from 'path'; import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); // Parse command line arguments program .option('--http-port <port>', 'HTTP server port (default: 3000)', '3000') .option('--mc-host <host>', 'Default Minecraft server host') .option('--mc-port <port>', 'Default Minecraft server port') .parse(process.argv); const options = program.opts(); class MCPProxy { private mcpProcess: ChildProcess | null = null; private messageId = 1; private pendingRequests: Map<number, { resolve: Function; reject: Function; timeout: NodeJS.Timeout }> = new Map(); private buffer = ''; async start() { console.error('[PROXY] Starting MCP server process...'); // Build the command to start the MCP server const mcpServerPath = join(__dirname, 'mcp-server.js'); const args = []; if (options.mcHost) { args.push('--host', options.mcHost); } if (options.mcPort) { args.push('--port', options.mcPort); } console.error(`[PROXY] Spawning: node ${mcpServerPath} ${args.join(' ')}`); // Spawn the MCP server process this.mcpProcess = spawn('node', [mcpServerPath, ...args], { stdio: ['pipe', 'pipe', 'pipe'] }); if (!this.mcpProcess.stdin || !this.mcpProcess.stdout || !this.mcpProcess.stderr) { throw new Error('Failed to create MCP server process with proper stdio'); } // Handle MCP server output this.mcpProcess.stdout.on('data', (data) => { const output = data.toString(); console.error(`[PROXY] Raw stdout from MCP server: ${JSON.stringify(output)}`); // Add to buffer this.buffer += output; console.error(`[PROXY] Buffer now contains: ${this.buffer.length} characters`); // Try to extract complete JSON messages from buffer this.processBuffer(); }); // Handle MCP server errors this.mcpProcess.stderr.on('data', (data) => { console.error('[MCP]', data.toString().trim()); }); // Handle process exit this.mcpProcess.on('exit', (code) => { console.error(`[PROXY] MCP server process exited with code ${code}`); this.mcpProcess = null; }); console.error('[PROXY] MCP server process started'); } private processBuffer() { // Try to find complete JSON messages in the buffer let startIndex = 0; while (startIndex < this.buffer.length) { // Look for the start of a JSON message const openBrace = this.buffer.indexOf('{', startIndex); if (openBrace === -1) { // No more JSON objects in buffer break; } // Find the matching closing brace let braceCount = 0; let endIndex = -1; for (let i = openBrace; i < this.buffer.length; i++) { if (this.buffer[i] === '{') { braceCount++; } else if (this.buffer[i] === '}') { braceCount--; if (braceCount === 0) { endIndex = i; break; } } } if (endIndex === -1) { // Incomplete JSON message, wait for more data console.error(`[PROXY] Incomplete JSON message in buffer, waiting for more data`); break; } // Extract the complete JSON message const jsonStr = this.buffer.substring(openBrace, endIndex + 1); console.error(`[PROXY] Extracted complete JSON: ${jsonStr.substring(0, 100)}...`); try { const message = JSON.parse(jsonStr); console.error(`[PROXY] Successfully parsed JSON message with id: ${message.id}`); this.handleMCPResponse(message); } catch (e) { console.error('[PROXY] Failed to parse extracted JSON:', e); console.error('[PROXY] JSON was:', jsonStr.substring(0, 200)); } // Move past this message startIndex = endIndex + 1; } // Remove processed data from buffer if (startIndex > 0) { this.buffer = this.buffer.substring(startIndex); console.error(`[PROXY] Buffer trimmed to ${this.buffer.length} characters`); } } private handleMCPResponse(message: any) { console.error(`[PROXY] Handling MCP response with ID: ${message.id}`); console.error(`[PROXY] Current pending requests: ${Array.from(this.pendingRequests.keys()).join(', ')}`); if (message.id && this.pendingRequests.has(message.id)) { console.error(`[PROXY] Found matching request for ID: ${message.id}`); const request = this.pendingRequests.get(message.id)!; clearTimeout(request.timeout); this.pendingRequests.delete(message.id); request.resolve(message); } else { console.error(`[PROXY] No matching request found for message ID: ${message.id}`); console.error(`[PROXY] Message was:`, JSON.stringify(message, null, 2)); } } async sendRequest(method: string, params?: any): Promise<any> { if (!this.mcpProcess || !this.mcpProcess.stdin) { throw new Error('MCP server process not running'); } const id = this.messageId++; const request = { jsonrpc: '2.0', id, method, ...(params && { params }) }; console.error(`[PROXY] Sending request to MCP server:`, JSON.stringify(request, null, 2)); return new Promise((resolve, reject) => { const timeout = setTimeout(() => { console.error(`[PROXY] Request ${id} timed out`); this.pendingRequests.delete(id); reject(new Error('Request timeout')); }, 30000); this.pendingRequests.set(id, { resolve, reject, timeout }); console.error(`[PROXY] Added request ${id} to pending requests`); try { const requestStr = JSON.stringify(request) + '\n'; console.error(`[PROXY] Writing to MCP server stdin: ${JSON.stringify(requestStr)}`); this.mcpProcess!.stdin!.write(requestStr); } catch (error) { console.error(`[PROXY] Error writing to MCP server stdin:`, error); clearTimeout(timeout); this.pendingRequests.delete(id); reject(error); } }); } async stop() { if (this.mcpProcess) { this.mcpProcess.kill(); this.mcpProcess = null; } // Reject all pending requests for (const [id, request] of this.pendingRequests) { clearTimeout(request.timeout); request.reject(new Error('Server shutting down')); } this.pendingRequests.clear(); } } async function main() { const httpPort = parseInt(options.httpPort); const proxy = new MCPProxy(); // Start the MCP server await proxy.start(); // Create Express app const app = express(); // Enable CORS app.use(cors({ origin: true, credentials: true })); // Parse JSON bodies app.use(express.json()); // Health check endpoint app.get('/health', (req, res) => { res.json({ status: 'healthy', transport: 'http-proxy', timestamp: new Date().toISOString(), mcpProcess: proxy['mcpProcess'] !== null }); }); // MCP endpoint - handle POST requests app.post('/mcp', async (req, res) => { try { // Validate Origin header for security const origin = req.get('Origin'); const message = req.body; console.error(`[PROXY] Received HTTP request:`, JSON.stringify(message, null, 2)); // Set CORS headers res.setHeader('Content-Type', 'application/json'); res.setHeader('Access-Control-Allow-Origin', origin || '*'); res.setHeader('Access-Control-Allow-Methods', 'POST, GET, OPTIONS'); res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Accept, Mcp-Session-Id'); // Special handling for initialize method if (message.method === 'initialize') { // Forward the initialize request to MCP server const initResponse = await proxy.sendRequest(message.method, message.params); // Also get the tools list to enhance capabilities const toolsResponse = await proxy.sendRequest('tools/list'); // Enhance the capabilities with actual tools info if (initResponse.result && initResponse.result.capabilities && toolsResponse.result && toolsResponse.result.tools) { initResponse.result.capabilities.tools = { available: toolsResponse.result.tools.length, listSupported: true }; console.error(`[PROXY] Enhanced capabilities with ${toolsResponse.result.tools.length} tools`); } console.error(`[PROXY] Sending enhanced HTTP response:`, JSON.stringify(initResponse, null, 2)); res.json({ ...initResponse, id: message.id // Use the original request ID }); } else { // Forward other requests normally const response = await proxy.sendRequest(message.method, message.params); console.error(`[PROXY] Sending HTTP response:`, JSON.stringify(response, null, 2)); res.json({ ...response, id: message.id // Use the original request ID }); } } catch (error) { console.error(`[PROXY] Error handling request:`, error); res.status(500).json({ jsonrpc: '2.0', id: req.body?.id || null, error: { code: -32603, message: 'Internal error', data: error instanceof Error ? error.message : String(error) } }); } }); // Handle OPTIONS requests for CORS app.options('/mcp', (req, res) => { res.setHeader('Access-Control-Allow-Origin', req.get('Origin') || '*'); res.setHeader('Access-Control-Allow-Methods', 'POST, GET, OPTIONS'); res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Accept, Mcp-Session-Id'); res.status(200).end(); }); // Start HTTP server app.listen(httpPort, '0.0.0.0', () => { console.error(`[PROXY] HTTP server running on http://0.0.0.0:${httpPort}`); console.error(`[PROXY] MCP endpoint: http://0.0.0.0:${httpPort}/mcp`); console.error(`[PROXY] Health check: http://0.0.0.0:${httpPort}/health`); console.error(`[PROXY] Example usage:`); console.error(` curl -X POST http://0.0.0.0:${httpPort}/mcp -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'`); }); // Handle shutdown gracefully const shutdown = async () => { console.error('[PROXY] Shutting down...'); await proxy.stop(); process.exit(0); }; process.on('SIGINT', shutdown); process.on('SIGTERM', shutdown); } main().catch((error) => { console.error('[PROXY] Failed to start:', error); process.exit(1); });

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/leo4life2/minecraft-mcp-http'

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