UNPKG

mcpdog

Version:

MCPDog - Universal MCP Server Manager with Web Interface

198 lines (193 loc) 7.18 kB
import { createInterface } from 'readline'; import { MCPDogServer } from './core/mcpdog-server.js'; import { ConfigManager } from './config/config-manager.js'; import { StreamableHttpMCPServer } from './streamable-http-server.js'; export class StdioMCPServer { server; readline; lastProcessedLine = ''; // Prevent duplicate line processing constructor(configManager) { console.error(`[STDIO] Creating StdioMCPServer instance`); this.server = new MCPDogServer(configManager); this.setupServer(); this.setupStdio(); } setupServer() { this.server.on('notification', (notification) => { this.sendMessage(notification); }); this.server.on('error', ({ error, context }) => { console.error(`MCPDog error [${context}]:`, error); }); this.server.on('started', () => { console.error('MCPDog Server started (stdio mode)'); }); this.server.on('stopped', () => { console.error('MCPDog Server stopped'); }); } setupStdio() { this.readline = createInterface({ input: process.stdin, output: process.stdout, crlfDelay: Infinity }); this.readline.on('line', (line) => { console.error(`[STDIO] Received line: ${line.substring(0, 50)}...`); this.handleInput(line.trim()); }); this.readline.on('close', () => { this.shutdown(); }); // Handle process signals process.on('SIGINT', () => this.shutdown()); process.on('SIGTERM', () => this.shutdown()); process.on('uncaughtException', (error) => { console.error('Uncaught exception:', error); this.shutdown(); }); } async handleInput(line) { console.error(`[STDIO] handleInput called with: ${line.substring(0, 30)}...`); if (!line) { return; } // Prevent processing duplicate lines if (line === this.lastProcessedLine) { console.error(`[DEDUP] Ignoring duplicate line processing`); return; } this.lastProcessedLine = line; try { const message = JSON.parse(line); // Check if it's a notification message (no id field) if (!('id' in message)) { const notification = message; console.error(`Handling notification: ${notification.method}`); // Notifications don't need responses return; } // Handle regular requests const request = message; console.error(`Processing request: ${request.method} (id: ${request.id})`); const response = await this.server.handleRequest(request, 'stdio-client'); console.error(`Sending response for: ${request.method} (id: ${request.id})`); this.sendMessage(response); } catch (error) { console.error('Error processing request:', error); // Don't send error response, only log error } } sendMessage(message) { const messageStr = JSON.stringify(message); console.log(messageStr); } async start() { try { console.error(`[STDIO] Starting StdioMCPServer...`); await this.server.start(); console.error(`[STDIO] StdioMCPServer started successfully`); } catch (error) { console.error('Failed to start server:', error); process.exit(1); } } async shutdown() { console.error('Shutting down MCPDog Server...'); try { if (this.readline) { this.readline.close(); } await this.server.stop(); process.exit(0); } catch (error) { console.error('Error during shutdown:', error); process.exit(1); } } } // Parse command line arguments function parseArgs() { const args = process.argv.slice(2); const result = {}; for (let i = 0; i < args.length; i++) { const arg = args[i]; if (arg === '--config' || arg === '-c') { result.configPath = args[i + 1]; i++; } else if (arg === '--web-port') { result.webPort = parseInt(args[i + 1], 10); i++; } else if (arg === '--transport' || arg === '-t') { result.transport = args[i + 1]; i++; } else if (arg === '--port' || arg === '-p') { result.port = parseInt(args[i + 1], 10); i++; } else if (arg === '--help' || arg === '-h') { console.log(` MCPDog - Universal MCP Server Manager Usage: mcpdog [options] Options: -c, --config <path> Configuration file path (default: ./mcpdog.config.json) -t, --transport <type> Transport protocol: stdio (default) or streamable-http -p, --port <port> Port for HTTP transport (default: 4000) --web-port <port> Enable web interface on port (experimental) -h, --help Show this help message Examples: mcpdog # Start with stdio transport mcpdog --transport streamable-http # Start with HTTP transport on default port mcpdog --transport streamable-http --port 8080 # Start with HTTP transport on port 8080 mcpdog --config ./my-config.json # Use custom config file mcpdog --web-port 3000 # Enable web interface (not yet implemented) Transport Types: stdio - Standard input/output (default, for MCP clients like Claude Desktop) streamable-http - HTTP-based transport with optional Server-Sent Events For more information, visit: https://github.com/kinhunt/mcpdog `); process.exit(0); } } return result; } // Main program entry point async function main() { const { configPath, webPort, transport, port } = parseArgs(); if (webPort) { console.error('Web interface not yet implemented'); process.exit(1); } const configManager = new ConfigManager(configPath); await configManager.loadConfig(); // Load config before passing to server // Choose transport type const transportType = transport || 'stdio'; if (transportType === 'streamable-http') { const httpPort = port || 4000; const httpServer = new StreamableHttpMCPServer(configManager, httpPort); await httpServer.start(); } else if (transportType === 'stdio') { const mcpServer = new StdioMCPServer(configManager); await mcpServer.start(); } else { console.error(`Unknown transport type: ${transportType}`); console.error('Supported transports: stdio, streamable-http'); process.exit(1); } } // Only start server when this file is run directly, not when imported if (import.meta.url === `file://${process.argv[1]}`) { main().catch(error => { console.error('Fatal error:', error); process.exit(1); }); } //# sourceMappingURL=index.js.map