Skip to content

Commit 1bd56ee

Browse files
Merge pull request modelcontextprotocol#713 from modelcontextprotocol/jerome/cors-browser-support
feat: Add CORS configuration for browser-based MCP clients
2 parents 593b1f7 + 0aea3fe commit 1bd56ee

File tree

5 files changed

+54
-14
lines changed

5 files changed

+54
-14
lines changed

README.md

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -570,20 +570,31 @@ app.listen(3000);
570570
```
571571

572572
> [!TIP]
573-
> 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.
574-
>
575-
> For example, in Node.js you can configure it like this:
576-
>
577-
> ```ts
578-
> app.use(
579-
> cors({
580-
> origin: ['https://your-remote-domain.com, https://your-other-remote-domain.com'],
581-
> exposedHeaders: ['mcp-session-id'],
582-
> allowedHeaders: ['Content-Type', 'mcp-session-id'],
583-
> })
584-
> );
573+
> 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.
585574
> ```
586575
576+
577+
#### CORS Configuration for Browser-Based Clients
578+
579+
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:
580+
581+
```typescript
582+
import cors from 'cors';
583+
584+
// Add CORS middleware before your MCP routes
585+
app.use(cors({
586+
origin: '*', // Configure appropriately for production, for example:
587+
// origin: ['https://your-remote-domain.com, https://your-other-remote-domain.com'],
588+
exposedHeaders: ['Mcp-Session-Id']
589+
allowedHeaders: ['Content-Type', 'mcp-session-id'],
590+
}));
591+
```
592+
593+
This configuration is necessary because:
594+
- The MCP streamable HTTP transport uses the `Mcp-Session-Id` header for session management
595+
- Browsers restrict access to response headers unless explicitly exposed via CORS
596+
- Without this configuration, browser-based clients won't be able to read the session ID from initialization responses
597+
587598
#### Without Session Management (Stateless)
588599

589600
For simpler use cases where session management isn't needed:

src/examples/server/jsonResponseStreamableHttp.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { McpServer } from '../../server/mcp.js';
44
import { StreamableHTTPServerTransport } from '../../server/streamableHttp.js';
55
import { z } from 'zod';
66
import { CallToolResult, isInitializeRequest } from '../../types.js';
7+
import cors from 'cors';
78

89

910
// Create an MCP server with implementation details
@@ -81,6 +82,12 @@ const getServer = () => {
8182
const app = express();
8283
app.use(express.json());
8384

85+
// Configure CORS to expose Mcp-Session-Id header for browser-based clients
86+
app.use(cors({
87+
origin: '*', // Allow all origins - adjust as needed for production
88+
exposedHeaders: ['Mcp-Session-Id']
89+
}));
90+
8491
// Map to store transports by session ID
8592
const transports: { [sessionId: string]: StreamableHTTPServerTransport } = {};
8693

src/examples/server/simpleStatelessStreamableHttp.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { McpServer } from '../../server/mcp.js';
33
import { StreamableHTTPServerTransport } from '../../server/streamableHttp.js';
44
import { z } from 'zod';
55
import { CallToolResult, GetPromptResult, ReadResourceResult } from '../../types.js';
6+
import cors from 'cors';
67

78
const getServer = () => {
89
// Create an MCP server with implementation details
@@ -96,6 +97,12 @@ const getServer = () => {
9697
const app = express();
9798
app.use(express.json());
9899

100+
// Configure CORS to expose Mcp-Session-Id header for browser-based clients
101+
app.use(cors({
102+
origin: '*', // Allow all origins - adjust as needed for production
103+
exposedHeaders: ['Mcp-Session-Id']
104+
}));
105+
99106
app.post('/mcp', async (req: Request, res: Response) => {
100107
const server = getServer();
101108
try {

src/examples/server/simpleStreamableHttp.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import { setupAuthServer } from './demoInMemoryOAuthProvider.js';
1111
import { OAuthMetadata } from 'src/shared/auth.js';
1212
import { checkResourceAllowed } from 'src/shared/auth-utils.js';
1313

14+
import cors from 'cors';
15+
1416
// Check for OAuth flag
1517
const useOAuth = process.argv.includes('--oauth');
1618
const strictOAuth = process.argv.includes('--oauth-strict');
@@ -420,12 +422,18 @@ const getServer = () => {
420422
return server;
421423
};
422424

423-
const MCP_PORT = 3000;
424-
const AUTH_PORT = 3001;
425+
const MCP_PORT = process.env.MCP_PORT ? parseInt(process.env.MCP_PORT, 10) : 3000;
426+
const AUTH_PORT = process.env.MCP_AUTH_PORT ? parseInt(process.env.MCP_AUTH_PORT, 10) : 3001;
425427

426428
const app = express();
427429
app.use(express.json());
428430

431+
// Allow CORS all domains, expose the Mcp-Session-Id header
432+
app.use(cors({
433+
origin: '*', // Allow all origins
434+
exposedHeaders: ["Mcp-Session-Id"]
435+
}));
436+
429437
// Set up OAuth if enabled
430438
let authMiddleware = null;
431439
if (useOAuth) {

src/examples/server/sseAndStreamableHttpCompatibleServer.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { SSEServerTransport } from '../../server/sse.js';
66
import { z } from 'zod';
77
import { CallToolResult, isInitializeRequest } from '../../types.js';
88
import { InMemoryEventStore } from '../shared/inMemoryEventStore.js';
9+
import cors from 'cors';
910

1011
/**
1112
* This example server demonstrates backwards compatibility with both:
@@ -71,6 +72,12 @@ const getServer = () => {
7172
const app = express();
7273
app.use(express.json());
7374

75+
// Configure CORS to expose Mcp-Session-Id header for browser-based clients
76+
app.use(cors({
77+
origin: '*', // Allow all origins - adjust as needed for production
78+
exposedHeaders: ['Mcp-Session-Id']
79+
}));
80+
7481
// Store transports by session ID
7582
const transports: Record<string, StreamableHTTPServerTransport | SSEServerTransport> = {};
7683

0 commit comments

Comments
 (0)