UNPKG

@nvhien20vn/git-mcp-server

Version:
186 lines (185 loc) 9.13 kB
#!/usr/bin/env node import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import http from 'http'; // Import http module import { config, environment, logLevel } from "./config/index.js"; // Import logLevel from config import { initializeAndStartServer } from "./mcp-server/server.js"; // Import utils from barrel import { logger, requestContextService } from './utils/index.js'; // Import McpLogLevel type /** * The main MCP server instance (used for stdio transport). * @type {McpServer | undefined} */ let mcpServerInstance; /** * The main HTTP server instance (used for http transport). * @type {http.Server | undefined} */ let httpServerInstance; /** * Gracefully shuts down the main MCP server. * Handles process termination signals (SIGTERM, SIGINT) and critical errors. * * @param signal - The signal or event name that triggered the shutdown (e.g., "SIGTERM", "uncaughtException"). */ const shutdown = async (signal) => { const transportType = (process.env.MCP_TRANSPORT_TYPE || 'stdio').toLowerCase(); const shutdownContext = { operation: 'Shutdown', signal, transport: transportType, }; logger.info(`Received ${signal}. Starting graceful shutdown...`, shutdownContext); try { let closePromise = Promise.resolve(); if (transportType === 'stdio') { // Close the main MCP server instance for stdio if (mcpServerInstance) { logger.info("Closing main MCP server (stdio)...", shutdownContext); closePromise = mcpServerInstance.close(); } else { logger.warning("Stdio MCP server instance not found during shutdown.", shutdownContext); } } else if (transportType === 'http') { // Close the main HTTP server listener for http if (httpServerInstance) { logger.info("Closing main HTTP server listener...", shutdownContext); closePromise = new Promise((resolve, reject) => { httpServerInstance.close((err) => { if (err) { logger.error("Error closing HTTP server listener", { ...shutdownContext, error: err.message }); reject(err); } else { logger.info("Main HTTP server listener closed successfully", shutdownContext); resolve(); } }); }); } else { logger.warning("HTTP server instance not found during shutdown.", shutdownContext); } // Note: Individual session transports (StreamableHTTPServerTransport) are closed // when the client disconnects or sends a DELETE request, managed in httpTransport.ts. } // Wait for the appropriate server/listener to close await closePromise; logger.info("Graceful shutdown completed successfully", shutdownContext); process.exit(0); } catch (error) { // Handle any errors during shutdown logger.error("Critical error during shutdown", { ...shutdownContext, error: error instanceof Error ? error.message : String(error), stack: error instanceof Error ? error.stack : undefined }); process.exit(1); // Exit with error code if shutdown fails } }; /** * Initializes and starts the main MCP server. * Sets up request context, initializes the server instance, starts the transport, * and registers signal handlers for graceful shutdown and error handling. */ const start = async () => { // --- Initialize Logger FIRST --- // Define valid MCP log levels based on the logger's type definition const validMcpLogLevels = ['debug', 'info', 'notice', 'warning', 'error', 'crit', 'alert', 'emerg']; let validatedMcpLogLevel = 'info'; // Default to 'info' if (validMcpLogLevels.includes(logLevel)) { validatedMcpLogLevel = logLevel; } else { // Use console.warn as logger isn't ready yet console.warn(`Invalid MCP_LOG_LEVEL "${logLevel}" found in config. Defaulting to "info".`); } // Initialize the logger singleton instance with the validated level. logger.initialize(validatedMcpLogLevel); // Now it's safe to use the logger. // --- Start Application --- const transportType = (process.env.MCP_TRANSPORT_TYPE || 'stdio').toLowerCase(); const startupContext = requestContextService.createRequestContext({ operation: `ServerStartup_${transportType}`, // Include transport in operation name appName: config.mcpServerName, appVersion: config.mcpServerVersion, environment: environment }); logger.info(`Starting ${config.mcpServerName} v${config.mcpServerVersion} (Transport: ${transportType})...`, startupContext); try { // Initialize the server instance and start the selected transport logger.debug("Initializing and starting MCP server transport", startupContext); // Start the server transport. This returns the McpServer instance for stdio // or the http.Server instance for http. const serverOrHttpInstance = await initializeAndStartServer(); if (transportType === 'stdio' && serverOrHttpInstance instanceof McpServer) { mcpServerInstance = serverOrHttpInstance; // Store McpServer for stdio shutdown logger.debug("Stored McpServer instance for stdio transport.", startupContext); } else if (transportType === 'http' && serverOrHttpInstance instanceof http.Server) { httpServerInstance = serverOrHttpInstance; // Store http.Server for http shutdown logger.debug("Stored http.Server instance for http transport.", startupContext); } else { // This case should ideally not happen if initializeAndStartServer works correctly logger.warning("initializeAndStartServer did not return the expected instance type.", { ...startupContext, instanceType: typeof serverOrHttpInstance }); } // If initializeAndStartServer failed internally, it would have thrown an error, // and execution would jump to the outer catch block. logger.info(`${config.mcpServerName} is running with ${transportType} transport`, { ...startupContext, startTime: new Date().toISOString(), }); // --- Signal and Error Handling Setup --- // Handle process signals for graceful shutdown process.on("SIGTERM", () => shutdown("SIGTERM")); process.on("SIGINT", () => shutdown("SIGINT")); // Handle uncaught exceptions process.on("uncaughtException", async (error) => { const errorContext = { ...startupContext, // Include base context for correlation event: 'uncaughtException', error: error instanceof Error ? error.message : String(error), stack: error instanceof Error ? error.stack : undefined }; logger.error("Uncaught exception detected. Initiating shutdown...", errorContext); // Attempt graceful shutdown; shutdown() handles its own errors. await shutdown("uncaughtException"); // If shutdown fails internally, it will call process.exit(1). // If shutdown succeeds, it calls process.exit(0). // If shutdown itself throws unexpectedly *before* exiting, this process might terminate abruptly, // but the core shutdown logic is handled within shutdown(). }); // Handle unhandled promise rejections process.on("unhandledRejection", async (reason) => { const rejectionContext = { ...startupContext, // Include base context for correlation event: 'unhandledRejection', reason: reason instanceof Error ? reason.message : String(reason), stack: reason instanceof Error ? reason.stack : undefined }; logger.error("Unhandled promise rejection detected. Initiating shutdown...", rejectionContext); // Attempt graceful shutdown; shutdown() handles its own errors. await shutdown("unhandledRejection"); // Similar logic as uncaughtException: shutdown handles its exit codes. }); } catch (error) { // Handle critical startup errors (already logged by ErrorHandler or caught above) // Log the final failure context, including error details, before exiting logger.error("Critical error during startup, exiting.", { ...startupContext, finalErrorContext: 'Startup Failure', error: error instanceof Error ? error.message : String(error), stack: error instanceof Error ? error.stack : undefined, }); process.exit(1); } }; // Start the application start();