Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 23 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -570,20 +570,31 @@ 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.
> ```


#### CORS Configuration for Browser-Based Clients

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this was added in #633

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah up - that's just above this, I've merged them

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, for example:
// origin: ['https://your-remote-domain.com, https://your-other-remote-domain.com'],
exposedHeaders: ['Mcp-Session-Id']
allowedHeaders: ['Content-Type', '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:
Expand Down
7 changes: 7 additions & 0 deletions src/examples/server/jsonResponseStreamableHttp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 } = {};

Expand Down
7 changes: 7 additions & 0 deletions src/examples/server/simpleStatelessStreamableHttp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd probably add a very big "WARNING" here not to include "*"

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If your MCP server will be accessible by browsers, then you might actually want this

exposedHeaders: ['Mcp-Session-Id']
}));

app.post('/mcp', async (req: Request, res: Response) => {
const server = getServer();
try {
Expand Down
12 changes: 10 additions & 2 deletions src/examples/server/simpleStreamableHttp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -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) {
Expand Down
7 changes: 7 additions & 0 deletions src/examples/server/sseAndStreamableHttpCompatibleServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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<string, StreamableHTTPServerTransport | SSEServerTransport> = {};

Expand Down