diff --git a/examples/basic-server-react/server.ts b/examples/basic-server-react/server.ts index ac2cffe3..88204d80 100644 --- a/examples/basic-server-react/server.ts +++ b/examples/basic-server-react/server.ts @@ -1,22 +1,18 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.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. @@ -57,49 +53,16 @@ const server = new McpServer({ ); } - -const app = express(); -app.use(cors()); -app.use(express.json()); - -app.post("/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, - }); - } +async function main() { + if (process.argv.includes("--stdio")) { + await server.connect(new StdioServerTransport()); + } else { + const port = parseInt(process.env.PORT ?? "3101", 10); + await startServer(server, { port, name: "Basic MCP App Server (React-based)" }); } -}); - -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); +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 993d35e8..1ad6eb80 100644 --- a/examples/basic-server-vanillajs/server.ts +++ b/examples/basic-server-vanillajs/server.ts @@ -1,22 +1,18 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.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. @@ -57,49 +53,16 @@ const server = new McpServer({ ); } - -const app = express(); -app.use(cors()); -app.use(express.json()); - -app.post("/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, - }); - } +async function main() { + if (process.argv.includes("--stdio")) { + await server.connect(new StdioServerTransport()); + } else { + const port = parseInt(process.env.PORT ?? "3102", 10); + await startServer(server, { port, name: "Basic MCP App Server (Vanilla JS)" }); } -}); - -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); +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 daf147de..0e4f39b3 100755 --- a/examples/budget-allocator-server/server.ts +++ b/examples/budget-allocator-server/server.ts @@ -6,19 +6,16 @@ */ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.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"); // --------------------------------------------------------------------------- @@ -298,59 +295,14 @@ server.registerResource( 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"); + await server.connect(new StdioServerTransport()); } else { - const app = express(); - app.use(cors()); - app.use(express.json()); - - app.post("/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, - }); - } - } - }); - - 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); + const port = parseInt(process.env.PORT ?? "3103", 10); + await startServer(server, { port, name: "Budget Allocator Server" }); } } -main().catch(console.error); +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 63a93f22..7580eef6 100644 --- a/examples/cohort-heatmap-server/server.ts +++ b/examples/cohort-heatmap-server/server.ts @@ -1,15 +1,12 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.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 @@ -207,60 +204,14 @@ 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"); + await server.connect(new StdioServerTransport()); } else { - const app = express(); - app.use(cors()); - app.use(express.json()); - - app.post("/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, - }); - } - } - }); - - 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); + const port = parseInt(process.env.PORT ?? "3104", 10); + await startServer(server, { port, name: "Cohort Heatmap Server" }); } } -main().catch(console.error); +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 bc95c7e6..6e086a82 100644 --- a/examples/customer-segmentation-server/server.ts +++ b/examples/customer-segmentation-server/server.ts @@ -1,23 +1,20 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.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 @@ -106,60 +103,14 @@ 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"); + await server.connect(new StdioServerTransport()); } else { - const app = express(); - app.use(cors()); - app.use(express.json()); - - app.post("/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, - }); - } - } - }); - - 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); + const port = parseInt(process.env.PORT ?? "3105", 10); + await startServer(server, { port, name: "Customer Segmentation Server" }); } } -main().catch(console.error); +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 4a9ef48f..24e1b670 100644 --- a/examples/scenario-modeler-server/server.ts +++ b/examples/scenario-modeler-server/server.ts @@ -1,18 +1,15 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.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"); // ============================================================================ @@ -314,59 +311,14 @@ const server = new McpServer({ 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"); + await server.connect(new StdioServerTransport()); } else { - const app = express(); - app.use(cors()); - app.use(express.json()); - - app.post("/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, - }); - } - } - }); - - 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); + const port = parseInt(process.env.PORT ?? "3106", 10); + await startServer(server, { port, name: "SaaS Scenario Modeler Server" }); } } -main().catch(console.error); +main().catch((e) => { + console.error(e); + process.exit(1); +}); diff --git a/examples/shared/server-utils.ts b/examples/shared/server-utils.ts new file mode 100644 index 00000000..49655dfa --- /dev/null +++ b/examples/shared/server-utils.ts @@ -0,0 +1,161 @@ +/** + * Shared utilities for running MCP servers with HTTP transports. + * + * Supports: + * - 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 { 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"; + +export interface ServerOptions { + /** Port to listen on (required). */ + port: number; + /** Server name for logging. */ + name?: string; +} + +type Transport = StreamableHTTPServerTransport | SSEServerTransport; + +/** + * Starts an MCP server with HTTP transports. + * + * 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, +): Promise { + const { port, name = "MCP Server" } = options; + + // Unified session store for both transport types + const sessions = new Map(); + + // Express app - bind to all interfaces for development/testing + const app = createMcpExpressApp({ host: "0.0.0.0" }); + app.use( + cors({ + exposedHeaders: ["mcp-session-id"], + }), + ); + + // 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, + }); + } + + // New session requires initialize request + if (!transport) { + if (req.method !== "POST" || !isInitializeRequest(req.body)) { + return res.status(400).json({ + jsonrpc: "2.0", + 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); + } + + 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, + }); + } + } + }); + + // Legacy SSE + app.get("/sse", async (_req: Request, res: Response) => { + try { + const transport = new SSEServerTransport("/messages", res); + 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) => { + 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); + } 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, + }); + } + } + }); + + 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); + }); +} diff --git a/examples/system-monitor-server/server.ts b/examples/system-monitor-server/server.ts index 9fca3719..757a8aa2 100644 --- a/examples/system-monitor-server/server.ts +++ b/examples/system-monitor-server/server.ts @@ -1,20 +1,16 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.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({ @@ -178,60 +174,14 @@ 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"); + await server.connect(new StdioServerTransport()); } else { - const app = express(); - app.use(cors()); - app.use(express.json()); - - app.post("/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, - }); - } - } - }); - - 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); + const port = parseInt(process.env.PORT ?? "3107", 10); + await startServer(server, { port, name: "System Monitor Server" }); } } -main().catch(console.error); +main().catch((e) => { + console.error(e); + process.exit(1); +}); diff --git a/examples/threejs-server/server.ts b/examples/threejs-server/server.ts index 81d860c8..399d3407 100644 --- a/examples/threejs-server/server.ts +++ b/examples/threejs-server/server.ts @@ -5,16 +5,13 @@ */ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.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 @@ -187,54 +184,14 @@ 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"); + await server.connect(new StdioServerTransport()); } else { - const app = express(); - app.use(cors()); - app.use(express.json()); - - app.post("/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, - }); - } - } - }); - - 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); + const port = parseInt(process.env.PORT ?? "3108", 10); + await startServer(server, { port, name: "Three.js Server" }); } } -main().catch(console.error); +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 79d17721..c4787898 100644 --- a/examples/wiki-explorer-server/server.ts +++ b/examples/wiki-explorer-server/server.ts @@ -1,19 +1,16 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.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 }; @@ -144,56 +141,14 @@ 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"); + await server.connect(new StdioServerTransport()); } else { - const app = express(); - app.use(cors()); - app.use(express.json()); - - app.post("/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, - }); - } - } - }); - - 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); + const port = parseInt(process.env.PORT ?? "3109", 10); + await startServer(server, { port, name: "Wiki Explorer" }); } } -main().catch(console.error); +main().catch((e) => { + console.error(e); + process.exit(1); +});