Skip to content

Commit fc3b224

Browse files
committed
Simplify to stateless Streamable HTTP - no session management
1 parent 443db85 commit fc3b224

File tree

1 file changed

+34
-70
lines changed

1 file changed

+34
-70
lines changed

src/mcp.ts

Lines changed: 34 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -574,96 +574,60 @@ export async function startMcpServer(
574574
const app = express();
575575
app.use(express.json());
576576

577-
// Store active sessions
578-
const sessions = new Map<
579-
string,
580-
{ transport: StreamableHTTPServerTransport; server: McpServer }
581-
>();
582-
583577
// ─── Health check endpoint ────────────────────────────────────────
584578
app.get("/health", (_req: Request, res: Response) => {
585579
res.json({
586580
status: "ok",
587581
whatsapp_connected: !!(sock && sock.user),
588-
whatsapp_user: sock?.user?.name ?? null,
589-
active_sessions: sessions.size,
590582
timestamp: new Date().toISOString(),
591583
});
592584
});
593585

594-
// ─── Streamable HTTP: POST /sse — JSON-RPC messages ──────────────
586+
// ─── Streamable HTTP: POST /sse — stateless mode ─────────────────
595587
app.post("/sse", async (req: Request, res: Response) => {
596-
const sessionId = req.headers["mcp-session-id"] as string | undefined;
597-
598-
// Existing session
599-
if (sessionId && sessions.has(sessionId)) {
600-
const session = sessions.get(sessionId)!;
601-
await session.transport.handleRequest(req, res, req.body);
602-
return;
603-
}
604-
605-
// New session (initialize request)
606-
const isInit =
607-
req.body?.method === "initialize" ||
608-
(Array.isArray(req.body) &&
609-
req.body.some((msg: any) => msg.method === "initialize"));
610-
611-
if (!isInit && sessionId) {
612-
res.status(404).json({ error: "Session not found" });
613-
return;
614-
}
588+
mcpLogger.info(`POST /sse from ${req.ip}`);
615589

616-
mcpLogger.info(`New Streamable HTTP session from ${req.ip}`);
590+
try {
591+
const transport = new StreamableHTTPServerTransport({
592+
sessionIdGenerator: undefined, // stateless
593+
});
617594

618-
const transport = new StreamableHTTPServerTransport({
619-
sessionIdGenerator: () => randomUUID(),
620-
});
595+
const server = createMcpServer(sock, mcpLogger, waLogger);
621596

622-
const server = createMcpServer(sock, mcpLogger, waLogger);
597+
res.on("close", () => {
598+
transport.close();
599+
server.close();
600+
});
623601

624-
transport.onclose = () => {
625-
const sid = transport.sessionId;
626-
if (sid) {
627-
mcpLogger.info(`Session closed: ${sid}`);
628-
sessions.delete(sid);
602+
await server.connect(transport);
603+
await transport.handleRequest(req, res, req.body);
604+
} catch (error: any) {
605+
mcpLogger.error(`Error handling POST /sse: ${error.message}`);
606+
if (!res.headersSent) {
607+
res.status(500).json({
608+
jsonrpc: "2.0",
609+
error: { code: -32603, message: "Internal server error" },
610+
id: null,
611+
});
629612
}
630-
};
631-
632-
await server.connect(transport);
633-
634-
const sid = transport.sessionId;
635-
if (sid) {
636-
sessions.set(sid, { transport, server });
637-
mcpLogger.info(`Session created: ${sid}`);
638613
}
639-
640-
await transport.handleRequest(req, res, req.body);
641614
});
642615

643-
// ─── Streamable HTTP: GET /sse — SSE stream for server notifications
644-
app.get("/sse", async (req: Request, res: Response) => {
645-
const sessionId = req.headers["mcp-session-id"] as string | undefined;
646-
647-
if (sessionId && sessions.has(sessionId)) {
648-
const session = sessions.get(sessionId)!;
649-
await session.transport.handleRequest(req, res);
650-
} else {
651-
res.status(400).json({
652-
error: "No valid session. Send an initialize request first.",
653-
});
654-
}
616+
// ─── GET & DELETE not supported in stateless mode ─────────────────
617+
app.get("/sse", (_req: Request, res: Response) => {
618+
res.status(405).set("Allow", "POST").json({
619+
jsonrpc: "2.0",
620+
error: { code: -32000, message: "Method not allowed in stateless mode. Use POST." },
621+
id: null,
622+
});
655623
});
656624

657-
// ─── Streamable HTTP: DELETE /sse — session cleanup ───────────────
658-
app.delete("/sse", async (req: Request, res: Response) => {
659-
const sessionId = req.headers["mcp-session-id"] as string | undefined;
660-
661-
if (sessionId && sessions.has(sessionId)) {
662-
const session = sessions.get(sessionId)!;
663-
await session.transport.handleRequest(req, res);
664-
} else {
665-
res.status(404).json({ error: "Session not found" });
666-
}
625+
app.delete("/sse", (_req: Request, res: Response) => {
626+
res.status(405).set("Allow", "POST").json({
627+
jsonrpc: "2.0",
628+
error: { code: -32000, message: "Method not allowed in stateless mode." },
629+
id: null,
630+
});
667631
});
668632

669633
// ─── Start the HTTP server ────────────────────────────────────────

0 commit comments

Comments
 (0)