diff --git a/src/everything/README.md b/src/everything/README.md index 8109e4449f..d338b7a8ad 100644 --- a/src/everything/README.md +++ b/src/everything/README.md @@ -70,6 +70,12 @@ npm install npm run start:sse ``` +Note: the SSE transport binds to `127.0.0.1` by default and only enables CORS for loopback origins (for Inspector direct connect). To intentionally expose it, set: + +```shell +HOST=0.0.0.0 MCP_CORS_ORIGIN_REGEX='^https?://your-allowed-origin(:\\d+)?$' npm run start:sse +``` + ## Run from source with [Streamable HTTP Transport](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http) ```shell @@ -78,6 +84,12 @@ npm install npm run start:streamableHttp ``` +Note: the Streamable HTTP transport binds to `127.0.0.1` by default and only enables CORS for loopback origins (for Inspector direct connect). To intentionally expose it, set: + +```shell +HOST=0.0.0.0 MCP_CORS_ORIGIN_REGEX='^https?://your-allowed-origin(:\\d+)?$' npm run start:streamableHttp +``` + ## Running as an installed package ### Install ```shell @@ -103,4 +115,3 @@ npx @modelcontextprotocol/server-everything sse ```shell npx @modelcontextprotocol/server-everything streamableHttp ``` - diff --git a/src/everything/transports/cors.ts b/src/everything/transports/cors.ts new file mode 100644 index 0000000000..2271f9c029 --- /dev/null +++ b/src/everything/transports/cors.ts @@ -0,0 +1,38 @@ +import type { CorsOptions } from "cors"; + +const DEFAULT_LOOPBACK_ORIGIN_REGEX = + /^https?:\/\/(localhost|127\.0\.0\.1|\[::1\])(?::\d+)?$/; + +function getCorsOriginRegex(): RegExp { + const raw = process.env.MCP_CORS_ORIGIN_REGEX; + if (!raw) return DEFAULT_LOOPBACK_ORIGIN_REGEX; + + try { + return new RegExp(raw); + } catch (err) { + // Fail fast with a clear message instead of silently allowing all origins. + throw new Error( + `Invalid MCP_CORS_ORIGIN_REGEX=${JSON.stringify(raw)}: ${String(err)}` + ); + } +} + +export function createCorsOptions(opts: { + methods: string; + exposedHeaders?: string[]; +}): CorsOptions { + const originRegex = getCorsOriginRegex(); + + return { + // Only allow loopback origins by default (Inspector direct connect). + // Override via MCP_CORS_ORIGIN_REGEX if you intentionally need a wider allowlist. + origin: (origin, callback) => { + if (!origin) return callback(null, false); + return callback(null, originRegex.test(origin)); + }, + methods: opts.methods, + preflightContinue: false, + optionsSuccessStatus: 204, + ...(opts.exposedHeaders ? { exposedHeaders: opts.exposedHeaders } : {}), + }; +} diff --git a/src/everything/transports/sse.ts b/src/everything/transports/sse.ts index 2406db7cfa..2256dc970f 100644 --- a/src/everything/transports/sse.ts +++ b/src/everything/transports/sse.ts @@ -2,18 +2,19 @@ import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; import express from "express"; import { createServer } from "../server/index.js"; import cors from "cors"; +import { createCorsOptions } from "./cors.js"; console.error("Starting SSE server..."); -// Express app with permissive CORS for testing with Inspector direct connect mode +// Express app with loopback-only CORS by default for Inspector direct connect mode. +// Override via MCP_CORS_ORIGIN_REGEX if you intentionally need a wider allowlist. const app = express(); app.use( - cors({ - origin: "*", // use "*" with caution in production - methods: "GET,POST", - preflightContinue: false, - optionsSuccessStatus: 204, - }) + cors( + createCorsOptions({ + methods: "GET,POST", + }) + ) ); // Map sessionId to transport for each client @@ -71,7 +72,11 @@ app.post("/message", async (req, res) => { }); // Start the express server -const PORT = process.env.PORT || 3001; -app.listen(PORT, () => { - console.error(`Server is running on port ${PORT}`); +const PORT = process.env.PORT ? Number(process.env.PORT) : 3001; +if (!Number.isFinite(PORT)) { + throw new Error(`Invalid PORT=${JSON.stringify(process.env.PORT)}`); +} +const HOST = process.env.HOST || "127.0.0.1"; +app.listen(PORT, HOST, () => { + console.error(`Server is running on http://${HOST}:${PORT}`); }); diff --git a/src/everything/transports/streamableHttp.ts b/src/everything/transports/streamableHttp.ts index 2e79abc554..7c8c4fafc0 100644 --- a/src/everything/transports/streamableHttp.ts +++ b/src/everything/transports/streamableHttp.ts @@ -6,6 +6,7 @@ import express, { Request, Response } from "express"; import { createServer } from "../server/index.js"; import { randomUUID } from "node:crypto"; import cors from "cors"; +import { createCorsOptions } from "./cors.js"; // Simple in-memory event store for SSE resumability class InMemoryEventStore implements EventStore { @@ -38,16 +39,20 @@ class InMemoryEventStore implements EventStore { console.log("Starting Streamable HTTP server..."); -// Express app with permissive CORS for testing with Inspector direct connect mode +// Express app with loopback-only CORS by default for Inspector direct connect mode. +// Override via MCP_CORS_ORIGIN_REGEX if you intentionally need a wider allowlist. const app = express(); app.use( - cors({ - origin: "*", // use "*" with caution in production - methods: "GET,POST,DELETE", - preflightContinue: false, - optionsSuccessStatus: 204, - exposedHeaders: ["mcp-session-id", "last-event-id", "mcp-protocol-version"], - }) + cors( + createCorsOptions({ + methods: "GET,POST,DELETE", + exposedHeaders: [ + "mcp-session-id", + "last-event-id", + "mcp-protocol-version", + ], + }) + ) ); // Map sessionId to server transport for each client @@ -198,9 +203,15 @@ app.delete("/mcp", async (req: Request, res: Response) => { }); // Start the server -const PORT = process.env.PORT || 3001; -const server = app.listen(PORT, () => { - console.error(`MCP Streamable HTTP Server listening on port ${PORT}`); +const PORT = process.env.PORT ? Number(process.env.PORT) : 3001; +if (!Number.isFinite(PORT)) { + throw new Error(`Invalid PORT=${JSON.stringify(process.env.PORT)}`); +} +const HOST = process.env.HOST || "127.0.0.1"; +const server = app.listen(PORT, HOST, () => { + console.error( + `MCP Streamable HTTP Server listening on http://${HOST}:${PORT}` + ); }); // Handle server errors