UNPKG

converse-mcp-server

Version:

Converse MCP Server - Converse with other LLMs with chat and consensus tools

275 lines (230 loc) 8.78 kB
#!/usr/bin/env node /** * Converse MCP Server - Main Entry Point * * Simplified, functional Node.js implementation of MCP server * with chat and consensus tools using modern Node.js practices. */ import { fileURLToPath, pathToFileURL } from 'url'; import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { loadConfig, validateRuntimeConfig, getMcpClientConfig, getHttpTransportConfig } from './config.js'; import { createRouter } from './router.js'; import { createHTTPTransport } from './transport/httpTransport.js'; import { createLogger, startTimer } from './utils/logger.js'; import { debugError } from './utils/console.js'; import { ConfigurationError } from './utils/errorHandler.js'; const logger = createLogger('server'); /** * Show help message */ function showHelp() { debugError(` Converse MCP Server Usage: node src/index.js [OPTIONS] Options: --transport <type> Transport type: stdio (default) or http --transport=<type> Alternative format for transport type --help Show this help message Environment Variables: MCP_TRANSPORT Transport type (stdio or http) PORT HTTP server port (default: 3157, for HTTP transport) HOST HTTP server host (default: localhost, for HTTP transport) Examples: node src/index.js # Start with stdio transport (default) node src/index.js --transport stdio # Start with stdio transport node src/index.js --transport http # Start with HTTP transport npm start # Start with stdio transport MCP_TRANSPORT=http npm start # Start with HTTP transport `); } /** * Determine transport type from command line arguments or environment */ function getTransportType() { // Check command line arguments const args = process.argv.slice(2); // Support --transport=value format const transportEqualArg = args.find(arg => arg.startsWith('--transport=')); if (transportEqualArg) { const transport = transportEqualArg.split('=')[1]; if (transport && ['http', 'stdio'].includes(transport)) { return transport; } } // Support --transport value format const transportIndex = args.findIndex(arg => arg === '--transport'); if (transportIndex >= 0 && transportIndex + 1 < args.length) { const transport = args[transportIndex + 1]; if (transport && ['http', 'stdio'].includes(transport)) { return transport; } } // Check environment variable if (process.env.MCP_TRANSPORT) { const transport = process.env.MCP_TRANSPORT.toLowerCase(); if (['http', 'stdio'].includes(transport)) { return transport; } } // Default to stdio for standard MCP usage return 'stdio'; } async function main() { // Check for help flag const args = process.argv.slice(2); if (args.includes('--help') || args.includes('-h')) { showHelp(); process.exit(0); } // Determine transport type first to configure logging appropriately const transportType = getTransportType(); // Set environment variable early for stdio transport to suppress console output if (transportType === 'stdio') { process.env.MCP_TRANSPORT = 'stdio'; // Reconfigure logger with updated environment const { configureLogger } = await import('./utils/logger.js'); configureLogger({ level: process.env.LOG_LEVEL || 'info', isDevelopment: process.env.NODE_ENV === 'development' }); } const serverTimer = startTimer('server-startup', 'server'); try { logger.info('Starting Converse MCP Server'); // Load and validate configuration const config = await loadConfig(); await validateRuntimeConfig(config); // Get MCP client configuration const mcpConfig = getMcpClientConfig(config); logger.info('Using transport type', { data: { transport: transportType } }); logger.debug('Creating MCP server instance', { data: { name: mcpConfig.name, version: mcpConfig.version } }); // Create MCP server with configuration const server = new Server( { name: mcpConfig.name, version: mcpConfig.version, }, mcpConfig ); // Set up router with server and config await createRouter(server, config); // Start server with appropriate transport if (transportType === 'http') { // HTTP streaming transport with full configuration const httpConfig = getHttpTransportConfig(config); const httpTransport = await createHTTPTransport(server, httpConfig); await httpTransport.start(); const status = httpTransport.getStatus(); const startupTime = serverTimer('completed'); logger.info('Converse MCP Server started successfully with HTTP transport', { data: { startupTime: `${startupTime}ms`, endpoint: `http://${status.host}:${status.port}/mcp`, host: status.host, port: status.port } }); // Store reference for shutdown process.httpTransport = httpTransport; } else { // Stdio transport (legacy) const transport = new StdioServerTransport(); await server.connect(transport); const startupTime = serverTimer('completed'); logger.info('Converse MCP Server started successfully with stdio transport', { data: { startupTime: `${startupTime}ms` } }); } } catch (error) { serverTimer('failed'); if (error instanceof ConfigurationError) { logger.error('Configuration error during startup', { error }); debugError('Configuration Error:'); debugError(error.message); if (error.details?.errors) { debugError('\nDetailed errors:'); error.details.errors.forEach(err => debugError(` - ${err}`)); } process.exit(1); } else { logger.error('Failed to start Converse MCP Server', { error }); debugError('Failed to start Converse MCP Server:', error.message); process.exit(1); } } } // Handle graceful shutdown async function gracefulShutdown(signal) { logger.info(`Received ${signal}, shutting down gracefully`); debugError('Shutting down Converse MCP Server...'); // Stop all cleanup intervals try { const { stopContinuationStoreCleanup } = await import('./continuationStore.js'); const { stopAsyncJobStoreCleanup } = await import('./async/asyncJobStore.js'); const { stopFileCacheCleanup } = await import('./async/fileCache.js'); stopContinuationStoreCleanup(); stopAsyncJobStoreCleanup(); stopFileCacheCleanup(); logger.info('Cleanup timers stopped successfully'); } catch (error) { logger.error('Error stopping cleanup timers', { error }); } if (process.httpTransport) { try { await process.httpTransport.stop(); logger.info('HTTP transport stopped successfully'); } catch (error) { logger.error('Error stopping HTTP transport', { error }); } } process.exit(0); } process.on('SIGINT', () => gracefulShutdown('SIGINT')); process.on('SIGTERM', () => gracefulShutdown('SIGTERM')); // Additional cleanup on process exit process.on('beforeExit', async (code) => { logger.info('Process beforeExit event', { data: { code } }); // Cleanup intervals if not already done try { const { stopContinuationStoreCleanup } = await import('./continuationStore.js'); const { stopAsyncJobStoreCleanup } = await import('./async/asyncJobStore.js'); const { stopFileCacheCleanup } = await import('./async/fileCache.js'); stopContinuationStoreCleanup(); stopAsyncJobStoreCleanup(); stopFileCacheCleanup(); } catch (error) { // Silently ignore - cleanup may have already been done } }); process.on('exit', (code) => { logger.info('Process exit event', { data: { code } }); }); process.on('uncaughtException', (error) => { logger.error('Uncaught exception', { error }); debugError('Fatal error:', error); process.exit(1); }); process.on('unhandledRejection', (reason, promise) => { logger.error('Unhandled promise rejection', { error: reason, data: { promise: promise.toString() } }); debugError('Unhandled promise rejection:', reason); process.exit(1); }); // Export the main function for use in bin/converse.js export { main }; // Check if this module is the main entry point // This works better across platforms and Node.js versions const isMainModule = import.meta.url === pathToFileURL(process.argv[1]).href || process.argv[1] === fileURLToPath(import.meta.url); if (isMainModule) { main().catch((error) => { logger.error('Fatal error in main', { error }); debugError('Fatal error:', error); process.exit(1); }); }