mcp.server.tsā¢7.05 kB
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import express, { Express, RequestHandler } from "express";
import { Server } from "http";
import { resolve } from "path";
export interface McpServerOptions {
appName?: string;
}
export class McpServerConfig {
private transportMap = new Map<string, SSEServerTransport>();
private app: Express;
constructor() {
this.app = express();
this.setupErrorHandlers();
this.setupRequestLogging();
}
private setupErrorHandlers(): void {
process.on("uncaughtException", (error) => {
console.error("Uncaught Exception:", error.message);
console.error(error.stack);
setTimeout(() => process.exit(1), 1000);
});
process.on("unhandledRejection", (reason) => {
console.error("Unhandled Rejection:", reason);
});
}
private setupRequestLogging(): void {
this.app.use((req, res, next) => {
const start = Date.now();
res.on("finish", () => {
const duration = Date.now() - start;
console.log(
`${req.method} ${req.path} - ${res.statusCode} (${duration}ms)`
);
});
next();
});
}
private createSseHandler(serverPath: string): RequestHandler {
return async (req, res) => {
let createServer;
try {
const callerDir = process.cwd();
const absoluteServerPath = resolve(callerDir, serverPath);
console.log("Loading server configuration from:", absoluteServerPath);
const serverModule = await import(absoluteServerPath);
createServer = serverModule.default;
} catch (error) {
console.error("Failed to import server file:", serverPath);
console.error(
"Error:",
error instanceof Error ? error.message : "Unknown error"
);
res.status(500).json({ error: "Failed to load server configuration" });
return;
}
const transport = new SSEServerTransport("/messages", res);
const server = createServer();
this.setupResponseHeaders(res);
const heartbeatInterval = this.setupHeartbeat(res, transport.sessionId);
try {
this.transportMap.set(transport.sessionId, transport);
res.on("close", async () => {
console.log("SSE connection closed:", transport.sessionId);
clearInterval(heartbeatInterval);
await server.close();
this.transportMap.delete(transport.sessionId);
});
await server.connect(transport);
console.log(
"SSE connection established successfully:",
transport.sessionId
);
} catch (error) {
clearInterval(heartbeatInterval);
console.error(
"Failed to establish SSE connection:",
transport.sessionId
);
console.error(
"Error:",
error instanceof Error ? error.message : "Unknown error"
);
this.transportMap.delete(transport.sessionId);
res.status(500).end();
}
};
}
private setupResponseHeaders(res: express.Response): void {
res.setHeader("X-Accel-Buffering", "no");
res.setHeader("Cache-Control", "no-cache");
res.setHeader("Connection", "keep-alive");
}
private setupHeartbeat(
res: express.Response,
sessionId: string
): NodeJS.Timeout {
return setInterval(() => {
try {
res.write(": heartbeat\n\n");
} catch (error) {
console.error(
"Error sending heartbeat:",
error instanceof Error ? error.message : "Unknown error"
);
}
}, 30000);
}
private createMessageHandler(): RequestHandler {
return async (req, res) => {
const sessionId = req.query.sessionId as string;
res.setHeader("X-Accel-Buffering", "no");
if (!sessionId) {
console.error("Message received without sessionId");
res.status(400).json({ error: "sessionId is required" });
return;
}
const transport = this.transportMap.get(sessionId);
if (!transport) {
console.error("No active transport found for session:", sessionId);
res
.status(404)
.json({ error: "No active connection found for this session" });
return;
}
try {
await transport.handlePostMessage(req, res);
} catch (error) {
console.error("Error handling message for session:", sessionId);
console.error(
"Error:",
error instanceof Error ? error.message : "Unknown error"
);
res.status(500).json({ error: "Internal server error" });
}
};
}
private setupShutdownHandlers(httpServer: Server): void {
process.on("SIGTERM", () => {
console.log("SIGTERM received, shutting down gracefully");
httpServer.close(() => {
console.log("Server closed");
process.exit(0);
});
setTimeout(() => {
console.error("Could not close server gracefully, forcing shutdown");
process.exit(1);
}, 10000);
});
process.on("SIGINT", () => {
console.log("SIGINT received, shutting down gracefully");
httpServer.close(() => {
console.log("Server closed");
process.exit(0);
});
setTimeout(() => {
console.error("Could not close server gracefully, forcing shutdown");
process.exit(1);
}, 10000);
});
}
public async start(
serverPath: string,
options: McpServerOptions = {}
): Promise<Server> {
try {
// Configure routes
this.app.get("/sse", this.createSseHandler(serverPath));
this.app.post("/messages", this.createMessageHandler());
const port = process.env.PORT || 3001;
const httpServer = this.app.listen(port, () => {
console.log(`š Server started on port ${port}`);
console.log(`š” MCP SSE endpoint: http://localhost:${port}/sse`);
console.log(`š§ Messages endpoint: http://localhost:${port}/messages`);
});
// Expose the Express app on the HTTP server for external access
(httpServer as any).app = this.app;
httpServer.on("error", (error: Error) => {
console.error("Server startup error:", error.message);
console.error(error.stack);
if ((error as any).code === "EADDRINUSE") {
console.error("Port is already in use, exiting process");
process.exit(1);
}
});
this.setupShutdownHandlers(httpServer);
return httpServer;
} catch (error) {
console.error(
"Failed to start server:",
error instanceof Error ? error.message : "Unknown error"
);
console.error(error instanceof Error ? error.stack : undefined);
throw error;
}
}
}
// Factory function to create and start the server
export async function startServer(
serverPath: string = "./server.js",
options: McpServerOptions = {}
): Promise<Server> {
const mcpServer = new McpServerConfig();
return mcpServer.start(serverPath, options);
}