@cyanheads/git-mcp-server
Version:
An MCP (Model Context Protocol) server enabling LLMs and AI agents to interact with Git repositories. Provides tools for comprehensive Git operations including clone, commit, branch, diff, log, status, push, pull, merge, rebase, worktree, tag management,
200 lines • 9.36 kB
JavaScript
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();
//# sourceMappingURL=index.js.map