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>
This commit is contained in:
Paul Carleton 2025-12-02 13:23:06 +00:00 • committed by GitHub
commit 09623e2aa5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1,4 +1,6 @@
import express, { Express } from 'express';
import { mergeCapabilities, Protocol, type NotificationOptions, type ProtocolOptions, type RequestOptions } from '../shared/protocol.js';
import { hostHeaderValidation, localhostHostValidation } from './middleware/hostHeaderValidation.js';
import {
type ClientCapabilities,
type CreateMessageRequest,
@ -667,3 +669,75 @@ export class Server<
return this.notification({ method: 'notifications/prompts/list_changed' });
}
}
/**
* Options for creating an MCP Express application.
*/
export interface CreateMcpExpressAppOptions {
/**
* The hostname to bind to. Defaults to '127.0.0.1'.
* When set to '127.0.0.1', 'localhost', or '::1', DNS rebinding protection is automatically enabled.
*/
host?: string;
/**
* List of allowed hostnames for DNS rebinding protection.
* If provided, host header validation will be applied using this list.
* For IPv6, provide addresses with brackets (e.g., '[::1]').
*
* This is useful when binding to '0.0.0.0' or '::' but still wanting
* to restrict which hostnames are allowed.
*/
allowedHosts?: string[];
}
/**
* Creates an Express application pre-configured for MCP servers.
*
* When the host is '127.0.0.1', 'localhost', or '::1' (the default is '127.0.0.1'),
* DNS rebinding protection middleware is automatically applied to protect against
* DNS rebinding attacks on localhost servers.
*
* @param options - Configuration options
* @returns A configured Express application
*
* @example
* ```typescript
* // Basic usage - defaults to 127.0.0.1 with DNS rebinding protection
* const app = createMcpExpressApp();
*
* // Custom host - DNS rebinding protection only applied for localhost hosts
* const app = createMcpExpressApp({ host: '0.0.0.0' }); // No automatic DNS rebinding protection
* const app = createMcpExpressApp({ host: 'localhost' }); // DNS rebinding protection enabled
*
* // Custom allowed hosts for non-localhost binding
* const app = createMcpExpressApp({ host: '0.0.0.0', allowedHosts: ['myapp.local', 'localhost'] });
* ```
*/
export function createMcpExpressApp(options: CreateMcpExpressAppOptions = {}): Express {
const { host = '127.0.0.1', allowedHosts } = options;
const app = express();
app.use(express.json());
// If allowedHosts is explicitly provided, use that for validation
if (allowedHosts) {
app.use(hostHeaderValidation(allowedHosts));
} else {
// Apply DNS rebinding protection automatically for localhost hosts
const localhostHosts = ['127.0.0.1', 'localhost', '::1'];
if (localhostHosts.includes(host)) {
app.use(localhostHostValidation());
} else if (host === '0.0.0.0' || host === '::') {
// Warn when binding to all interfaces without DNS rebinding protection
// eslint-disable-next-line no-console
console.warn(
`Warning: Server is binding to ${host} without DNS rebinding protection. ` +
'Consider using the allowedHosts option to restrict allowed hosts, ' +
'or use authentication to protect your server.'
);
}
}
return app;
}