From bda811a95037b71dae55fdc2f2fc55660aaf486b Mon Sep 17 00:00:00 2001 From: Jerome Date: Mon, 30 Jun 2025 15:21:57 +0100 Subject: [PATCH 1/3] feat: Add CORS configuration for browser-based MCP clients - Add cors middleware to example servers with Mcp-Session-Id exposed - Add CORS documentation section to README - Configure minimal CORS settings (only expose required headers) This enables browser-based clients to connect to MCP servers by properly exposing the Mcp-Session-Id header required for session management. Reported-by: Jerome --- README.md | 20 +++++++++++++++++++ .../server/jsonResponseStreamableHttp.ts | 7 +++++++ .../server/simpleStatelessStreamableHttp.ts | 7 +++++++ .../sseAndStreamableHttpCompatibleServer.ts | 7 +++++++ 4 files changed, 41 insertions(+) diff --git a/README.md b/README.md index ac10e8cb0..651893531 100644 --- a/README.md +++ b/README.md @@ -584,6 +584,26 @@ app.listen(3000); > ); > ``` + +#### CORS Configuration for Browser-Based Clients + +If you'd like your server to be accessible by browser-based MCP clients, you'll need to configure CORS headers. The `Mcp-Session-Id` header must be exposed for browser clients to access it: + +```typescript +import cors from 'cors'; + +// Add CORS middleware before your MCP routes +app.use(cors({ + origin: '*', // Configure appropriately for production + exposedHeaders: ['Mcp-Session-Id'] +})); +``` + +This configuration is necessary because: +- The MCP streamable HTTP transport uses the `Mcp-Session-Id` header for session management +- Browsers restrict access to response headers unless explicitly exposed via CORS +- Without this configuration, browser-based clients won't be able to read the session ID from initialization responses + #### Without Session Management (Stateless) For simpler use cases where session management isn't needed: diff --git a/src/examples/server/jsonResponseStreamableHttp.ts b/src/examples/server/jsonResponseStreamableHttp.ts index 02d8c2de0..04b14470b 100644 --- a/src/examples/server/jsonResponseStreamableHttp.ts +++ b/src/examples/server/jsonResponseStreamableHttp.ts @@ -4,6 +4,7 @@ import { McpServer } from '../../server/mcp.js'; import { StreamableHTTPServerTransport } from '../../server/streamableHttp.js'; import { z } from 'zod'; import { CallToolResult, isInitializeRequest } from '../../types.js'; +import cors from 'cors'; // Create an MCP server with implementation details @@ -81,6 +82,12 @@ const getServer = () => { const app = express(); app.use(express.json()); +// Configure CORS to expose Mcp-Session-Id header for browser-based clients +app.use(cors({ + origin: '*', // Allow all origins - adjust as needed for production + exposedHeaders: ['Mcp-Session-Id'] +})); + // Map to store transports by session ID const transports: { [sessionId: string]: StreamableHTTPServerTransport } = {}; diff --git a/src/examples/server/simpleStatelessStreamableHttp.ts b/src/examples/server/simpleStatelessStreamableHttp.ts index 6fb2ae831..d235265cd 100644 --- a/src/examples/server/simpleStatelessStreamableHttp.ts +++ b/src/examples/server/simpleStatelessStreamableHttp.ts @@ -3,6 +3,7 @@ import { McpServer } from '../../server/mcp.js'; import { StreamableHTTPServerTransport } from '../../server/streamableHttp.js'; import { z } from 'zod'; import { CallToolResult, GetPromptResult, ReadResourceResult } from '../../types.js'; +import cors from 'cors'; const getServer = () => { // Create an MCP server with implementation details @@ -96,6 +97,12 @@ const getServer = () => { const app = express(); app.use(express.json()); +// Configure CORS to expose Mcp-Session-Id header for browser-based clients +app.use(cors({ + origin: '*', // Allow all origins - adjust as needed for production + exposedHeaders: ['Mcp-Session-Id'] +})); + app.post('/mcp', async (req: Request, res: Response) => { const server = getServer(); try { diff --git a/src/examples/server/sseAndStreamableHttpCompatibleServer.ts b/src/examples/server/sseAndStreamableHttpCompatibleServer.ts index ded110a13..7b18578a5 100644 --- a/src/examples/server/sseAndStreamableHttpCompatibleServer.ts +++ b/src/examples/server/sseAndStreamableHttpCompatibleServer.ts @@ -6,6 +6,7 @@ import { SSEServerTransport } from '../../server/sse.js'; import { z } from 'zod'; import { CallToolResult, isInitializeRequest } from '../../types.js'; import { InMemoryEventStore } from '../shared/inMemoryEventStore.js'; +import cors from 'cors'; /** * This example server demonstrates backwards compatibility with both: @@ -71,6 +72,12 @@ const getServer = () => { const app = express(); app.use(express.json()); +// Configure CORS to expose Mcp-Session-Id header for browser-based clients +app.use(cors({ + origin: '*', // Allow all origins - adjust as needed for production + exposedHeaders: ['Mcp-Session-Id'] +})); + // Store transports by session ID const transports: Record = {}; From 3d381df2bc53ad6fbe5eba80d6454d83d3d5ab13 Mon Sep 17 00:00:00 2001 From: Jerome Date: Mon, 7 Jul 2025 12:35:52 +0100 Subject: [PATCH 2/3] Added cors settings to simpleStreamableHttp example server --- src/examples/server/simpleStreamableHttp.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/examples/server/simpleStreamableHttp.ts b/src/examples/server/simpleStreamableHttp.ts index 3d5235430..029fff77a 100644 --- a/src/examples/server/simpleStreamableHttp.ts +++ b/src/examples/server/simpleStreamableHttp.ts @@ -11,6 +11,8 @@ import { setupAuthServer } from './demoInMemoryOAuthProvider.js'; import { OAuthMetadata } from 'src/shared/auth.js'; import { checkResourceAllowed } from 'src/shared/auth-utils.js'; +import cors from 'cors'; + // Check for OAuth flag const useOAuth = process.argv.includes('--oauth'); const strictOAuth = process.argv.includes('--oauth-strict'); @@ -420,12 +422,18 @@ const getServer = () => { return server; }; -const MCP_PORT = 3000; -const AUTH_PORT = 3001; +const MCP_PORT = process.env.MCP_PORT ? parseInt(process.env.MCP_PORT, 10) : 3000; +const AUTH_PORT = process.env.MCP_AUTH_PORT ? parseInt(process.env.MCP_AUTH_PORT, 10) : 3001; const app = express(); app.use(express.json()); +// Allow CORS all domains, expose the Mcp-Session-Id header +app.use(cors({ + origin: '*', // Allow all origins + exposedHeaders: ["Mcp-Session-Id"] +})); + // Set up OAuth if enabled let authMiddleware = null; if (useOAuth) { From 7c374fd81e67abc8889a6931cce702c6ae9fb628 Mon Sep 17 00:00:00 2001 From: Jerome Date: Mon, 7 Jul 2025 12:40:28 +0100 Subject: [PATCH 3/3] Merged the CORS tips --- README.md | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 651893531..b91f004af 100644 --- a/README.md +++ b/README.md @@ -570,18 +570,7 @@ app.listen(3000); ``` > [!TIP] -> When using this in a remote environment, make sure to allow the header parameter `mcp-session-id` in CORS. Otherwise, it may result in a `Bad Request: No valid session ID provided` error. -> -> For example, in Node.js you can configure it like this: -> -> ```ts -> app.use( -> cors({ -> origin: ['https://your-remote-domain.com, https://your-other-remote-domain.com'], -> exposedHeaders: ['mcp-session-id'], -> allowedHeaders: ['Content-Type', 'mcp-session-id'], -> }) -> ); +> When using this in a remote environment, make sure to allow the header parameter `mcp-session-id` in CORS. Otherwise, it may result in a `Bad Request: No valid session ID provided` error. Read the following section for examples. > ``` @@ -594,8 +583,10 @@ import cors from 'cors'; // Add CORS middleware before your MCP routes app.use(cors({ - origin: '*', // Configure appropriately for production + origin: '*', // Configure appropriately for production, for example: + // origin: ['https://your-remote-domain.com, https://your-other-remote-domain.com'], exposedHeaders: ['Mcp-Session-Id'] + allowedHeaders: ['Content-Type', 'mcp-session-id'], })); ```