UNPKG

@smithery/sdk

Version:

SDK to develop with Smithery

111 lines (110 loc) 4.3 kB
import { randomUUID } from "node:crypto"; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js"; import express from "express"; import { parseExpressRequestConfig } from "../shared/config.js"; import { createLRUStore } from "./session.js"; /** * Creates a stateful server for handling MCP requests. * For every new session, we invoke createMcpServer to create a new instance of the server. * @param createMcpServer Function to create an MCP server * @returns Express app */ export function createStatefulServer(createMcpServer, options) { const app = express(); app.use(express.json()); const sessionStore = options?.sessionStore ?? createLRUStore(); // Handle POST requests for client-to-server communication app.post("/mcp", async (req, res) => { // Check for existing session ID const sessionId = req.headers["mcp-session-id"]; let transport; if (sessionId && sessionStore.get(sessionId)) { // Reuse existing transport // biome-ignore lint/style/noNonNullAssertion: Not possible transport = sessionStore.get(sessionId); } else if (!sessionId && isInitializeRequest(req.body)) { // New initialization request const newSessionId = randomUUID(); transport = new StreamableHTTPServerTransport({ sessionIdGenerator: () => newSessionId, onsessioninitialized: (sessionId) => { // Store the transport by session ID sessionStore.set(sessionId, transport); }, }); // Clean up transport when closed transport.onclose = () => { if (transport.sessionId) { sessionStore.delete?.(transport.sessionId); } }; let config; try { config = parseExpressRequestConfig(req); } catch (error) { res.status(400).json({ jsonrpc: "2.0", error: { code: -32000, message: "Bad Request: Invalid configuration", }, id: null, }); return; } try { const server = createMcpServer({ sessionId: newSessionId, config: config, }); // Connect to the MCP server await server.connect(transport); } catch (error) { console.error("Error initializing server:", error); res.status(500).json({ jsonrpc: "2.0", error: { code: -32603, message: "Error initializing server.", }, id: null, }); return; } } else { // Invalid request res.status(400).json({ jsonrpc: "2.0", error: { code: -32000, message: "Bad Request: No valid session ID provided. Session may have expired.", }, id: null, }); return; } // Handle the request await transport.handleRequest(req, res, req.body); }); // Reusable handler for GET and DELETE requests const handleSessionRequest = async (req, res) => { const sessionId = req.headers["mcp-session-id"]; if (!sessionId || !sessionStore.get(sessionId)) { res.status(400).send("Invalid or expired session ID"); return; } // biome-ignore lint/style/noNonNullAssertion: Not possible const transport = sessionStore.get(sessionId); await transport.handleRequest(req, res); }; // Handle GET requests for server-to-client notifications via SSE app.get("/mcp", handleSessionRequest); // Handle DELETE requests for session termination app.delete("/mcp", handleSessionRequest); return { app }; }