Skip to content

Commit 09623e2

Browse files
pcarletonclaude
andauthored
Merge commit from fork
* Add secure default for DNS rebinding, use in examples * Update README examples to use createMcpExpressApp, bump to 1.23.0 - Update all server examples to use createMcpExpressApp() for secure defaults - Rewrite DNS Rebinding Protection section to document new middleware approach - Bump version from 1.23.0-beta.0 to 1.23.0 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Add IPv6 support, allowedHosts option, and warning for unprotected servers - Use native URL API to parse Host headers (handles IPv6 correctly) - Add [::1] (IPv6 localhost) to localhostHostValidation - Add ::1 to list of localhost hosts that get automatic protection - Add allowedHosts option to createMcpExpressApp for custom host validation - Warn when binding to 0.0.0.0 or :: without allowedHosts configured - Add tests for IPv6 and allowedHosts functionality 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Update remaining examples to use createMcpExpressApp and add DNS rebinding docs - Update simpleTaskInteractive.ts, ssePollingExample.ts, and elicitationUrlExample.ts to use createMcpExpressApp() instead of express() + express.json() - Add DNS rebinding protection section to docs/server.md explaining how to use createMcpExpressApp() and hostHeaderValidation middleware 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent cf51343 commit 09623e2

16 files changed

+387
-77
lines changed

docs/server.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,34 @@ For a minimal “getting started” experience:
6666

6767
For more detailed patterns (stateless vs stateful, JSON response mode, CORS, DNS rebind protection), see the examples above and the MCP spec sections on transports.
6868

69+
## DNS rebinding protection
70+
71+
MCP servers running on localhost are vulnerable to DNS rebinding attacks. Use `createMcpExpressApp()` to create an Express app with DNS rebinding protection enabled by default:
72+
73+
```typescript
74+
import { createMcpExpressApp } from '@modelcontextprotocol/sdk/server/index.js';
75+
76+
// Protection auto-enabled (default host is 127.0.0.1)
77+
const app = createMcpExpressApp();
78+
79+
// Protection auto-enabled for localhost
80+
const app = createMcpExpressApp({ host: 'localhost' });
81+
82+
// No auto protection when binding to all interfaces
83+
const app = createMcpExpressApp({ host: '0.0.0.0' });
84+
```
85+
86+
For custom host validation, use the middleware directly:
87+
88+
```typescript
89+
import express from 'express';
90+
import { hostHeaderValidation } from '@modelcontextprotocol/sdk/server/middleware/hostHeaderValidation.js';
91+
92+
const app = express();
93+
app.use(express.json());
94+
app.use(hostHeaderValidation(['localhost', '127.0.0.1', 'myhost.local']));
95+
```
96+
6997
## Tools, resources, and prompts
7098

7199
### Tools

src/examples/server/elicitationFormExample.ts

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@
88
// to collect *sensitive* user input via a browser.
99

1010
import { randomUUID } from 'node:crypto';
11-
import cors from 'cors';
12-
import express, { type Request, type Response } from 'express';
11+
import { type Request, type Response } from 'express';
1312
import { McpServer } from '../../server/mcp.js';
1413
import { StreamableHTTPServerTransport } from '../../server/streamableHttp.js';
1514
import { isInitializeRequest } from '../../types.js';
15+
import { createMcpExpressApp } from '../../server/index.js';
1616

1717
// Create MCP server - it will automatically use AjvJsonSchemaValidator with sensible defaults
1818
// The validator supports format validation (email, date, etc.) if ajv-formats is installed
@@ -320,16 +320,7 @@ mcpServer.registerTool(
320320
async function main() {
321321
const PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : 3000;
322322

323-
const app = express();
324-
app.use(express.json());
325-
326-
// Allow CORS for all domains, expose the Mcp-Session-Id header
327-
app.use(
328-
cors({
329-
origin: '*',
330-
exposedHeaders: ['Mcp-Session-Id']
331-
})
332-
);
323+
const app = createMcpExpressApp();
333324

334325
// Map to store transports by session ID
335326
const transports: { [sessionId: string]: StreamableHTTPServerTransport } = {};

src/examples/server/elicitationUrlExample.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import express, { Request, Response } from 'express';
1111
import { randomUUID } from 'node:crypto';
1212
import { z } from 'zod';
1313
import { McpServer } from '../../server/mcp.js';
14+
import { createMcpExpressApp } from '../../server/index.js';
1415
import { StreamableHTTPServerTransport } from '../../server/streamableHttp.js';
1516
import { getOAuthProtectedResourceMetadataUrl, mcpAuthMetadataRouter } from '../../server/auth/router.js';
1617
import { requireBearerAuth } from '../../server/auth/middleware/bearerAuth.js';
@@ -214,8 +215,7 @@ function completeURLElicitation(elicitationId: string) {
214215
const MCP_PORT = process.env.MCP_PORT ? parseInt(process.env.MCP_PORT, 10) : 3000;
215216
const AUTH_PORT = process.env.MCP_AUTH_PORT ? parseInt(process.env.MCP_AUTH_PORT, 10) : 3001;
216217

217-
const app = express();
218-
app.use(express.json());
218+
const app = createMcpExpressApp();
219219

220220
// Allow CORS all domains, expose the Mcp-Session-Id header
221221
app.use(

src/examples/server/jsonResponseStreamableHttp.ts

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import express, { Request, Response } from 'express';
1+
import { Request, Response } from 'express';
22
import { randomUUID } from 'node:crypto';
33
import { McpServer } from '../../server/mcp.js';
44
import { StreamableHTTPServerTransport } from '../../server/streamableHttp.js';
55
import * as z from 'zod/v4';
66
import { CallToolResult, isInitializeRequest } from '../../types.js';
7-
import cors from 'cors';
7+
import { createMcpExpressApp } from '../../server/index.js';
88

99
// Create an MCP server with implementation details
1010
const getServer = () => {
@@ -90,16 +90,7 @@ const getServer = () => {
9090
return server;
9191
};
9292

93-
const app = express();
94-
app.use(express.json());
95-
96-
// Configure CORS to expose Mcp-Session-Id header for browser-based clients
97-
app.use(
98-
cors({
99-
origin: '*', // Allow all origins - adjust as needed for production
100-
exposedHeaders: ['Mcp-Session-Id']
101-
})
102-
);
93+
const app = createMcpExpressApp();
10394

10495
// Map to store transports by session ID
10596
const transports: { [sessionId: string]: StreamableHTTPServerTransport } = {};

src/examples/server/simpleSseServer.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import express, { Request, Response } from 'express';
1+
import { Request, Response } from 'express';
22
import { McpServer } from '../../server/mcp.js';
33
import { SSEServerTransport } from '../../server/sse.js';
44
import * as z from 'zod/v4';
55
import { CallToolResult } from '../../types.js';
6+
import { createMcpExpressApp } from '../../server/index.js';
67

78
/**
89
* This example server demonstrates the deprecated HTTP+SSE transport
@@ -75,8 +76,7 @@ const getServer = () => {
7576
return server;
7677
};
7778

78-
const app = express();
79-
app.use(express.json());
79+
const app = createMcpExpressApp();
8080

8181
// Store transports by session ID
8282
const transports: Record<string, SSEServerTransport> = {};

src/examples/server/simpleStatelessStreamableHttp.ts

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import express, { Request, Response } from 'express';
1+
import { Request, Response } from 'express';
22
import { McpServer } from '../../server/mcp.js';
33
import { StreamableHTTPServerTransport } from '../../server/streamableHttp.js';
44
import * as z from 'zod/v4';
55
import { CallToolResult, GetPromptResult, ReadResourceResult } from '../../types.js';
6-
import cors from 'cors';
6+
import { createMcpExpressApp } from '../../server/index.js';
77

88
const getServer = () => {
99
// Create an MCP server with implementation details
@@ -96,16 +96,7 @@ const getServer = () => {
9696
return server;
9797
};
9898

99-
const app = express();
100-
app.use(express.json());
101-
102-
// Configure CORS to expose Mcp-Session-Id header for browser-based clients
103-
app.use(
104-
cors({
105-
origin: '*', // Allow all origins - adjust as needed for production
106-
exposedHeaders: ['Mcp-Session-Id']
107-
})
108-
);
99+
const app = createMcpExpressApp();
109100

110101
app.post('/mcp', async (req: Request, res: Response) => {
111102
const server = getServer();

src/examples/server/simpleStreamableHttp.ts

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
import express, { Request, Response } from 'express';
1+
import { Request, Response } from 'express';
22
import { randomUUID } from 'node:crypto';
33
import * as z from 'zod/v4';
44
import { McpServer } from '../../server/mcp.js';
55
import { StreamableHTTPServerTransport } from '../../server/streamableHttp.js';
66
import { getOAuthProtectedResourceMetadataUrl, mcpAuthMetadataRouter } from '../../server/auth/router.js';
77
import { requireBearerAuth } from '../../server/auth/middleware/bearerAuth.js';
8+
import { createMcpExpressApp } from '../../server/index.js';
89
import {
910
CallToolResult,
1011
ElicitResultSchema,
@@ -20,8 +21,6 @@ import { setupAuthServer } from './demoInMemoryOAuthProvider.js';
2021
import { OAuthMetadata } from '../../shared/auth.js';
2122
import { checkResourceAllowed } from '../../shared/auth-utils.js';
2223

23-
import cors from 'cors';
24-
2524
// Check for OAuth flag
2625
const useOAuth = process.argv.includes('--oauth');
2726
const strictOAuth = process.argv.includes('--oauth-strict');
@@ -507,16 +506,7 @@ const getServer = () => {
507506
const MCP_PORT = process.env.MCP_PORT ? parseInt(process.env.MCP_PORT, 10) : 3000;
508507
const AUTH_PORT = process.env.MCP_AUTH_PORT ? parseInt(process.env.MCP_AUTH_PORT, 10) : 3001;
509508

510-
const app = express();
511-
app.use(express.json());
512-
513-
// Allow CORS all domains, expose the Mcp-Session-Id header
514-
app.use(
515-
cors({
516-
origin: '*', // Allow all origins
517-
exposedHeaders: ['Mcp-Session-Id']
518-
})
519-
);
509+
const app = createMcpExpressApp();
520510

521511
// Set up OAuth if enabled
522512
let authMiddleware = null;

src/examples/server/simpleTaskInteractive.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99
* creates a task, and the result is fetched via tasks/result endpoint.
1010
*/
1111

12-
import express, { Request, Response } from 'express';
12+
import { Request, Response } from 'express';
1313
import { randomUUID } from 'node:crypto';
14-
import { Server } from '../../server/index.js';
14+
import { createMcpExpressApp, Server } from '../../server/index.js';
1515
import { StreamableHTTPServerTransport } from '../../server/streamableHttp.js';
1616
import {
1717
CallToolResult,
@@ -630,8 +630,7 @@ const createServer = (): Server => {
630630
// Express App Setup
631631
// ============================================================================
632632

633-
const app = express();
634-
app.use(express.json());
633+
const app = createMcpExpressApp();
635634

636635
// Map to store transports by session ID
637636
const transports: { [sessionId: string]: StreamableHTTPServerTransport } = {};

src/examples/server/sseAndStreamableHttpCompatibleServer.ts

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import express, { Request, Response } from 'express';
1+
import { Request, Response } from 'express';
22
import { randomUUID } from 'node:crypto';
33
import { McpServer } from '../../server/mcp.js';
44
import { StreamableHTTPServerTransport } from '../../server/streamableHttp.js';
55
import { SSEServerTransport } from '../../server/sse.js';
66
import * as z from 'zod/v4';
77
import { CallToolResult, isInitializeRequest } from '../../types.js';
88
import { InMemoryEventStore } from '../shared/inMemoryEventStore.js';
9-
import cors from 'cors';
9+
import { createMcpExpressApp } from '../../server/index.js';
1010

1111
/**
1212
* This example server demonstrates backwards compatibility with both:
@@ -71,16 +71,7 @@ const getServer = () => {
7171
};
7272

7373
// Create Express application
74-
const app = express();
75-
app.use(express.json());
76-
77-
// Configure CORS to expose Mcp-Session-Id header for browser-based clients
78-
app.use(
79-
cors({
80-
origin: '*', // Allow all origins - adjust as needed for production
81-
exposedHeaders: ['Mcp-Session-Id']
82-
})
83-
);
74+
const app = createMcpExpressApp();
8475

8576
// Store transports by session ID
8677
const transports: Record<string, StreamableHTTPServerTransport | SSEServerTransport> = {};

src/examples/server/ssePollingExample.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,10 @@
1212
* Run with: npx tsx src/examples/server/ssePollingExample.ts
1313
* Test with: curl or the MCP Inspector
1414
*/
15-
import express, { Request, Response } from 'express';
15+
import { Request, Response } from 'express';
1616
import { randomUUID } from 'node:crypto';
1717
import { McpServer } from '../../server/mcp.js';
18+
import { createMcpExpressApp } from '../../server/index.js';
1819
import { StreamableHTTPServerTransport } from '../../server/streamableHttp.js';
1920
import { CallToolResult } from '../../types.js';
2021
import { InMemoryEventStore } from '../shared/inMemoryEventStore.js';
@@ -103,7 +104,7 @@ server.tool(
103104
);
104105

105106
// Set up Express app
106-
const app = express();
107+
const app = createMcpExpressApp();
107108
app.use(cors());
108109

109110
// Create event store for resumability
@@ -112,8 +113,8 @@ const eventStore = new InMemoryEventStore();
112113
// Track transports by session ID for session reuse
113114
const transports = new Map<string, StreamableHTTPServerTransport>();
114115

115-
// Handle all MCP requests - use express.json() only for this route
116-
app.all('/mcp', express.json(), async (req: Request, res: Response) => {
116+
// Handle all MCP requests
117+
app.all('/mcp', async (req: Request, res: Response) => {
117118
const sessionId = req.headers['mcp-session-id'] as string | undefined;
118119

119120
// Reuse existing transport or create new one

0 commit comments

Comments
 (0)