@smithery/sdk
Version:
SDK to develop with Smithery
111 lines (110 loc) • 4.3 kB
JavaScript
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 };
}