From 7b090cae4b18ce815a45cb556f4c74213c5c1341 Mon Sep 17 00:00:00 2001 From: Olivier Chafik Date: Thu, 11 Dec 2025 18:24:24 +0100 Subject: [PATCH 01/12] feat(examples): add SSE transport support to all example servers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add backwards-compatible SSE transport support alongside the current Streamable HTTP transport. Each server now exposes: - /mcp (GET, POST, DELETE) - Streamable HTTP transport (current spec) - /sse (GET) - Legacy SSE transport stream endpoint - /messages (POST) - Legacy SSE transport message endpoint This enables older clients using the deprecated HTTP+SSE protocol (version 2024-11-05) to connect to the example servers. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- examples/basic-server-react/server.ts | 24 ++++++++++++++++++- examples/basic-server-vanillajs/server.ts | 24 ++++++++++++++++++- examples/budget-allocator-server/server.ts | 24 ++++++++++++++++++- examples/cohort-heatmap-server/server.ts | 24 ++++++++++++++++++- .../customer-segmentation-server/server.ts | 24 ++++++++++++++++++- examples/scenario-modeler-server/server.ts | 24 ++++++++++++++++++- examples/system-monitor-server/server.ts | 24 ++++++++++++++++++- examples/threejs-server/server.ts | 24 ++++++++++++++++++- examples/wiki-explorer-server/server.ts | 24 ++++++++++++++++++- 9 files changed, 207 insertions(+), 9 deletions(-) diff --git a/examples/basic-server-react/server.ts b/examples/basic-server-react/server.ts index ac2cffe3..7f851add 100644 --- a/examples/basic-server-react/server.ts +++ b/examples/basic-server-react/server.ts @@ -1,4 +1,5 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import type { CallToolResult, ReadResourceResult } from "@modelcontextprotocol/sdk/types.js"; import cors from "cors"; @@ -62,7 +63,8 @@ const app = express(); app.use(cors()); app.use(express.json()); -app.post("/mcp", async (req: Request, res: Response) => { +// Streamable HTTP transport (current spec) - handles GET, POST, DELETE +app.all("/mcp", async (req: Request, res: Response) => { try { const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, @@ -85,6 +87,26 @@ app.post("/mcp", async (req: Request, res: Response) => { } }); +// Legacy SSE transport (deprecated) - for backwards compatibility +const sseTransports = new Map(); + +app.get("/sse", async (_req: Request, res: Response) => { + const transport = new SSEServerTransport("/messages", res); + sseTransports.set(transport.sessionId, transport); + res.on("close", () => { sseTransports.delete(transport.sessionId); }); + await server.connect(transport); +}); + +app.post("/messages", async (req: Request, res: Response) => { + const sessionId = req.query.sessionId as string; + const transport = sseTransports.get(sessionId); + if (!transport) { + res.status(404).json({ error: "Session not found" }); + return; + } + await transport.handlePostMessage(req, res, req.body); +}); + const httpServer = app.listen(PORT, (err) => { if (err) { console.error("Error starting server:", err); diff --git a/examples/basic-server-vanillajs/server.ts b/examples/basic-server-vanillajs/server.ts index 993d35e8..2055b935 100644 --- a/examples/basic-server-vanillajs/server.ts +++ b/examples/basic-server-vanillajs/server.ts @@ -1,4 +1,5 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import type { CallToolResult, ReadResourceResult } from "@modelcontextprotocol/sdk/types.js"; import cors from "cors"; @@ -62,7 +63,8 @@ const app = express(); app.use(cors()); app.use(express.json()); -app.post("/mcp", async (req: Request, res: Response) => { +// Streamable HTTP transport (current spec) - handles GET, POST, DELETE +app.all("/mcp", async (req: Request, res: Response) => { try { const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, @@ -85,6 +87,26 @@ app.post("/mcp", async (req: Request, res: Response) => { } }); +// Legacy SSE transport (deprecated) - for backwards compatibility +const sseTransports = new Map(); + +app.get("/sse", async (_req: Request, res: Response) => { + const transport = new SSEServerTransport("/messages", res); + sseTransports.set(transport.sessionId, transport); + res.on("close", () => { sseTransports.delete(transport.sessionId); }); + await server.connect(transport); +}); + +app.post("/messages", async (req: Request, res: Response) => { + const sessionId = req.query.sessionId as string; + const transport = sseTransports.get(sessionId); + if (!transport) { + res.status(404).json({ error: "Session not found" }); + return; + } + await transport.handlePostMessage(req, res, req.body); +}); + const httpServer = app.listen(PORT, (err) => { if (err) { console.error("Error starting server:", err); diff --git a/examples/budget-allocator-server/server.ts b/examples/budget-allocator-server/server.ts index daf147de..368725a4 100755 --- a/examples/budget-allocator-server/server.ts +++ b/examples/budget-allocator-server/server.ts @@ -5,6 +5,7 @@ * and industry benchmarks by company stage. */ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import type { @@ -306,7 +307,8 @@ async function main() { app.use(cors()); app.use(express.json()); - app.post("/mcp", async (req: Request, res: Response) => { + // Streamable HTTP transport (current spec) - handles GET, POST, DELETE + app.all("/mcp", async (req: Request, res: Response) => { try { const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, @@ -330,6 +332,26 @@ async function main() { } }); + // Legacy SSE transport (deprecated) - for backwards compatibility + const sseTransports = new Map(); + + app.get("/sse", async (_req: Request, res: Response) => { + const transport = new SSEServerTransport("/messages", res); + sseTransports.set(transport.sessionId, transport); + res.on("close", () => { sseTransports.delete(transport.sessionId); }); + await server.connect(transport); + }); + + app.post("/messages", async (req: Request, res: Response) => { + const sessionId = req.query.sessionId as string; + const transport = sseTransports.get(sessionId); + if (!transport) { + res.status(404).json({ error: "Session not found" }); + return; + } + await transport.handlePostMessage(req, res, req.body); + }); + const httpServer = app.listen(PORT, (err) => { if (err) { console.error("Error starting server:", err); diff --git a/examples/cohort-heatmap-server/server.ts b/examples/cohort-heatmap-server/server.ts index 63a93f22..b5c14fe8 100644 --- a/examples/cohort-heatmap-server/server.ts +++ b/examples/cohort-heatmap-server/server.ts @@ -1,4 +1,5 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import type { ReadResourceResult } from "@modelcontextprotocol/sdk/types.js"; @@ -215,7 +216,8 @@ async function main() { app.use(cors()); app.use(express.json()); - app.post("/mcp", async (req: Request, res: Response) => { + // Streamable HTTP transport (current spec) - handles GET, POST, DELETE + app.all("/mcp", async (req: Request, res: Response) => { try { const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, @@ -240,6 +242,26 @@ async function main() { } }); + // Legacy SSE transport (deprecated) - for backwards compatibility + const sseTransports = new Map(); + + app.get("/sse", async (_req: Request, res: Response) => { + const transport = new SSEServerTransport("/messages", res); + sseTransports.set(transport.sessionId, transport); + res.on("close", () => { sseTransports.delete(transport.sessionId); }); + await server.connect(transport); + }); + + app.post("/messages", async (req: Request, res: Response) => { + const sessionId = req.query.sessionId as string; + const transport = sseTransports.get(sessionId); + if (!transport) { + res.status(404).json({ error: "Session not found" }); + return; + } + await transport.handlePostMessage(req, res, req.body); + }); + const httpServer = app.listen(PORT, (err) => { if (err) { console.error("Error starting server:", err); diff --git a/examples/customer-segmentation-server/server.ts b/examples/customer-segmentation-server/server.ts index bc95c7e6..9815ed6b 100644 --- a/examples/customer-segmentation-server/server.ts +++ b/examples/customer-segmentation-server/server.ts @@ -1,4 +1,5 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import type { @@ -114,7 +115,8 @@ async function main() { app.use(cors()); app.use(express.json()); - app.post("/mcp", async (req: Request, res: Response) => { + // Streamable HTTP transport (current spec) - handles GET, POST, DELETE + app.all("/mcp", async (req: Request, res: Response) => { try { const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, @@ -139,6 +141,26 @@ async function main() { } }); + // Legacy SSE transport (deprecated) - for backwards compatibility + const sseTransports = new Map(); + + app.get("/sse", async (_req: Request, res: Response) => { + const transport = new SSEServerTransport("/messages", res); + sseTransports.set(transport.sessionId, transport); + res.on("close", () => { sseTransports.delete(transport.sessionId); }); + await server.connect(transport); + }); + + app.post("/messages", async (req: Request, res: Response) => { + const sessionId = req.query.sessionId as string; + const transport = sseTransports.get(sessionId); + if (!transport) { + res.status(404).json({ error: "Session not found" }); + return; + } + await transport.handlePostMessage(req, res, req.body); + }); + const httpServer = app.listen(PORT, (err) => { if (err) { console.error("Error starting server:", err); diff --git a/examples/scenario-modeler-server/server.ts b/examples/scenario-modeler-server/server.ts index 4a9ef48f..faf747ec 100644 --- a/examples/scenario-modeler-server/server.ts +++ b/examples/scenario-modeler-server/server.ts @@ -1,4 +1,5 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import type { @@ -322,7 +323,8 @@ async function main() { app.use(cors()); app.use(express.json()); - app.post("/mcp", async (req: Request, res: Response) => { + // Streamable HTTP transport (current spec) - handles GET, POST, DELETE + app.all("/mcp", async (req: Request, res: Response) => { try { const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, @@ -346,6 +348,26 @@ async function main() { } }); + // Legacy SSE transport (deprecated) - for backwards compatibility + const sseTransports = new Map(); + + app.get("/sse", async (_req: Request, res: Response) => { + const transport = new SSEServerTransport("/messages", res); + sseTransports.set(transport.sessionId, transport); + res.on("close", () => { sseTransports.delete(transport.sessionId); }); + await server.connect(transport); + }); + + app.post("/messages", async (req: Request, res: Response) => { + const sessionId = req.query.sessionId as string; + const transport = sseTransports.get(sessionId); + if (!transport) { + res.status(404).json({ error: "Session not found" }); + return; + } + await transport.handlePostMessage(req, res, req.body); + }); + const httpServer = app.listen(PORT, (err) => { if (err) { console.error("Error starting server:", err); diff --git a/examples/system-monitor-server/server.ts b/examples/system-monitor-server/server.ts index 9fca3719..ddde7e75 100644 --- a/examples/system-monitor-server/server.ts +++ b/examples/system-monitor-server/server.ts @@ -1,4 +1,5 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import type { @@ -186,7 +187,8 @@ async function main() { app.use(cors()); app.use(express.json()); - app.post("/mcp", async (req: Request, res: Response) => { + // Streamable HTTP transport (current spec) - handles GET, POST, DELETE + app.all("/mcp", async (req: Request, res: Response) => { try { const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, @@ -211,6 +213,26 @@ async function main() { } }); + // Legacy SSE transport (deprecated) - for backwards compatibility + const sseTransports = new Map(); + + app.get("/sse", async (_req: Request, res: Response) => { + const transport = new SSEServerTransport("/messages", res); + sseTransports.set(transport.sessionId, transport); + res.on("close", () => { sseTransports.delete(transport.sessionId); }); + await server.connect(transport); + }); + + app.post("/messages", async (req: Request, res: Response) => { + const sessionId = req.query.sessionId as string; + const transport = sseTransports.get(sessionId); + if (!transport) { + res.status(404).json({ error: "Session not found" }); + return; + } + await transport.handlePostMessage(req, res, req.body); + }); + const httpServer = app.listen(PORT, (err) => { if (err) { console.error("Error starting server:", err); diff --git a/examples/threejs-server/server.ts b/examples/threejs-server/server.ts index 81d860c8..320812b6 100644 --- a/examples/threejs-server/server.ts +++ b/examples/threejs-server/server.ts @@ -4,6 +4,7 @@ * Provides tools for rendering interactive 3D scenes using Three.js. */ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import type { ReadResourceResult } from "@modelcontextprotocol/sdk/types.js"; @@ -195,7 +196,8 @@ async function main() { app.use(cors()); app.use(express.json()); - app.post("/mcp", async (req: Request, res: Response) => { + // Streamable HTTP transport (current spec) - handles GET, POST, DELETE + app.all("/mcp", async (req: Request, res: Response) => { try { const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, @@ -220,6 +222,26 @@ async function main() { } }); + // Legacy SSE transport (deprecated) - for backwards compatibility + const sseTransports = new Map(); + + app.get("/sse", async (_req: Request, res: Response) => { + const transport = new SSEServerTransport("/messages", res); + sseTransports.set(transport.sessionId, transport); + res.on("close", () => { sseTransports.delete(transport.sessionId); }); + await server.connect(transport); + }); + + app.post("/messages", async (req: Request, res: Response) => { + const sessionId = req.query.sessionId as string; + const transport = sseTransports.get(sessionId); + if (!transport) { + res.status(404).json({ error: "Session not found" }); + return; + } + await transport.handlePostMessage(req, res, req.body); + }); + const httpServer = app.listen(PORT, () => { console.log(`Three.js Server listening on http://localhost:${PORT}/mcp`); }); diff --git a/examples/wiki-explorer-server/server.ts b/examples/wiki-explorer-server/server.ts index 79d17721..ac0f291f 100644 --- a/examples/wiki-explorer-server/server.ts +++ b/examples/wiki-explorer-server/server.ts @@ -1,4 +1,5 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import type { @@ -152,7 +153,8 @@ async function main() { app.use(cors()); app.use(express.json()); - app.post("/mcp", async (req: Request, res: Response) => { + // Streamable HTTP transport (current spec) - handles GET, POST, DELETE + app.all("/mcp", async (req: Request, res: Response) => { try { const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, @@ -177,6 +179,26 @@ async function main() { } }); + // Legacy SSE transport (deprecated) - for backwards compatibility + const sseTransports = new Map(); + + app.get("/sse", async (_req: Request, res: Response) => { + const transport = new SSEServerTransport("/messages", res); + sseTransports.set(transport.sessionId, transport); + res.on("close", () => { sseTransports.delete(transport.sessionId); }); + await server.connect(transport); + }); + + app.post("/messages", async (req: Request, res: Response) => { + const sessionId = req.query.sessionId as string; + const transport = sseTransports.get(sessionId); + if (!transport) { + res.status(404).json({ error: "Session not found" }); + return; + } + await transport.handlePostMessage(req, res, req.body); + }); + const httpServer = app.listen(PORT, () => { console.log( `Wiki Explorer server listening on http://localhost:${PORT}/mcp`, From 6b404214f67154558d142da668c2c16749c88fa3 Mon Sep 17 00:00:00 2001 From: Olivier Chafik Date: Thu, 11 Dec 2025 18:28:13 +0100 Subject: [PATCH 02/12] refactor(examples): migrate 6 servers to use shared server utility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refactored the following example servers to use the shared startServer utility from examples/shared/server-utils.ts: - threejs-server - system-monitor-server - cohort-heatmap-server - budget-allocator-server - customer-segmentation-server - scenario-modeler-server Changes for each server: - Removed direct imports: SSEServerTransport, StdioServerTransport, StreamableHTTPServerTransport, cors, express - Removed PORT constant (now handled by shared utility) - Added import for startServer from ../shared/server-utils.js - Replaced entire async main() function with single startServer() call - Preserved all business logic (tool registration, resource registration, helper functions) This reduces code duplication by ~700 lines and ensures consistent transport handling across all example servers. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- examples/budget-allocator-server/server.ts | 87 +----------------- examples/cohort-heatmap-server/server.ts | 88 +----------------- .../customer-segmentation-server/server.ts | 88 +----------------- examples/scenario-modeler-server/server.ts | 87 +----------------- examples/system-monitor-server/server.ts | 89 +------------------ examples/threejs-server/server.ts | 82 +---------------- 6 files changed, 12 insertions(+), 509 deletions(-) diff --git a/examples/budget-allocator-server/server.ts b/examples/budget-allocator-server/server.ts index 368725a4..d1224b7e 100755 --- a/examples/budget-allocator-server/server.ts +++ b/examples/budget-allocator-server/server.ts @@ -5,21 +5,16 @@ * and industry benchmarks by company stage. */ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import type { CallToolResult, ReadResourceResult, } from "@modelcontextprotocol/sdk/types.js"; -import cors from "cors"; -import express, { type Request, type Response } from "express"; import fs from "node:fs/promises"; import path from "node:path"; import { z } from "zod"; import { RESOURCE_MIME_TYPE, RESOURCE_URI_META_KEY } from "../../dist/src/app"; +import { startServer } from "../shared/server-utils.js"; -const PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : 3001; const DIST_DIR = path.join(import.meta.dirname, "dist"); // --------------------------------------------------------------------------- @@ -297,82 +292,4 @@ server.registerResource( // Server Startup // --------------------------------------------------------------------------- -async function main() { - if (process.argv.includes("--stdio")) { - const transport = new StdioServerTransport(); - await server.connect(transport); - console.error("Budget Allocator Server running in stdio mode"); - } else { - const app = express(); - app.use(cors()); - app.use(express.json()); - - // Streamable HTTP transport (current spec) - handles GET, POST, DELETE - app.all("/mcp", async (req: Request, res: Response) => { - try { - const transport = new StreamableHTTPServerTransport({ - sessionIdGenerator: undefined, - enableJsonResponse: true, - }); - res.on("close", () => { - transport.close(); - }); - - await server.connect(transport); - await transport.handleRequest(req, res, req.body); - } catch (error) { - console.error("Error handling MCP request:", error); - if (!res.headersSent) { - res.status(500).json({ - jsonrpc: "2.0", - error: { code: -32603, message: "Internal server error" }, - id: null, - }); - } - } - }); - - // Legacy SSE transport (deprecated) - for backwards compatibility - const sseTransports = new Map(); - - app.get("/sse", async (_req: Request, res: Response) => { - const transport = new SSEServerTransport("/messages", res); - sseTransports.set(transport.sessionId, transport); - res.on("close", () => { sseTransports.delete(transport.sessionId); }); - await server.connect(transport); - }); - - app.post("/messages", async (req: Request, res: Response) => { - const sessionId = req.query.sessionId as string; - const transport = sseTransports.get(sessionId); - if (!transport) { - res.status(404).json({ error: "Session not found" }); - return; - } - await transport.handlePostMessage(req, res, req.body); - }); - - const httpServer = app.listen(PORT, (err) => { - if (err) { - console.error("Error starting server:", err); - process.exit(1); - } - console.log( - `Budget Allocator Server listening on http://localhost:${PORT}/mcp`, - ); - }); - - function shutdown() { - console.log("\nShutting down..."); - httpServer.close(() => { - console.log("Server closed"); - process.exit(0); - }); - } - - process.on("SIGINT", shutdown); - process.on("SIGTERM", shutdown); - } -} - -main().catch(console.error); +startServer(server, { name: "Budget Allocator Server" }); diff --git a/examples/cohort-heatmap-server/server.ts b/examples/cohort-heatmap-server/server.ts index b5c14fe8..6128c2cf 100644 --- a/examples/cohort-heatmap-server/server.ts +++ b/examples/cohort-heatmap-server/server.ts @@ -1,16 +1,11 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import type { ReadResourceResult } from "@modelcontextprotocol/sdk/types.js"; -import cors from "cors"; -import express, { type Request, type Response } from "express"; import fs from "node:fs/promises"; import path from "node:path"; import { z } from "zod"; import { RESOURCE_MIME_TYPE, RESOURCE_URI_META_KEY } from "../../dist/src/app"; +import { startServer } from "../shared/server-utils.js"; -const PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : 3001; const DIST_DIR = path.join(import.meta.dirname, "dist"); // Schemas - types are derived from these using z.infer @@ -206,83 +201,4 @@ const server = new McpServer({ ); } -async function main() { - if (process.argv.includes("--stdio")) { - const transport = new StdioServerTransport(); - await server.connect(transport); - console.error("Cohort Heatmap Server running in stdio mode"); - } else { - const app = express(); - app.use(cors()); - app.use(express.json()); - - // Streamable HTTP transport (current spec) - handles GET, POST, DELETE - app.all("/mcp", async (req: Request, res: Response) => { - try { - const transport = new StreamableHTTPServerTransport({ - sessionIdGenerator: undefined, - enableJsonResponse: true, - }); - res.on("close", () => { - transport.close(); - }); - - await server.connect(transport); - - await transport.handleRequest(req, res, req.body); - } catch (error) { - console.error("Error handling MCP request:", error); - if (!res.headersSent) { - res.status(500).json({ - jsonrpc: "2.0", - error: { code: -32603, message: "Internal server error" }, - id: null, - }); - } - } - }); - - // Legacy SSE transport (deprecated) - for backwards compatibility - const sseTransports = new Map(); - - app.get("/sse", async (_req: Request, res: Response) => { - const transport = new SSEServerTransport("/messages", res); - sseTransports.set(transport.sessionId, transport); - res.on("close", () => { sseTransports.delete(transport.sessionId); }); - await server.connect(transport); - }); - - app.post("/messages", async (req: Request, res: Response) => { - const sessionId = req.query.sessionId as string; - const transport = sseTransports.get(sessionId); - if (!transport) { - res.status(404).json({ error: "Session not found" }); - return; - } - await transport.handlePostMessage(req, res, req.body); - }); - - const httpServer = app.listen(PORT, (err) => { - if (err) { - console.error("Error starting server:", err); - process.exit(1); - } - console.log( - `Cohort Heatmap Server listening on http://localhost:${PORT}/mcp`, - ); - }); - - function shutdown() { - console.log("\nShutting down..."); - httpServer.close(() => { - console.log("Server closed"); - process.exit(0); - }); - } - - process.on("SIGINT", shutdown); - process.on("SIGTERM", shutdown); - } -} - -main().catch(console.error); +startServer(server, { name: "Cohort Heatmap Server" }); diff --git a/examples/customer-segmentation-server/server.ts b/examples/customer-segmentation-server/server.ts index 9815ed6b..9e8511d5 100644 --- a/examples/customer-segmentation-server/server.ts +++ b/examples/customer-segmentation-server/server.ts @@ -1,24 +1,19 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import type { CallToolResult, ReadResourceResult, } from "@modelcontextprotocol/sdk/types.js"; -import cors from "cors"; -import express, { type Request, type Response } from "express"; import fs from "node:fs/promises"; import path from "node:path"; import { z } from "zod"; import { RESOURCE_MIME_TYPE, RESOURCE_URI_META_KEY } from "../../dist/src/app"; +import { startServer } from "../shared/server-utils.js"; import { generateCustomers, generateSegmentSummaries, } from "./src/data-generator.ts"; import { SEGMENTS, type Customer, type SegmentSummary } from "./src/types.ts"; -const PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : 3001; const DIST_DIR = path.join(import.meta.dirname, "dist"); // Schemas - types are derived from these using z.infer @@ -105,83 +100,4 @@ const server = new McpServer({ ); } -async function main() { - if (process.argv.includes("--stdio")) { - const transport = new StdioServerTransport(); - await server.connect(transport); - console.error("Customer Segmentation Server running in stdio mode"); - } else { - const app = express(); - app.use(cors()); - app.use(express.json()); - - // Streamable HTTP transport (current spec) - handles GET, POST, DELETE - app.all("/mcp", async (req: Request, res: Response) => { - try { - const transport = new StreamableHTTPServerTransport({ - sessionIdGenerator: undefined, - enableJsonResponse: true, - }); - res.on("close", () => { - transport.close(); - }); - - await server.connect(transport); - - await transport.handleRequest(req, res, req.body); - } catch (error) { - console.error("Error handling MCP request:", error); - if (!res.headersSent) { - res.status(500).json({ - jsonrpc: "2.0", - error: { code: -32603, message: "Internal server error" }, - id: null, - }); - } - } - }); - - // Legacy SSE transport (deprecated) - for backwards compatibility - const sseTransports = new Map(); - - app.get("/sse", async (_req: Request, res: Response) => { - const transport = new SSEServerTransport("/messages", res); - sseTransports.set(transport.sessionId, transport); - res.on("close", () => { sseTransports.delete(transport.sessionId); }); - await server.connect(transport); - }); - - app.post("/messages", async (req: Request, res: Response) => { - const sessionId = req.query.sessionId as string; - const transport = sseTransports.get(sessionId); - if (!transport) { - res.status(404).json({ error: "Session not found" }); - return; - } - await transport.handlePostMessage(req, res, req.body); - }); - - const httpServer = app.listen(PORT, (err) => { - if (err) { - console.error("Error starting server:", err); - process.exit(1); - } - console.log( - `Customer Segmentation Server listening on http://localhost:${PORT}/mcp`, - ); - }); - - function shutdown() { - console.log("\nShutting down..."); - httpServer.close(() => { - console.log("Server closed"); - process.exit(0); - }); - } - - process.on("SIGINT", shutdown); - process.on("SIGTERM", shutdown); - } -} - -main().catch(console.error); +startServer(server, { name: "Customer Segmentation Server" }); diff --git a/examples/scenario-modeler-server/server.ts b/examples/scenario-modeler-server/server.ts index faf747ec..d42b9c4c 100644 --- a/examples/scenario-modeler-server/server.ts +++ b/examples/scenario-modeler-server/server.ts @@ -1,19 +1,14 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import type { CallToolResult, ReadResourceResult, } from "@modelcontextprotocol/sdk/types.js"; -import cors from "cors"; -import express, { type Request, type Response } from "express"; import fs from "node:fs/promises"; import path from "node:path"; import { z } from "zod"; import { RESOURCE_MIME_TYPE, RESOURCE_URI_META_KEY } from "../../dist/src/app"; +import { startServer } from "../shared/server-utils.js"; -const PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : 3001; const DIST_DIR = path.join(import.meta.dirname, "dist"); // ============================================================================ @@ -313,82 +308,4 @@ const server = new McpServer({ // Server Startup // ============================================================================ -async function main() { - if (process.argv.includes("--stdio")) { - const transport = new StdioServerTransport(); - await server.connect(transport); - console.error("SaaS Scenario Modeler Server running in stdio mode"); - } else { - const app = express(); - app.use(cors()); - app.use(express.json()); - - // Streamable HTTP transport (current spec) - handles GET, POST, DELETE - app.all("/mcp", async (req: Request, res: Response) => { - try { - const transport = new StreamableHTTPServerTransport({ - sessionIdGenerator: undefined, - enableJsonResponse: true, - }); - res.on("close", () => { - transport.close(); - }); - - await server.connect(transport); - await transport.handleRequest(req, res, req.body); - } catch (error) { - console.error("Error handling MCP request:", error); - if (!res.headersSent) { - res.status(500).json({ - jsonrpc: "2.0", - error: { code: -32603, message: "Internal server error" }, - id: null, - }); - } - } - }); - - // Legacy SSE transport (deprecated) - for backwards compatibility - const sseTransports = new Map(); - - app.get("/sse", async (_req: Request, res: Response) => { - const transport = new SSEServerTransport("/messages", res); - sseTransports.set(transport.sessionId, transport); - res.on("close", () => { sseTransports.delete(transport.sessionId); }); - await server.connect(transport); - }); - - app.post("/messages", async (req: Request, res: Response) => { - const sessionId = req.query.sessionId as string; - const transport = sseTransports.get(sessionId); - if (!transport) { - res.status(404).json({ error: "Session not found" }); - return; - } - await transport.handlePostMessage(req, res, req.body); - }); - - const httpServer = app.listen(PORT, (err) => { - if (err) { - console.error("Error starting server:", err); - process.exit(1); - } - console.log( - `SaaS Scenario Modeler Server listening on http://localhost:${PORT}/mcp`, - ); - }); - - function shutdown() { - console.log("\nShutting down..."); - httpServer.close(() => { - console.log("Server closed"); - process.exit(0); - }); - } - - process.on("SIGINT", shutdown); - process.on("SIGTERM", shutdown); - } -} - -main().catch(console.error); +startServer(server, { name: "SaaS Scenario Modeler Server" }); diff --git a/examples/system-monitor-server/server.ts b/examples/system-monitor-server/server.ts index ddde7e75..6e6fa808 100644 --- a/examples/system-monitor-server/server.ts +++ b/examples/system-monitor-server/server.ts @@ -1,21 +1,15 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import type { CallToolResult, ReadResourceResult, } from "@modelcontextprotocol/sdk/types.js"; -import cors from "cors"; -import express, { type Request, type Response } from "express"; import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import si from "systeminformation"; import { z } from "zod"; import { RESOURCE_MIME_TYPE, RESOURCE_URI_META_KEY } from "../../dist/src/app"; - -const PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : 3001; +import { startServer } from "../shared/server-utils.js"; // Schemas - types are derived from these using z.infer const CpuCoreSchema = z.object({ @@ -177,83 +171,4 @@ const server = new McpServer({ ); } -async function main() { - if (process.argv.includes("--stdio")) { - const transport = new StdioServerTransport(); - await server.connect(transport); - console.error("System Monitor Server running in stdio mode"); - } else { - const app = express(); - app.use(cors()); - app.use(express.json()); - - // Streamable HTTP transport (current spec) - handles GET, POST, DELETE - app.all("/mcp", async (req: Request, res: Response) => { - try { - const transport = new StreamableHTTPServerTransport({ - sessionIdGenerator: undefined, - enableJsonResponse: true, - }); - res.on("close", () => { - transport.close(); - }); - - await server.connect(transport); - - await transport.handleRequest(req, res, req.body); - } catch (error) { - console.error("Error handling MCP request:", error); - if (!res.headersSent) { - res.status(500).json({ - jsonrpc: "2.0", - error: { code: -32603, message: "Internal server error" }, - id: null, - }); - } - } - }); - - // Legacy SSE transport (deprecated) - for backwards compatibility - const sseTransports = new Map(); - - app.get("/sse", async (_req: Request, res: Response) => { - const transport = new SSEServerTransport("/messages", res); - sseTransports.set(transport.sessionId, transport); - res.on("close", () => { sseTransports.delete(transport.sessionId); }); - await server.connect(transport); - }); - - app.post("/messages", async (req: Request, res: Response) => { - const sessionId = req.query.sessionId as string; - const transport = sseTransports.get(sessionId); - if (!transport) { - res.status(404).json({ error: "Session not found" }); - return; - } - await transport.handlePostMessage(req, res, req.body); - }); - - const httpServer = app.listen(PORT, (err) => { - if (err) { - console.error("Error starting server:", err); - process.exit(1); - } - console.log( - `System Monitor Server listening on http://localhost:${PORT}/mcp`, - ); - }); - - function shutdown() { - console.log("\nShutting down..."); - httpServer.close(() => { - console.log("Server closed"); - process.exit(0); - }); - } - - process.on("SIGINT", shutdown); - process.on("SIGTERM", shutdown); - } -} - -main().catch(console.error); +startServer(server, { name: "System Monitor Server" }); diff --git a/examples/threejs-server/server.ts b/examples/threejs-server/server.ts index 320812b6..db8098a3 100644 --- a/examples/threejs-server/server.ts +++ b/examples/threejs-server/server.ts @@ -4,18 +4,13 @@ * Provides tools for rendering interactive 3D scenes using Three.js. */ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import type { ReadResourceResult } from "@modelcontextprotocol/sdk/types.js"; -import cors from "cors"; -import express, { type Request, type Response } from "express"; import fs from "node:fs/promises"; import path from "node:path"; import { z } from "zod"; import { RESOURCE_MIME_TYPE, RESOURCE_URI_META_KEY } from "../../dist/src/app"; +import { startServer } from "../shared/server-utils.js"; -const PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : 3001; const DIST_DIR = path.join(import.meta.dirname, "dist"); const THREEJS_DOCUMENTATION = `# Three.js Widget Documentation @@ -186,77 +181,4 @@ const server = new McpServer({ ); } -async function main() { - if (process.argv.includes("--stdio")) { - const transport = new StdioServerTransport(); - await server.connect(transport); - console.error("Three.js Server running in stdio mode"); - } else { - const app = express(); - app.use(cors()); - app.use(express.json()); - - // Streamable HTTP transport (current spec) - handles GET, POST, DELETE - app.all("/mcp", async (req: Request, res: Response) => { - try { - const transport = new StreamableHTTPServerTransport({ - sessionIdGenerator: undefined, - enableJsonResponse: true, - }); - res.on("close", () => { - transport.close(); - }); - - await server.connect(transport); - - await transport.handleRequest(req, res, req.body); - } catch (error) { - console.error("Error handling MCP request:", error); - if (!res.headersSent) { - res.status(500).json({ - jsonrpc: "2.0", - error: { code: -32603, message: "Internal server error" }, - id: null, - }); - } - } - }); - - // Legacy SSE transport (deprecated) - for backwards compatibility - const sseTransports = new Map(); - - app.get("/sse", async (_req: Request, res: Response) => { - const transport = new SSEServerTransport("/messages", res); - sseTransports.set(transport.sessionId, transport); - res.on("close", () => { sseTransports.delete(transport.sessionId); }); - await server.connect(transport); - }); - - app.post("/messages", async (req: Request, res: Response) => { - const sessionId = req.query.sessionId as string; - const transport = sseTransports.get(sessionId); - if (!transport) { - res.status(404).json({ error: "Session not found" }); - return; - } - await transport.handlePostMessage(req, res, req.body); - }); - - const httpServer = app.listen(PORT, () => { - console.log(`Three.js Server listening on http://localhost:${PORT}/mcp`); - }); - - function shutdown() { - console.log("\nShutting down..."); - httpServer.close(() => { - console.log("Server closed"); - process.exit(0); - }); - } - - process.on("SIGINT", shutdown); - process.on("SIGTERM", shutdown); - } -} - -main().catch(console.error); +startServer(server, { name: "Three.js Server" }); From 6d3ff410dae89ea0d65d85d0479d9af555daf55d Mon Sep 17 00:00:00 2001 From: Olivier Chafik Date: Thu, 11 Dec 2025 18:28:59 +0100 Subject: [PATCH 03/12] refactor(examples): migrate remaining servers to shared utility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Complete the migration of example servers to use the shared server-utils.ts module for transport setup: - basic-server-react - basic-server-vanillajs - wiki-explorer-server - Add shared/server-utils.ts This centralizes all transport handling (stdio, Streamable HTTP, SSE) in one place, reducing code duplication across ~500 lines. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- examples/basic-server-react/server.ts | 76 +-------------- examples/basic-server-vanillajs/server.ts | 76 +-------------- examples/shared/server-utils.ts | 113 ++++++++++++++++++++++ examples/wiki-explorer-server/server.ts | 84 +--------------- 4 files changed, 119 insertions(+), 230 deletions(-) create mode 100644 examples/shared/server-utils.ts diff --git a/examples/basic-server-react/server.ts b/examples/basic-server-react/server.ts index 7f851add..f0a6adcb 100644 --- a/examples/basic-server-react/server.ts +++ b/examples/basic-server-react/server.ts @@ -1,23 +1,17 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; -import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import type { CallToolResult, ReadResourceResult } from "@modelcontextprotocol/sdk/types.js"; -import cors from "cors"; -import express, { type Request, type Response } from "express"; import fs from "node:fs/promises"; import path from "node:path"; import { RESOURCE_MIME_TYPE, RESOURCE_URI_META_KEY } from "../../dist/src/app"; +import { startServer } from "../shared/server-utils.js"; -const PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : 3001; const DIST_DIR = path.join(import.meta.dirname, "dist"); - const server = new McpServer({ name: "Basic MCP App Server (React-based)", version: "1.0.0", }); - // MCP Apps require two-part registration: a tool (what the LLM calls) and a // resource (the UI it renders). The `_meta` field on the tool links to the // resource URI, telling hosts which UI to display when the tool executes. @@ -58,70 +52,4 @@ const server = new McpServer({ ); } - -const app = express(); -app.use(cors()); -app.use(express.json()); - -// Streamable HTTP transport (current spec) - handles GET, POST, DELETE -app.all("/mcp", async (req: Request, res: Response) => { - try { - const transport = new StreamableHTTPServerTransport({ - sessionIdGenerator: undefined, - enableJsonResponse: true, - }); - res.on("close", () => { transport.close(); }); - - await server.connect(transport); - - await transport.handleRequest(req, res, req.body); - } catch (error) { - console.error("Error handling MCP request:", error); - if (!res.headersSent) { - res.status(500).json({ - jsonrpc: "2.0", - error: { code: -32603, message: "Internal server error" }, - id: null, - }); - } - } -}); - -// Legacy SSE transport (deprecated) - for backwards compatibility -const sseTransports = new Map(); - -app.get("/sse", async (_req: Request, res: Response) => { - const transport = new SSEServerTransport("/messages", res); - sseTransports.set(transport.sessionId, transport); - res.on("close", () => { sseTransports.delete(transport.sessionId); }); - await server.connect(transport); -}); - -app.post("/messages", async (req: Request, res: Response) => { - const sessionId = req.query.sessionId as string; - const transport = sseTransports.get(sessionId); - if (!transport) { - res.status(404).json({ error: "Session not found" }); - return; - } - await transport.handlePostMessage(req, res, req.body); -}); - -const httpServer = app.listen(PORT, (err) => { - if (err) { - console.error("Error starting server:", err); - process.exit(1); - } - console.log(`Server listening on http://localhost:${PORT}/mcp`); -}); - -function shutdown() { - console.log("\nShutting down..."); - httpServer.close(() => { - console.log("Server closed"); - process.exit(0); - }); -} - -process.on("SIGINT", shutdown); -process.on("SIGTERM", shutdown); +startServer(server, { name: "Basic MCP App Server (React-based)" }); diff --git a/examples/basic-server-vanillajs/server.ts b/examples/basic-server-vanillajs/server.ts index 2055b935..8c3621f9 100644 --- a/examples/basic-server-vanillajs/server.ts +++ b/examples/basic-server-vanillajs/server.ts @@ -1,23 +1,17 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; -import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import type { CallToolResult, ReadResourceResult } from "@modelcontextprotocol/sdk/types.js"; -import cors from "cors"; -import express, { type Request, type Response } from "express"; import fs from "node:fs/promises"; import path from "node:path"; import { RESOURCE_MIME_TYPE, RESOURCE_URI_META_KEY } from "../../dist/src/app"; +import { startServer } from "../shared/server-utils.js"; -const PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : 3001; const DIST_DIR = path.join(import.meta.dirname, "dist"); - const server = new McpServer({ name: "Basic MCP App Server (Vanilla JS)", version: "1.0.0", }); - // MCP Apps require two-part registration: a tool (what the LLM calls) and a // resource (the UI it renders). The `_meta` field on the tool links to the // resource URI, telling hosts which UI to display when the tool executes. @@ -58,70 +52,4 @@ const server = new McpServer({ ); } - -const app = express(); -app.use(cors()); -app.use(express.json()); - -// Streamable HTTP transport (current spec) - handles GET, POST, DELETE -app.all("/mcp", async (req: Request, res: Response) => { - try { - const transport = new StreamableHTTPServerTransport({ - sessionIdGenerator: undefined, - enableJsonResponse: true, - }); - res.on("close", () => { transport.close(); }); - - await server.connect(transport); - - await transport.handleRequest(req, res, req.body); - } catch (error) { - console.error("Error handling MCP request:", error); - if (!res.headersSent) { - res.status(500).json({ - jsonrpc: "2.0", - error: { code: -32603, message: "Internal server error" }, - id: null, - }); - } - } -}); - -// Legacy SSE transport (deprecated) - for backwards compatibility -const sseTransports = new Map(); - -app.get("/sse", async (_req: Request, res: Response) => { - const transport = new SSEServerTransport("/messages", res); - sseTransports.set(transport.sessionId, transport); - res.on("close", () => { sseTransports.delete(transport.sessionId); }); - await server.connect(transport); -}); - -app.post("/messages", async (req: Request, res: Response) => { - const sessionId = req.query.sessionId as string; - const transport = sseTransports.get(sessionId); - if (!transport) { - res.status(404).json({ error: "Session not found" }); - return; - } - await transport.handlePostMessage(req, res, req.body); -}); - -const httpServer = app.listen(PORT, (err) => { - if (err) { - console.error("Error starting server:", err); - process.exit(1); - } - console.log(`Server listening on http://localhost:${PORT}/mcp`); -}); - -function shutdown() { - console.log("\nShutting down..."); - httpServer.close(() => { - console.log("Server closed"); - process.exit(0); - }); -} - -process.on("SIGINT", shutdown); -process.on("SIGTERM", shutdown); +startServer(server, { name: "Basic MCP App Server (Vanilla JS)" }); diff --git a/examples/shared/server-utils.ts b/examples/shared/server-utils.ts new file mode 100644 index 00000000..998246e7 --- /dev/null +++ b/examples/shared/server-utils.ts @@ -0,0 +1,113 @@ +/** + * Shared utilities for running MCP servers with multiple transports. + * + * This module provides a unified way to start MCP servers supporting: + * - stdio transport (for local CLI tools) + * - Streamable HTTP transport (current spec) + * - Legacy SSE transport (deprecated, for backwards compatibility) + */ + +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; +import cors from "cors"; +import express, { type Request, type Response } from "express"; + +export interface ServerOptions { + /** Port to listen on for HTTP mode. Defaults to 3001 or PORT env variable. */ + port?: number; + /** Server name for logging. Defaults to "MCP Server". */ + name?: string; +} + +/** + * Starts an MCP server with support for stdio and HTTP transports. + * + * Transport is selected based on command line arguments: + * - `--stdio`: Uses stdio transport for local process communication + * - Otherwise: Starts HTTP server with Streamable HTTP and legacy SSE support + * + * @param server - The MCP server instance to start + * @param options - Optional configuration + */ +export async function startServer( + server: McpServer, + options: ServerOptions = {}, +): Promise { + const port = + options.port ?? (process.env.PORT ? parseInt(process.env.PORT, 10) : 3001); + const name = options.name ?? "MCP Server"; + + if (process.argv.includes("--stdio")) { + const transport = new StdioServerTransport(); + await server.connect(transport); + console.error(`${name} running in stdio mode`); + } else { + const app = express(); + app.use(cors()); + app.use(express.json()); + + // Streamable HTTP transport (current spec) - handles GET, POST, DELETE + app.all("/mcp", async (req: Request, res: Response) => { + try { + const transport = new StreamableHTTPServerTransport({ + sessionIdGenerator: undefined, + enableJsonResponse: true, + }); + res.on("close", () => { + transport.close(); + }); + + await server.connect(transport); + await transport.handleRequest(req, res, req.body); + } catch (error) { + console.error("Error handling MCP request:", error); + if (!res.headersSent) { + res.status(500).json({ + jsonrpc: "2.0", + error: { code: -32603, message: "Internal server error" }, + id: null, + }); + } + } + }); + + // Legacy SSE transport (deprecated) - for backwards compatibility + const sseTransports = new Map(); + + app.get("/sse", async (_req: Request, res: Response) => { + const transport = new SSEServerTransport("/messages", res); + sseTransports.set(transport.sessionId, transport); + res.on("close", () => { + sseTransports.delete(transport.sessionId); + }); + await server.connect(transport); + }); + + app.post("/messages", async (req: Request, res: Response) => { + const sessionId = req.query.sessionId as string; + const transport = sseTransports.get(sessionId); + if (!transport) { + res.status(404).json({ error: "Session not found" }); + return; + } + await transport.handlePostMessage(req, res, req.body); + }); + + const httpServer = app.listen(port, () => { + console.log(`${name} listening on http://localhost:${port}/mcp`); + }); + + const shutdown = () => { + console.log("\nShutting down..."); + httpServer.close(() => { + console.log("Server closed"); + process.exit(0); + }); + }; + + process.on("SIGINT", shutdown); + process.on("SIGTERM", shutdown); + } +} diff --git a/examples/wiki-explorer-server/server.ts b/examples/wiki-explorer-server/server.ts index ac0f291f..dc1f3a3c 100644 --- a/examples/wiki-explorer-server/server.ts +++ b/examples/wiki-explorer-server/server.ts @@ -1,20 +1,15 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import type { CallToolResult, ReadResourceResult, } from "@modelcontextprotocol/sdk/types.js"; import * as cheerio from "cheerio"; -import cors from "cors"; -import express, { type Request, type Response } from "express"; import fs from "node:fs/promises"; import path from "node:path"; import { z } from "zod"; import { RESOURCE_MIME_TYPE, RESOURCE_URI_META_KEY } from "../../dist/src/app"; +import { startServer } from "../shared/server-utils.js"; -const PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : 3001; const DIST_DIR = path.join(import.meta.dirname, "dist"); type PageInfo = { url: string; title: string }; @@ -143,79 +138,4 @@ const server = new McpServer({ ); } -async function main() { - if (process.argv.includes("--stdio")) { - const transport = new StdioServerTransport(); - await server.connect(transport); - console.error("Wiki Explorer server running in stdio mode"); - } else { - const app = express(); - app.use(cors()); - app.use(express.json()); - - // Streamable HTTP transport (current spec) - handles GET, POST, DELETE - app.all("/mcp", async (req: Request, res: Response) => { - try { - const transport = new StreamableHTTPServerTransport({ - sessionIdGenerator: undefined, - enableJsonResponse: true, - }); - res.on("close", () => { - transport.close(); - }); - - await server.connect(transport); - - await transport.handleRequest(req, res, req.body); - } catch (error) { - console.error("Error handling MCP request:", error); - if (!res.headersSent) { - res.status(500).json({ - jsonrpc: "2.0", - error: { code: -32603, message: "Internal server error" }, - id: null, - }); - } - } - }); - - // Legacy SSE transport (deprecated) - for backwards compatibility - const sseTransports = new Map(); - - app.get("/sse", async (_req: Request, res: Response) => { - const transport = new SSEServerTransport("/messages", res); - sseTransports.set(transport.sessionId, transport); - res.on("close", () => { sseTransports.delete(transport.sessionId); }); - await server.connect(transport); - }); - - app.post("/messages", async (req: Request, res: Response) => { - const sessionId = req.query.sessionId as string; - const transport = sseTransports.get(sessionId); - if (!transport) { - res.status(404).json({ error: "Session not found" }); - return; - } - await transport.handlePostMessage(req, res, req.body); - }); - - const httpServer = app.listen(PORT, () => { - console.log( - `Wiki Explorer server listening on http://localhost:${PORT}/mcp`, - ); - }); - - function shutdown() { - console.log("\nShutting down..."); - httpServer.close(() => { - console.log("Server closed"); - process.exit(0); - }); - } - - process.on("SIGINT", shutdown); - process.on("SIGTERM", shutdown); - } -} - -main().catch(console.error); +startServer(server, { name: "Wiki Explorer" }); From c251e40a918d41ac68cbd595b817a7550adf5267 Mon Sep 17 00:00:00 2001 From: Olivier Chafik Date: Thu, 11 Dec 2025 19:01:50 +0100 Subject: [PATCH 04/12] fix(examples): improve server-utils with stateful sessions and security MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address critical issues in the shared server utility: 1. Stateful sessions: Use sessionIdGenerator + onsessioninitialized to persist StreamableHTTPServerTransport across requests 2. Unified session store: Single Map for both transport types with proper type discrimination 3. Error handling: Try/catch on all endpoints with JSON-RPC errors 4. DNS rebinding protection: Use SDK's createMcpExpressApp helper 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- examples/shared/server-utils.ts | 183 ++++++++++++++++++++------------ 1 file changed, 113 insertions(+), 70 deletions(-) diff --git a/examples/shared/server-utils.ts b/examples/shared/server-utils.ts index 998246e7..38479434 100644 --- a/examples/shared/server-utils.ts +++ b/examples/shared/server-utils.ts @@ -1,35 +1,36 @@ /** * Shared utilities for running MCP servers with multiple transports. * - * This module provides a unified way to start MCP servers supporting: - * - stdio transport (for local CLI tools) - * - Streamable HTTP transport (current spec) - * - Legacy SSE transport (deprecated, for backwards compatibility) + * Supports: + * - stdio transport (--stdio flag) + * - Streamable HTTP transport (/mcp) - stateful sessions + * - Legacy SSE transport (/sse, /messages) - backwards compatibility */ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; -import cors from "cors"; -import express, { type Request, type Response } from "express"; +import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js"; +import { randomUUID } from "node:crypto"; +import type { Request, Response } from "express"; export interface ServerOptions { - /** Port to listen on for HTTP mode. Defaults to 3001 or PORT env variable. */ + /** Port to listen on. Defaults to PORT env var or 3001. */ port?: number; - /** Server name for logging. Defaults to "MCP Server". */ + /** Server name for logging. */ name?: string; } +type Transport = StreamableHTTPServerTransport | SSEServerTransport; + /** - * Starts an MCP server with support for stdio and HTTP transports. - * - * Transport is selected based on command line arguments: - * - `--stdio`: Uses stdio transport for local process communication - * - Otherwise: Starts HTTP server with Streamable HTTP and legacy SSE support + * Starts an MCP server with stdio and HTTP transports. * - * @param server - The MCP server instance to start - * @param options - Optional configuration + * HTTP mode provides: + * - /mcp (GET/POST/DELETE): Streamable HTTP with stateful sessions + * - /sse (GET) + /messages (POST): Legacy SSE for older clients */ export async function startServer( server: McpServer, @@ -40,74 +41,116 @@ export async function startServer( const name = options.name ?? "MCP Server"; if (process.argv.includes("--stdio")) { - const transport = new StdioServerTransport(); - await server.connect(transport); + await server.connect(new StdioServerTransport()); console.error(`${name} running in stdio mode`); - } else { - const app = express(); - app.use(cors()); - app.use(express.json()); - - // Streamable HTTP transport (current spec) - handles GET, POST, DELETE - app.all("/mcp", async (req: Request, res: Response) => { - try { - const transport = new StreamableHTTPServerTransport({ - sessionIdGenerator: undefined, - enableJsonResponse: true, - }); - res.on("close", () => { - transport.close(); + return; + } + + // Unified session store for both transport types + const sessions = new Map(); + + // Express with DNS rebinding protection + const app = createMcpExpressApp(); + + // Streamable HTTP (stateful) + app.all("/mcp", async (req: Request, res: Response) => { + try { + const sessionId = req.headers["mcp-session-id"] as string | undefined; + let transport = sessionId + ? (sessions.get(sessionId) as StreamableHTTPServerTransport | undefined) + : undefined; + + // Session exists but wrong transport type + if (sessionId && sessions.has(sessionId) && !transport) { + return res.status(400).json({ + jsonrpc: "2.0", + error: { code: -32000, message: "Session uses different transport" }, + id: null, }); + } - await server.connect(transport); - await transport.handleRequest(req, res, req.body); - } catch (error) { - console.error("Error handling MCP request:", error); - if (!res.headersSent) { - res.status(500).json({ + // New session requires initialize request + if (!transport) { + if (req.method !== "POST" || !isInitializeRequest(req.body)) { + return res.status(400).json({ jsonrpc: "2.0", - error: { code: -32603, message: "Internal server error" }, + error: { code: -32000, message: "Bad request: not initialized" }, id: null, }); } + + transport = new StreamableHTTPServerTransport({ + sessionIdGenerator: () => randomUUID(), + onsessioninitialized: (id) => { + sessions.set(id, transport!); + }, + }); + const t = transport; + t.onclose = () => { + if (t.sessionId) sessions.delete(t.sessionId); + }; + await server.connect(transport); } - }); - // Legacy SSE transport (deprecated) - for backwards compatibility - const sseTransports = new Map(); + await transport.handleRequest(req, res, req.body); + } catch (error) { + console.error("MCP error:", error); + if (!res.headersSent) { + res.status(500).json({ + jsonrpc: "2.0", + error: { code: -32603, message: "Internal server error" }, + id: null, + }); + } + } + }); - app.get("/sse", async (_req: Request, res: Response) => { + // Legacy SSE + app.get("/sse", async (_req: Request, res: Response) => { + try { const transport = new SSEServerTransport("/messages", res); - sseTransports.set(transport.sessionId, transport); - res.on("close", () => { - sseTransports.delete(transport.sessionId); - }); + sessions.set(transport.sessionId, transport); + res.on("close", () => sessions.delete(transport.sessionId)); await server.connect(transport); - }); + } catch (error) { + console.error("SSE error:", error); + if (!res.headersSent) res.status(500).end(); + } + }); - app.post("/messages", async (req: Request, res: Response) => { - const sessionId = req.query.sessionId as string; - const transport = sseTransports.get(sessionId); - if (!transport) { - res.status(404).json({ error: "Session not found" }); - return; + app.post("/messages", async (req: Request, res: Response) => { + try { + const transport = sessions.get(req.query.sessionId as string); + if (!(transport instanceof SSEServerTransport)) { + return res.status(404).json({ + jsonrpc: "2.0", + error: { code: -32001, message: "Session not found" }, + id: null, + }); } await transport.handlePostMessage(req, res, req.body); - }); - - const httpServer = app.listen(port, () => { - console.log(`${name} listening on http://localhost:${port}/mcp`); - }); - - const shutdown = () => { - console.log("\nShutting down..."); - httpServer.close(() => { - console.log("Server closed"); - process.exit(0); - }); - }; - - process.on("SIGINT", shutdown); - process.on("SIGTERM", shutdown); - } + } catch (error) { + console.error("Message error:", error); + if (!res.headersSent) { + res.status(500).json({ + jsonrpc: "2.0", + error: { code: -32603, message: "Internal server error" }, + id: null, + }); + } + } + }); + + const httpServer = app.listen(port, () => { + console.log(`${name} listening on http://localhost:${port}/mcp`); + }); + + const shutdown = () => { + console.log("\nShutting down..."); + sessions.forEach((t) => t.close().catch(() => {})); + httpServer.close(() => process.exit(0)); + }; + + process.on("SIGINT", shutdown); + process.on("SIGTERM", shutdown); } From 7a1eb613855a46ed7dc9cbaa709cc43c30993f9d Mon Sep 17 00:00:00 2001 From: Olivier Chafik Date: Thu, 11 Dec 2025 19:13:53 +0100 Subject: [PATCH 05/12] fix(examples): allow connections from any host for development MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use host: "0.0.0.0" in createMcpExpressApp to disable DNS rebinding protection, allowing connections from Android emulators and other devices on the network. Also re-add CORS middleware. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- examples/shared/server-utils.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/examples/shared/server-utils.ts b/examples/shared/server-utils.ts index 38479434..3a0859a6 100644 --- a/examples/shared/server-utils.ts +++ b/examples/shared/server-utils.ts @@ -13,6 +13,7 @@ import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js"; +import cors from "cors"; import { randomUUID } from "node:crypto"; import type { Request, Response } from "express"; @@ -49,8 +50,10 @@ export async function startServer( // Unified session store for both transport types const sessions = new Map(); - // Express with DNS rebinding protection - const app = createMcpExpressApp(); + // Express app - bind to all interfaces for development/testing + // (allows connections from Android emulators, other devices, etc.) + const app = createMcpExpressApp({ host: "0.0.0.0" }); + app.use(cors()); // Streamable HTTP (stateful) app.all("/mcp", async (req: Request, res: Response) => { From 46a1596ffb7f4684d655657417c3b12228ff6148 Mon Sep 17 00:00:00 2001 From: Olivier Chafik Date: Thu, 11 Dec 2025 19:24:08 +0100 Subject: [PATCH 06/12] refactor(examples): use getPort() helper for transport selection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add getPort() helper that returns undefined for --stdio, else port - Caller now explicitly passes port to startServer() - port: undefined → stdio mode, port: number → HTTP mode This makes the transport selection explicit at the call site. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- examples/basic-server-react/server.ts | 4 ++-- examples/basic-server-vanillajs/server.ts | 4 ++-- examples/budget-allocator-server/server.ts | 4 ++-- examples/cohort-heatmap-server/server.ts | 4 ++-- .../customer-segmentation-server/server.ts | 4 ++-- examples/scenario-modeler-server/server.ts | 4 ++-- examples/shared/server-utils.ts | 24 ++++++++++--------- examples/system-monitor-server/server.ts | 4 ++-- examples/threejs-server/server.ts | 4 ++-- examples/wiki-explorer-server/server.ts | 4 ++-- 10 files changed, 31 insertions(+), 29 deletions(-) diff --git a/examples/basic-server-react/server.ts b/examples/basic-server-react/server.ts index f0a6adcb..f225517a 100644 --- a/examples/basic-server-react/server.ts +++ b/examples/basic-server-react/server.ts @@ -3,7 +3,7 @@ import type { CallToolResult, ReadResourceResult } from "@modelcontextprotocol/s import fs from "node:fs/promises"; import path from "node:path"; import { RESOURCE_MIME_TYPE, RESOURCE_URI_META_KEY } from "../../dist/src/app"; -import { startServer } from "../shared/server-utils.js"; +import { getPort, startServer } from "../shared/server-utils.js"; const DIST_DIR = path.join(import.meta.dirname, "dist"); @@ -52,4 +52,4 @@ const server = new McpServer({ ); } -startServer(server, { name: "Basic MCP App Server (React-based)" }); +startServer(server, { port: getPort(), name: "Basic MCP App Server (React-based)" }); diff --git a/examples/basic-server-vanillajs/server.ts b/examples/basic-server-vanillajs/server.ts index 8c3621f9..56afdf9c 100644 --- a/examples/basic-server-vanillajs/server.ts +++ b/examples/basic-server-vanillajs/server.ts @@ -3,7 +3,7 @@ import type { CallToolResult, ReadResourceResult } from "@modelcontextprotocol/s import fs from "node:fs/promises"; import path from "node:path"; import { RESOURCE_MIME_TYPE, RESOURCE_URI_META_KEY } from "../../dist/src/app"; -import { startServer } from "../shared/server-utils.js"; +import { getPort, startServer } from "../shared/server-utils.js"; const DIST_DIR = path.join(import.meta.dirname, "dist"); @@ -52,4 +52,4 @@ const server = new McpServer({ ); } -startServer(server, { name: "Basic MCP App Server (Vanilla JS)" }); +startServer(server, { port: getPort(), name: "Basic MCP App Server (Vanilla JS)" }); diff --git a/examples/budget-allocator-server/server.ts b/examples/budget-allocator-server/server.ts index d1224b7e..83858583 100755 --- a/examples/budget-allocator-server/server.ts +++ b/examples/budget-allocator-server/server.ts @@ -13,7 +13,7 @@ import fs from "node:fs/promises"; import path from "node:path"; import { z } from "zod"; import { RESOURCE_MIME_TYPE, RESOURCE_URI_META_KEY } from "../../dist/src/app"; -import { startServer } from "../shared/server-utils.js"; +import { getPort, startServer } from "../shared/server-utils.js"; const DIST_DIR = path.join(import.meta.dirname, "dist"); @@ -292,4 +292,4 @@ server.registerResource( // Server Startup // --------------------------------------------------------------------------- -startServer(server, { name: "Budget Allocator Server" }); +startServer(server, { port: getPort(), name: "Budget Allocator Server" }); diff --git a/examples/cohort-heatmap-server/server.ts b/examples/cohort-heatmap-server/server.ts index 6128c2cf..f73b353b 100644 --- a/examples/cohort-heatmap-server/server.ts +++ b/examples/cohort-heatmap-server/server.ts @@ -4,7 +4,7 @@ import fs from "node:fs/promises"; import path from "node:path"; import { z } from "zod"; import { RESOURCE_MIME_TYPE, RESOURCE_URI_META_KEY } from "../../dist/src/app"; -import { startServer } from "../shared/server-utils.js"; +import { getPort, startServer } from "../shared/server-utils.js"; const DIST_DIR = path.join(import.meta.dirname, "dist"); @@ -201,4 +201,4 @@ const server = new McpServer({ ); } -startServer(server, { name: "Cohort Heatmap Server" }); +startServer(server, { port: getPort(), name: "Cohort Heatmap Server" }); diff --git a/examples/customer-segmentation-server/server.ts b/examples/customer-segmentation-server/server.ts index 9e8511d5..dc91fc53 100644 --- a/examples/customer-segmentation-server/server.ts +++ b/examples/customer-segmentation-server/server.ts @@ -7,7 +7,7 @@ import fs from "node:fs/promises"; import path from "node:path"; import { z } from "zod"; import { RESOURCE_MIME_TYPE, RESOURCE_URI_META_KEY } from "../../dist/src/app"; -import { startServer } from "../shared/server-utils.js"; +import { getPort, startServer } from "../shared/server-utils.js"; import { generateCustomers, generateSegmentSummaries, @@ -100,4 +100,4 @@ const server = new McpServer({ ); } -startServer(server, { name: "Customer Segmentation Server" }); +startServer(server, { port: getPort(), name: "Customer Segmentation Server" }); diff --git a/examples/scenario-modeler-server/server.ts b/examples/scenario-modeler-server/server.ts index d42b9c4c..051ebf8c 100644 --- a/examples/scenario-modeler-server/server.ts +++ b/examples/scenario-modeler-server/server.ts @@ -7,7 +7,7 @@ import fs from "node:fs/promises"; import path from "node:path"; import { z } from "zod"; import { RESOURCE_MIME_TYPE, RESOURCE_URI_META_KEY } from "../../dist/src/app"; -import { startServer } from "../shared/server-utils.js"; +import { getPort, startServer } from "../shared/server-utils.js"; const DIST_DIR = path.join(import.meta.dirname, "dist"); @@ -308,4 +308,4 @@ const server = new McpServer({ // Server Startup // ============================================================================ -startServer(server, { name: "SaaS Scenario Modeler Server" }); +startServer(server, { port: getPort(), name: "SaaS Scenario Modeler Server" }); diff --git a/examples/shared/server-utils.ts b/examples/shared/server-utils.ts index 3a0859a6..87c63559 100644 --- a/examples/shared/server-utils.ts +++ b/examples/shared/server-utils.ts @@ -2,7 +2,7 @@ * Shared utilities for running MCP servers with multiple transports. * * Supports: - * - stdio transport (--stdio flag) + * - stdio transport (when port is undefined) * - Streamable HTTP transport (/mcp) - stateful sessions * - Legacy SSE transport (/sse, /messages) - backwards compatibility */ @@ -18,7 +18,7 @@ import { randomUUID } from "node:crypto"; import type { Request, Response } from "express"; export interface ServerOptions { - /** Port to listen on. Defaults to PORT env var or 3001. */ + /** Port to listen on. If undefined, uses stdio transport. */ port?: number; /** Server name for logging. */ name?: string; @@ -27,21 +27,18 @@ export interface ServerOptions { type Transport = StreamableHTTPServerTransport | SSEServerTransport; /** - * Starts an MCP server with stdio and HTTP transports. + * Starts an MCP server. * - * HTTP mode provides: - * - /mcp (GET/POST/DELETE): Streamable HTTP with stateful sessions - * - /sse (GET) + /messages (POST): Legacy SSE for older clients + * - If port is undefined: uses stdio transport + * - If port is provided: HTTP server with Streamable HTTP + legacy SSE */ export async function startServer( server: McpServer, options: ServerOptions = {}, ): Promise { - const port = - options.port ?? (process.env.PORT ? parseInt(process.env.PORT, 10) : 3001); - const name = options.name ?? "MCP Server"; + const { port, name = "MCP Server" } = options; - if (process.argv.includes("--stdio")) { + if (port === undefined) { await server.connect(new StdioServerTransport()); console.error(`${name} running in stdio mode`); return; @@ -51,7 +48,6 @@ export async function startServer( const sessions = new Map(); // Express app - bind to all interfaces for development/testing - // (allows connections from Android emulators, other devices, etc.) const app = createMcpExpressApp({ host: "0.0.0.0" }); app.use(cors()); @@ -157,3 +153,9 @@ export async function startServer( process.on("SIGINT", shutdown); process.on("SIGTERM", shutdown); } + +/** Helper to get port from args/env, returns undefined for stdio mode */ +export function getPort(defaultPort = 3001): number | undefined { + if (process.argv.includes("--stdio")) return undefined; + return process.env.PORT ? parseInt(process.env.PORT, 10) : defaultPort; +} diff --git a/examples/system-monitor-server/server.ts b/examples/system-monitor-server/server.ts index 6e6fa808..dc02e1fa 100644 --- a/examples/system-monitor-server/server.ts +++ b/examples/system-monitor-server/server.ts @@ -9,7 +9,7 @@ import path from "node:path"; import si from "systeminformation"; import { z } from "zod"; import { RESOURCE_MIME_TYPE, RESOURCE_URI_META_KEY } from "../../dist/src/app"; -import { startServer } from "../shared/server-utils.js"; +import { getPort, startServer } from "../shared/server-utils.js"; // Schemas - types are derived from these using z.infer const CpuCoreSchema = z.object({ @@ -171,4 +171,4 @@ const server = new McpServer({ ); } -startServer(server, { name: "System Monitor Server" }); +startServer(server, { port: getPort(), name: "System Monitor Server" }); diff --git a/examples/threejs-server/server.ts b/examples/threejs-server/server.ts index db8098a3..a0939729 100644 --- a/examples/threejs-server/server.ts +++ b/examples/threejs-server/server.ts @@ -9,7 +9,7 @@ import fs from "node:fs/promises"; import path from "node:path"; import { z } from "zod"; import { RESOURCE_MIME_TYPE, RESOURCE_URI_META_KEY } from "../../dist/src/app"; -import { startServer } from "../shared/server-utils.js"; +import { getPort, startServer } from "../shared/server-utils.js"; const DIST_DIR = path.join(import.meta.dirname, "dist"); @@ -181,4 +181,4 @@ const server = new McpServer({ ); } -startServer(server, { name: "Three.js Server" }); +startServer(server, { port: getPort(), name: "Three.js Server" }); diff --git a/examples/wiki-explorer-server/server.ts b/examples/wiki-explorer-server/server.ts index dc1f3a3c..a4411a0f 100644 --- a/examples/wiki-explorer-server/server.ts +++ b/examples/wiki-explorer-server/server.ts @@ -8,7 +8,7 @@ import fs from "node:fs/promises"; import path from "node:path"; import { z } from "zod"; import { RESOURCE_MIME_TYPE, RESOURCE_URI_META_KEY } from "../../dist/src/app"; -import { startServer } from "../shared/server-utils.js"; +import { getPort, startServer } from "../shared/server-utils.js"; const DIST_DIR = path.join(import.meta.dirname, "dist"); @@ -138,4 +138,4 @@ const server = new McpServer({ ); } -startServer(server, { name: "Wiki Explorer" }); +startServer(server, { port: getPort(), name: "Wiki Explorer" }); From 7fefe2ac2e30d5a81ecc84cc07e1a766e8932a63 Mon Sep 17 00:00:00 2001 From: Olivier Chafik Date: Thu, 11 Dec 2025 19:26:52 +0100 Subject: [PATCH 07/12] fix(examples): startServer returns promise that resolves/rejects on listen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use httpServer.on('listening') and httpServer.on('error') events to properly resolve or reject the returned promise. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- examples/shared/server-utils.ts | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/examples/shared/server-utils.ts b/examples/shared/server-utils.ts index 87c63559..f8847258 100644 --- a/examples/shared/server-utils.ts +++ b/examples/shared/server-utils.ts @@ -140,18 +140,27 @@ export async function startServer( } }); - const httpServer = app.listen(port, () => { - console.log(`${name} listening on http://localhost:${port}/mcp`); + return new Promise((resolve, reject) => { + const httpServer = app.listen(port); + + httpServer.on("listening", () => { + console.log(`${name} listening on http://localhost:${port}/mcp`); + resolve(); + }); + + httpServer.on("error", (err: Error) => { + reject(err); + }); + + const shutdown = () => { + console.log("\nShutting down..."); + sessions.forEach((t) => t.close().catch(() => {})); + httpServer.close(() => process.exit(0)); + }; + + process.on("SIGINT", shutdown); + process.on("SIGTERM", shutdown); }); - - const shutdown = () => { - console.log("\nShutting down..."); - sessions.forEach((t) => t.close().catch(() => {})); - httpServer.close(() => process.exit(0)); - }; - - process.on("SIGINT", shutdown); - process.on("SIGTERM", shutdown); } /** Helper to get port from args/env, returns undefined for stdio mode */ From 3d9f54837e947c4220c3ec5f65fd1be0dc724af6 Mon Sep 17 00:00:00 2001 From: Olivier Chafik Date: Thu, 11 Dec 2025 19:28:17 +0100 Subject: [PATCH 08/12] fix(examples): add error handling to startServer calls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Exit with code 1 if server fails to start (e.g., port in use). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- examples/basic-server-react/server.ts | 2 +- examples/basic-server-vanillajs/server.ts | 2 +- examples/budget-allocator-server/server.ts | 2 +- examples/cohort-heatmap-server/server.ts | 2 +- examples/customer-segmentation-server/server.ts | 2 +- examples/scenario-modeler-server/server.ts | 2 +- examples/system-monitor-server/server.ts | 2 +- examples/threejs-server/server.ts | 2 +- examples/wiki-explorer-server/server.ts | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/basic-server-react/server.ts b/examples/basic-server-react/server.ts index f225517a..dd04d810 100644 --- a/examples/basic-server-react/server.ts +++ b/examples/basic-server-react/server.ts @@ -52,4 +52,4 @@ const server = new McpServer({ ); } -startServer(server, { port: getPort(), name: "Basic MCP App Server (React-based)" }); +startServer(server, { port: getPort(), name: "Basic MCP App Server (React-based)" }).catch((e) => { console.error(e); process.exit(1); }); diff --git a/examples/basic-server-vanillajs/server.ts b/examples/basic-server-vanillajs/server.ts index 56afdf9c..f04d1674 100644 --- a/examples/basic-server-vanillajs/server.ts +++ b/examples/basic-server-vanillajs/server.ts @@ -52,4 +52,4 @@ const server = new McpServer({ ); } -startServer(server, { port: getPort(), name: "Basic MCP App Server (Vanilla JS)" }); +startServer(server, { port: getPort(), name: "Basic MCP App Server (Vanilla JS)" }).catch((e) => { console.error(e); process.exit(1); }); diff --git a/examples/budget-allocator-server/server.ts b/examples/budget-allocator-server/server.ts index 83858583..8d089cad 100755 --- a/examples/budget-allocator-server/server.ts +++ b/examples/budget-allocator-server/server.ts @@ -292,4 +292,4 @@ server.registerResource( // Server Startup // --------------------------------------------------------------------------- -startServer(server, { port: getPort(), name: "Budget Allocator Server" }); +startServer(server, { port: getPort(), name: "Budget Allocator Server" }).catch((e) => { console.error(e); process.exit(1); }); diff --git a/examples/cohort-heatmap-server/server.ts b/examples/cohort-heatmap-server/server.ts index f73b353b..1900bef9 100644 --- a/examples/cohort-heatmap-server/server.ts +++ b/examples/cohort-heatmap-server/server.ts @@ -201,4 +201,4 @@ const server = new McpServer({ ); } -startServer(server, { port: getPort(), name: "Cohort Heatmap Server" }); +startServer(server, { port: getPort(), name: "Cohort Heatmap Server" }).catch((e) => { console.error(e); process.exit(1); }); diff --git a/examples/customer-segmentation-server/server.ts b/examples/customer-segmentation-server/server.ts index dc91fc53..c777eae1 100644 --- a/examples/customer-segmentation-server/server.ts +++ b/examples/customer-segmentation-server/server.ts @@ -100,4 +100,4 @@ const server = new McpServer({ ); } -startServer(server, { port: getPort(), name: "Customer Segmentation Server" }); +startServer(server, { port: getPort(), name: "Customer Segmentation Server" }).catch((e) => { console.error(e); process.exit(1); }); diff --git a/examples/scenario-modeler-server/server.ts b/examples/scenario-modeler-server/server.ts index 051ebf8c..e8fb5301 100644 --- a/examples/scenario-modeler-server/server.ts +++ b/examples/scenario-modeler-server/server.ts @@ -308,4 +308,4 @@ const server = new McpServer({ // Server Startup // ============================================================================ -startServer(server, { port: getPort(), name: "SaaS Scenario Modeler Server" }); +startServer(server, { port: getPort(), name: "SaaS Scenario Modeler Server" }).catch((e) => { console.error(e); process.exit(1); }); diff --git a/examples/system-monitor-server/server.ts b/examples/system-monitor-server/server.ts index dc02e1fa..b81699c6 100644 --- a/examples/system-monitor-server/server.ts +++ b/examples/system-monitor-server/server.ts @@ -171,4 +171,4 @@ const server = new McpServer({ ); } -startServer(server, { port: getPort(), name: "System Monitor Server" }); +startServer(server, { port: getPort(), name: "System Monitor Server" }).catch((e) => { console.error(e); process.exit(1); }); diff --git a/examples/threejs-server/server.ts b/examples/threejs-server/server.ts index a0939729..3a5ac770 100644 --- a/examples/threejs-server/server.ts +++ b/examples/threejs-server/server.ts @@ -181,4 +181,4 @@ const server = new McpServer({ ); } -startServer(server, { port: getPort(), name: "Three.js Server" }); +startServer(server, { port: getPort(), name: "Three.js Server" }).catch((e) => { console.error(e); process.exit(1); }); diff --git a/examples/wiki-explorer-server/server.ts b/examples/wiki-explorer-server/server.ts index a4411a0f..10e1bdfa 100644 --- a/examples/wiki-explorer-server/server.ts +++ b/examples/wiki-explorer-server/server.ts @@ -138,4 +138,4 @@ const server = new McpServer({ ); } -startServer(server, { port: getPort(), name: "Wiki Explorer" }); +startServer(server, { port: getPort(), name: "Wiki Explorer" }).catch((e) => { console.error(e); process.exit(1); }); From 55d652bdd5f8d378bd08c8f0947cfc78131273de Mon Sep 17 00:00:00 2001 From: Olivier Chafik Date: Thu, 11 Dec 2025 19:35:45 +0100 Subject: [PATCH 09/12] refactor(examples): update server files to use main() pattern with stdio support Update all example server files to use the new main() pattern that supports both HTTP and stdio transports: - Add StdioServerTransport import - Remove getPort import (replaced with direct env var parsing) - Replace direct startServer call with async main() function - Add --stdio flag detection to choose between transports - Use PORT env var with default 3001 for HTTP mode Updated servers: - basic-server-vanillajs - wiki-explorer-server - threejs-server - system-monitor-server - cohort-heatmap-server - budget-allocator-server - customer-segmentation-server - scenario-modeler-server Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- examples/basic-server-react/server.ts | 17 +++++++++-- examples/basic-server-vanillajs/server.ts | 17 +++++++++-- examples/budget-allocator-server/server.ts | 17 +++++++++-- examples/cohort-heatmap-server/server.ts | 17 +++++++++-- .../customer-segmentation-server/server.ts | 17 +++++++++-- examples/scenario-modeler-server/server.ts | 17 +++++++++-- examples/shared/server-utils.ts | 29 +++++-------------- examples/system-monitor-server/server.ts | 17 +++++++++-- examples/threejs-server/server.ts | 17 +++++++++-- examples/wiki-explorer-server/server.ts | 17 +++++++++-- 10 files changed, 143 insertions(+), 39 deletions(-) diff --git a/examples/basic-server-react/server.ts b/examples/basic-server-react/server.ts index dd04d810..9802c10c 100644 --- a/examples/basic-server-react/server.ts +++ b/examples/basic-server-react/server.ts @@ -1,9 +1,10 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import type { CallToolResult, ReadResourceResult } from "@modelcontextprotocol/sdk/types.js"; import fs from "node:fs/promises"; import path from "node:path"; import { RESOURCE_MIME_TYPE, RESOURCE_URI_META_KEY } from "../../dist/src/app"; -import { getPort, startServer } from "../shared/server-utils.js"; +import { startServer } from "../shared/server-utils.js"; const DIST_DIR = path.join(import.meta.dirname, "dist"); @@ -52,4 +53,16 @@ const server = new McpServer({ ); } -startServer(server, { port: getPort(), name: "Basic MCP App Server (React-based)" }).catch((e) => { console.error(e); process.exit(1); }); +async function main() { + if (process.argv.includes("--stdio")) { + await server.connect(new StdioServerTransport()); + } else { + const port = parseInt(process.env.PORT ?? "3001", 10); + await startServer(server, { port, name: "Basic MCP App Server (React-based)" }); + } +} + +main().catch((e) => { + console.error(e); + process.exit(1); +}); diff --git a/examples/basic-server-vanillajs/server.ts b/examples/basic-server-vanillajs/server.ts index f04d1674..d0e040f4 100644 --- a/examples/basic-server-vanillajs/server.ts +++ b/examples/basic-server-vanillajs/server.ts @@ -1,9 +1,10 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import type { CallToolResult, ReadResourceResult } from "@modelcontextprotocol/sdk/types.js"; import fs from "node:fs/promises"; import path from "node:path"; import { RESOURCE_MIME_TYPE, RESOURCE_URI_META_KEY } from "../../dist/src/app"; -import { getPort, startServer } from "../shared/server-utils.js"; +import { startServer } from "../shared/server-utils.js"; const DIST_DIR = path.join(import.meta.dirname, "dist"); @@ -52,4 +53,16 @@ const server = new McpServer({ ); } -startServer(server, { port: getPort(), name: "Basic MCP App Server (Vanilla JS)" }).catch((e) => { console.error(e); process.exit(1); }); +async function main() { + if (process.argv.includes("--stdio")) { + await server.connect(new StdioServerTransport()); + } else { + const port = parseInt(process.env.PORT ?? "3001", 10); + await startServer(server, { port, name: "Basic MCP App Server (Vanilla JS)" }); + } +} + +main().catch((e) => { + console.error(e); + process.exit(1); +}); diff --git a/examples/budget-allocator-server/server.ts b/examples/budget-allocator-server/server.ts index 8d089cad..020d31d9 100755 --- a/examples/budget-allocator-server/server.ts +++ b/examples/budget-allocator-server/server.ts @@ -5,6 +5,7 @@ * and industry benchmarks by company stage. */ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import type { CallToolResult, ReadResourceResult, @@ -13,7 +14,7 @@ import fs from "node:fs/promises"; import path from "node:path"; import { z } from "zod"; import { RESOURCE_MIME_TYPE, RESOURCE_URI_META_KEY } from "../../dist/src/app"; -import { getPort, startServer } from "../shared/server-utils.js"; +import { startServer } from "../shared/server-utils.js"; const DIST_DIR = path.join(import.meta.dirname, "dist"); @@ -292,4 +293,16 @@ server.registerResource( // Server Startup // --------------------------------------------------------------------------- -startServer(server, { port: getPort(), name: "Budget Allocator Server" }).catch((e) => { console.error(e); process.exit(1); }); +async function main() { + if (process.argv.includes("--stdio")) { + await server.connect(new StdioServerTransport()); + } else { + const port = parseInt(process.env.PORT ?? "3001", 10); + await startServer(server, { port, name: "Budget Allocator Server" }); + } +} + +main().catch((e) => { + console.error(e); + process.exit(1); +}); diff --git a/examples/cohort-heatmap-server/server.ts b/examples/cohort-heatmap-server/server.ts index 1900bef9..c17d8686 100644 --- a/examples/cohort-heatmap-server/server.ts +++ b/examples/cohort-heatmap-server/server.ts @@ -1,10 +1,11 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import type { ReadResourceResult } from "@modelcontextprotocol/sdk/types.js"; import fs from "node:fs/promises"; import path from "node:path"; import { z } from "zod"; import { RESOURCE_MIME_TYPE, RESOURCE_URI_META_KEY } from "../../dist/src/app"; -import { getPort, startServer } from "../shared/server-utils.js"; +import { startServer } from "../shared/server-utils.js"; const DIST_DIR = path.join(import.meta.dirname, "dist"); @@ -201,4 +202,16 @@ const server = new McpServer({ ); } -startServer(server, { port: getPort(), name: "Cohort Heatmap Server" }).catch((e) => { console.error(e); process.exit(1); }); +async function main() { + if (process.argv.includes("--stdio")) { + await server.connect(new StdioServerTransport()); + } else { + const port = parseInt(process.env.PORT ?? "3001", 10); + await startServer(server, { port, name: "Cohort Heatmap Server" }); + } +} + +main().catch((e) => { + console.error(e); + process.exit(1); +}); diff --git a/examples/customer-segmentation-server/server.ts b/examples/customer-segmentation-server/server.ts index c777eae1..c97e7dd2 100644 --- a/examples/customer-segmentation-server/server.ts +++ b/examples/customer-segmentation-server/server.ts @@ -1,4 +1,5 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import type { CallToolResult, ReadResourceResult, @@ -7,7 +8,7 @@ import fs from "node:fs/promises"; import path from "node:path"; import { z } from "zod"; import { RESOURCE_MIME_TYPE, RESOURCE_URI_META_KEY } from "../../dist/src/app"; -import { getPort, startServer } from "../shared/server-utils.js"; +import { startServer } from "../shared/server-utils.js"; import { generateCustomers, generateSegmentSummaries, @@ -100,4 +101,16 @@ const server = new McpServer({ ); } -startServer(server, { port: getPort(), name: "Customer Segmentation Server" }).catch((e) => { console.error(e); process.exit(1); }); +async function main() { + if (process.argv.includes("--stdio")) { + await server.connect(new StdioServerTransport()); + } else { + const port = parseInt(process.env.PORT ?? "3001", 10); + await startServer(server, { port, name: "Customer Segmentation Server" }); + } +} + +main().catch((e) => { + console.error(e); + process.exit(1); +}); diff --git a/examples/scenario-modeler-server/server.ts b/examples/scenario-modeler-server/server.ts index e8fb5301..8fbbb517 100644 --- a/examples/scenario-modeler-server/server.ts +++ b/examples/scenario-modeler-server/server.ts @@ -1,4 +1,5 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import type { CallToolResult, ReadResourceResult, @@ -7,7 +8,7 @@ import fs from "node:fs/promises"; import path from "node:path"; import { z } from "zod"; import { RESOURCE_MIME_TYPE, RESOURCE_URI_META_KEY } from "../../dist/src/app"; -import { getPort, startServer } from "../shared/server-utils.js"; +import { startServer } from "../shared/server-utils.js"; const DIST_DIR = path.join(import.meta.dirname, "dist"); @@ -308,4 +309,16 @@ const server = new McpServer({ // Server Startup // ============================================================================ -startServer(server, { port: getPort(), name: "SaaS Scenario Modeler Server" }).catch((e) => { console.error(e); process.exit(1); }); +async function main() { + if (process.argv.includes("--stdio")) { + await server.connect(new StdioServerTransport()); + } else { + const port = parseInt(process.env.PORT ?? "3001", 10); + await startServer(server, { port, name: "SaaS Scenario Modeler Server" }); + } +} + +main().catch((e) => { + console.error(e); + process.exit(1); +}); diff --git a/examples/shared/server-utils.ts b/examples/shared/server-utils.ts index f8847258..73d5aa84 100644 --- a/examples/shared/server-utils.ts +++ b/examples/shared/server-utils.ts @@ -1,8 +1,7 @@ /** - * Shared utilities for running MCP servers with multiple transports. + * Shared utilities for running MCP servers with HTTP transports. * * Supports: - * - stdio transport (when port is undefined) * - Streamable HTTP transport (/mcp) - stateful sessions * - Legacy SSE transport (/sse, /messages) - backwards compatibility */ @@ -10,7 +9,6 @@ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js"; import cors from "cors"; @@ -18,8 +16,8 @@ import { randomUUID } from "node:crypto"; import type { Request, Response } from "express"; export interface ServerOptions { - /** Port to listen on. If undefined, uses stdio transport. */ - port?: number; + /** Port to listen on (required). */ + port: number; /** Server name for logging. */ name?: string; } @@ -27,23 +25,18 @@ export interface ServerOptions { type Transport = StreamableHTTPServerTransport | SSEServerTransport; /** - * Starts an MCP server. + * Starts an MCP server with HTTP transports. * - * - If port is undefined: uses stdio transport - * - If port is provided: HTTP server with Streamable HTTP + legacy SSE + * Provides: + * - /mcp (GET/POST/DELETE): Streamable HTTP with stateful sessions + * - /sse (GET) + /messages (POST): Legacy SSE for older clients */ export async function startServer( server: McpServer, - options: ServerOptions = {}, + options: ServerOptions, ): Promise { const { port, name = "MCP Server" } = options; - if (port === undefined) { - await server.connect(new StdioServerTransport()); - console.error(`${name} running in stdio mode`); - return; - } - // Unified session store for both transport types const sessions = new Map(); @@ -162,9 +155,3 @@ export async function startServer( process.on("SIGTERM", shutdown); }); } - -/** Helper to get port from args/env, returns undefined for stdio mode */ -export function getPort(defaultPort = 3001): number | undefined { - if (process.argv.includes("--stdio")) return undefined; - return process.env.PORT ? parseInt(process.env.PORT, 10) : defaultPort; -} diff --git a/examples/system-monitor-server/server.ts b/examples/system-monitor-server/server.ts index b81699c6..b1d26b29 100644 --- a/examples/system-monitor-server/server.ts +++ b/examples/system-monitor-server/server.ts @@ -1,4 +1,5 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import type { CallToolResult, ReadResourceResult, @@ -9,7 +10,7 @@ import path from "node:path"; import si from "systeminformation"; import { z } from "zod"; import { RESOURCE_MIME_TYPE, RESOURCE_URI_META_KEY } from "../../dist/src/app"; -import { getPort, startServer } from "../shared/server-utils.js"; +import { startServer } from "../shared/server-utils.js"; // Schemas - types are derived from these using z.infer const CpuCoreSchema = z.object({ @@ -171,4 +172,16 @@ const server = new McpServer({ ); } -startServer(server, { port: getPort(), name: "System Monitor Server" }).catch((e) => { console.error(e); process.exit(1); }); +async function main() { + if (process.argv.includes("--stdio")) { + await server.connect(new StdioServerTransport()); + } else { + const port = parseInt(process.env.PORT ?? "3001", 10); + await startServer(server, { port, name: "System Monitor Server" }); + } +} + +main().catch((e) => { + console.error(e); + process.exit(1); +}); diff --git a/examples/threejs-server/server.ts b/examples/threejs-server/server.ts index 3a5ac770..ba565df5 100644 --- a/examples/threejs-server/server.ts +++ b/examples/threejs-server/server.ts @@ -4,12 +4,13 @@ * Provides tools for rendering interactive 3D scenes using Three.js. */ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import type { ReadResourceResult } from "@modelcontextprotocol/sdk/types.js"; import fs from "node:fs/promises"; import path from "node:path"; import { z } from "zod"; import { RESOURCE_MIME_TYPE, RESOURCE_URI_META_KEY } from "../../dist/src/app"; -import { getPort, startServer } from "../shared/server-utils.js"; +import { startServer } from "../shared/server-utils.js"; const DIST_DIR = path.join(import.meta.dirname, "dist"); @@ -181,4 +182,16 @@ const server = new McpServer({ ); } -startServer(server, { port: getPort(), name: "Three.js Server" }).catch((e) => { console.error(e); process.exit(1); }); +async function main() { + if (process.argv.includes("--stdio")) { + await server.connect(new StdioServerTransport()); + } else { + const port = parseInt(process.env.PORT ?? "3001", 10); + await startServer(server, { port, name: "Three.js Server" }); + } +} + +main().catch((e) => { + console.error(e); + process.exit(1); +}); diff --git a/examples/wiki-explorer-server/server.ts b/examples/wiki-explorer-server/server.ts index 10e1bdfa..56382447 100644 --- a/examples/wiki-explorer-server/server.ts +++ b/examples/wiki-explorer-server/server.ts @@ -1,4 +1,5 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import type { CallToolResult, ReadResourceResult, @@ -8,7 +9,7 @@ import fs from "node:fs/promises"; import path from "node:path"; import { z } from "zod"; import { RESOURCE_MIME_TYPE, RESOURCE_URI_META_KEY } from "../../dist/src/app"; -import { getPort, startServer } from "../shared/server-utils.js"; +import { startServer } from "../shared/server-utils.js"; const DIST_DIR = path.join(import.meta.dirname, "dist"); @@ -138,4 +139,16 @@ const server = new McpServer({ ); } -startServer(server, { port: getPort(), name: "Wiki Explorer" }).catch((e) => { console.error(e); process.exit(1); }); +async function main() { + if (process.argv.includes("--stdio")) { + await server.connect(new StdioServerTransport()); + } else { + const port = parseInt(process.env.PORT ?? "3001", 10); + await startServer(server, { port, name: "Wiki Explorer" }); + } +} + +main().catch((e) => { + console.error(e); + process.exit(1); +}); From 929898f69bb0bee2a43df5d8f72028e712c865d7 Mon Sep 17 00:00:00 2001 From: Olivier Chafik Date: Thu, 11 Dec 2025 19:39:59 +0100 Subject: [PATCH 10/12] fix(examples): use correct default ports matching run-all.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Each server now defaults to its assigned port from run-all.ts: - basic-server-react: 3101 - basic-server-vanillajs: 3102 - budget-allocator-server: 3103 - cohort-heatmap-server: 3104 - customer-segmentation-server: 3105 - scenario-modeler-server: 3106 - system-monitor-server: 3107 - threejs-server: 3108 - wiki-explorer-server: 3109 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- examples/basic-server-react/server.ts | 2 +- examples/basic-server-vanillajs/server.ts | 2 +- examples/budget-allocator-server/server.ts | 2 +- examples/cohort-heatmap-server/server.ts | 2 +- examples/customer-segmentation-server/server.ts | 2 +- examples/scenario-modeler-server/server.ts | 2 +- examples/system-monitor-server/server.ts | 2 +- examples/threejs-server/server.ts | 2 +- examples/wiki-explorer-server/server.ts | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/basic-server-react/server.ts b/examples/basic-server-react/server.ts index 9802c10c..88204d80 100644 --- a/examples/basic-server-react/server.ts +++ b/examples/basic-server-react/server.ts @@ -57,7 +57,7 @@ async function main() { if (process.argv.includes("--stdio")) { await server.connect(new StdioServerTransport()); } else { - const port = parseInt(process.env.PORT ?? "3001", 10); + const port = parseInt(process.env.PORT ?? "3101", 10); await startServer(server, { port, name: "Basic MCP App Server (React-based)" }); } } diff --git a/examples/basic-server-vanillajs/server.ts b/examples/basic-server-vanillajs/server.ts index d0e040f4..1ad6eb80 100644 --- a/examples/basic-server-vanillajs/server.ts +++ b/examples/basic-server-vanillajs/server.ts @@ -57,7 +57,7 @@ async function main() { if (process.argv.includes("--stdio")) { await server.connect(new StdioServerTransport()); } else { - const port = parseInt(process.env.PORT ?? "3001", 10); + const port = parseInt(process.env.PORT ?? "3102", 10); await startServer(server, { port, name: "Basic MCP App Server (Vanilla JS)" }); } } diff --git a/examples/budget-allocator-server/server.ts b/examples/budget-allocator-server/server.ts index 020d31d9..0e4f39b3 100755 --- a/examples/budget-allocator-server/server.ts +++ b/examples/budget-allocator-server/server.ts @@ -297,7 +297,7 @@ async function main() { if (process.argv.includes("--stdio")) { await server.connect(new StdioServerTransport()); } else { - const port = parseInt(process.env.PORT ?? "3001", 10); + const port = parseInt(process.env.PORT ?? "3103", 10); await startServer(server, { port, name: "Budget Allocator Server" }); } } diff --git a/examples/cohort-heatmap-server/server.ts b/examples/cohort-heatmap-server/server.ts index c17d8686..7580eef6 100644 --- a/examples/cohort-heatmap-server/server.ts +++ b/examples/cohort-heatmap-server/server.ts @@ -206,7 +206,7 @@ async function main() { if (process.argv.includes("--stdio")) { await server.connect(new StdioServerTransport()); } else { - const port = parseInt(process.env.PORT ?? "3001", 10); + const port = parseInt(process.env.PORT ?? "3104", 10); await startServer(server, { port, name: "Cohort Heatmap Server" }); } } diff --git a/examples/customer-segmentation-server/server.ts b/examples/customer-segmentation-server/server.ts index c97e7dd2..6e086a82 100644 --- a/examples/customer-segmentation-server/server.ts +++ b/examples/customer-segmentation-server/server.ts @@ -105,7 +105,7 @@ async function main() { if (process.argv.includes("--stdio")) { await server.connect(new StdioServerTransport()); } else { - const port = parseInt(process.env.PORT ?? "3001", 10); + const port = parseInt(process.env.PORT ?? "3105", 10); await startServer(server, { port, name: "Customer Segmentation Server" }); } } diff --git a/examples/scenario-modeler-server/server.ts b/examples/scenario-modeler-server/server.ts index 8fbbb517..24e1b670 100644 --- a/examples/scenario-modeler-server/server.ts +++ b/examples/scenario-modeler-server/server.ts @@ -313,7 +313,7 @@ async function main() { if (process.argv.includes("--stdio")) { await server.connect(new StdioServerTransport()); } else { - const port = parseInt(process.env.PORT ?? "3001", 10); + const port = parseInt(process.env.PORT ?? "3106", 10); await startServer(server, { port, name: "SaaS Scenario Modeler Server" }); } } diff --git a/examples/system-monitor-server/server.ts b/examples/system-monitor-server/server.ts index b1d26b29..757a8aa2 100644 --- a/examples/system-monitor-server/server.ts +++ b/examples/system-monitor-server/server.ts @@ -176,7 +176,7 @@ async function main() { if (process.argv.includes("--stdio")) { await server.connect(new StdioServerTransport()); } else { - const port = parseInt(process.env.PORT ?? "3001", 10); + const port = parseInt(process.env.PORT ?? "3107", 10); await startServer(server, { port, name: "System Monitor Server" }); } } diff --git a/examples/threejs-server/server.ts b/examples/threejs-server/server.ts index ba565df5..399d3407 100644 --- a/examples/threejs-server/server.ts +++ b/examples/threejs-server/server.ts @@ -186,7 +186,7 @@ async function main() { if (process.argv.includes("--stdio")) { await server.connect(new StdioServerTransport()); } else { - const port = parseInt(process.env.PORT ?? "3001", 10); + const port = parseInt(process.env.PORT ?? "3108", 10); await startServer(server, { port, name: "Three.js Server" }); } } diff --git a/examples/wiki-explorer-server/server.ts b/examples/wiki-explorer-server/server.ts index 56382447..c4787898 100644 --- a/examples/wiki-explorer-server/server.ts +++ b/examples/wiki-explorer-server/server.ts @@ -143,7 +143,7 @@ async function main() { if (process.argv.includes("--stdio")) { await server.connect(new StdioServerTransport()); } else { - const port = parseInt(process.env.PORT ?? "3001", 10); + const port = parseInt(process.env.PORT ?? "3109", 10); await startServer(server, { port, name: "Wiki Explorer" }); } } From 4e05bdfdbc860baa1f915f7c4e68e67a6c268a22 Mon Sep 17 00:00:00 2001 From: Olivier Chafik Date: Thu, 11 Dec 2025 19:55:44 +0100 Subject: [PATCH 11/12] fix(examples): expose mcp-session-id header for CORS requests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Without exposedHeaders in the CORS config, browsers block JavaScript from reading the mcp-session-id response header. This caused the SDK's StreamableHTTPClientTransport to never capture the session ID, breaking all subsequent requests with "Bad request: not initialized" errors. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- examples/shared/server-utils.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/shared/server-utils.ts b/examples/shared/server-utils.ts index 73d5aa84..fb949b1f 100644 --- a/examples/shared/server-utils.ts +++ b/examples/shared/server-utils.ts @@ -42,7 +42,9 @@ export async function startServer( // Express app - bind to all interfaces for development/testing const app = createMcpExpressApp({ host: "0.0.0.0" }); - app.use(cors()); + app.use(cors({ + exposedHeaders: ["mcp-session-id"], + })); // Streamable HTTP (stateful) app.all("/mcp", async (req: Request, res: Response) => { From 503d1c2bfa55cb491547ed5469b9ff6376e64a4b Mon Sep 17 00:00:00 2001 From: Olivier Chafik Date: Thu, 11 Dec 2025 20:39:34 +0100 Subject: [PATCH 12/12] pretty:fix --- examples/shared/server-utils.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/examples/shared/server-utils.ts b/examples/shared/server-utils.ts index fb949b1f..49655dfa 100644 --- a/examples/shared/server-utils.ts +++ b/examples/shared/server-utils.ts @@ -42,9 +42,11 @@ export async function startServer( // Express app - bind to all interfaces for development/testing const app = createMcpExpressApp({ host: "0.0.0.0" }); - app.use(cors({ - exposedHeaders: ["mcp-session-id"], - })); + app.use( + cors({ + exposedHeaders: ["mcp-session-id"], + }), + ); // Streamable HTTP (stateful) app.all("/mcp", async (req: Request, res: Response) => {