import express from "express";
import cors from "cors";
import { randomUUID } from "node:crypto";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
import { registerTools } from "./tools/index.js";
import type { Request, Response } from "express";
// Create and configure a new MCP server instance
function createServer() {
const server = new McpServer({
name: "blacklotus_mcp",
version: "0.1.0",
});
registerTools(server);
return server;
}
const app = express();
app.use(express.json());
// CORS for browser-based clients. Adjust origin for production.
app.use(
cors({
origin: "*",
exposedHeaders: ["Mcp-Session-Id"],
allowedHeaders: ["Content-Type", "mcp-session-id"],
})
);
// Keep track of transports by session ID
const transports: Record<string, StreamableHTTPServerTransport> = {};
// Health endpoint for readiness/liveness checks
app.get("/health", (_req: Request, res: Response) => {
res.status(200).json({ status: "ok" });
});
// Handle POST requests for client-to-server communication
app.post("/mcp", async (req: Request, res: Response) => {
const sessionId = req.headers["mcp-session-id"] as string | undefined;
let transport: StreamableHTTPServerTransport | undefined;
if (sessionId && transports[sessionId]) {
// Reuse existing transport for this session
transport = transports[sessionId];
} else if (!sessionId && isInitializeRequest(req.body)) {
// New initialization request => create a transport and server
transport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => randomUUID(),
onsessioninitialized: (newSessionId: string) => {
transports[newSessionId] = transport as StreamableHTTPServerTransport;
},
// If running locally, consider enabling DNS rebinding protection
// enableDnsRebindingProtection: true,
// allowedHosts: ["127.0.0.1"],
});
const server = createServer();
// Clean up when closed
transport.onclose = () => {
if (transport?.sessionId) {
delete transports[transport.sessionId];
}
server.close().catch(() => void 0);
};
await server.connect(transport);
} else {
// Not an initialize request and no known session
res.status(400).json({
jsonrpc: "2.0",
error: {
code: -32000,
message: "Bad Request: No valid session ID provided",
},
id: null,
});
return;
}
// Delegate handling to the transport (body is required on POST)
try {
await transport.handleRequest(req, res, req.body);
} catch (err) {
console.error("Error handling MCP POST request:", err);
if (!res.headersSent) {
res.status(500).json({
jsonrpc: "2.0",
error: { code: -32603, message: "Internal server error" },
id: null,
});
}
}
});
// Reusable handler for GET (SSE) and DELETE (end session) requests
const handleSessionRequest = async (req: Request, res: Response) => {
const sessionId = req.headers["mcp-session-id"] as string | undefined;
if (!sessionId || !transports[sessionId]) {
res.status(400).send("Invalid or missing session ID");
return;
}
try {
await transports[sessionId].handleRequest(req, res);
} catch (err) {
console.error("Error handling MCP request:", err);
if (!res.headersSent) {
res.status(500).json({
jsonrpc: "2.0",
error: { code: -32603, message: "Internal server error" },
id: null,
});
}
}
};
// GET => server-to-client notifications via SSE
app.get("/mcp", handleSessionRequest);
// DELETE => session termination
app.delete("/mcp", handleSessionRequest);
const PORT = Number(process.env.PORT || 3000);
app.listen(PORT, () => {
console.log(`MCP server listening on http://localhost:${PORT}`);
});