refactor: make Server class framework-agnostic by moving express to separate module (#1223)

This commit is contained in:
Wuma 2025-12-03 19:58:42 +08:00 • committed by GitHub
commit 7c9257cdf5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 93 additions and 92 deletions

View file

@ -71,7 +71,7 @@ For more detailed patterns (stateless vs stateful, JSON response mode, CORS, DNS
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:
```typescript
import { createMcpExpressApp } from '@modelcontextprotocol/sdk/server/index.js';
import { createMcpExpressApp } from '@modelcontextprotocol/sdk/server/express.js';
// Protection auto-enabled (default host is 127.0.0.1)
const app = createMcpExpressApp();

View file

@ -12,7 +12,7 @@ import { type Request, type Response } from 'express';
import { McpServer } from '../../server/mcp.js';
import { StreamableHTTPServerTransport } from '../../server/streamableHttp.js';
import { isInitializeRequest } from '../../types.js';
import { createMcpExpressApp } from '../../server/index.js';
import { createMcpExpressApp } from '../../server/express.js';
// Create MCP server - it will automatically use AjvJsonSchemaValidator with sensible defaults
// The validator supports format validation (email, date, etc.) if ajv-formats is installed

View file

@ -11,7 +11,7 @@ import express, { Request, Response } from 'express';
import { randomUUID } from 'node:crypto';
import { z } from 'zod';
import { McpServer } from '../../server/mcp.js';
import { createMcpExpressApp } from '../../server/index.js';
import { createMcpExpressApp } from '../../server/express.js';
import { StreamableHTTPServerTransport } from '../../server/streamableHttp.js';
import { getOAuthProtectedResourceMetadataUrl, mcpAuthMetadataRouter } from '../../server/auth/router.js';
import { requireBearerAuth } from '../../server/auth/middleware/bearerAuth.js';

View file

@ -4,7 +4,7 @@ import { McpServer } from '../../server/mcp.js';
import { StreamableHTTPServerTransport } from '../../server/streamableHttp.js';
import * as z from 'zod/v4';
import { CallToolResult, isInitializeRequest } from '../../types.js';
import { createMcpExpressApp } from '../../server/index.js';
import { createMcpExpressApp } from '../../server/express.js';
// Create an MCP server with implementation details
const getServer = () => {

View file

@ -3,7 +3,7 @@ import { McpServer } from '../../server/mcp.js';
import { SSEServerTransport } from '../../server/sse.js';
import * as z from 'zod/v4';
import { CallToolResult } from '../../types.js';
import { createMcpExpressApp } from '../../server/index.js';
import { createMcpExpressApp } from '../../server/express.js';
/**
* This example server demonstrates the deprecated HTTP+SSE transport

View file

@ -3,7 +3,7 @@ import { McpServer } from '../../server/mcp.js';
import { StreamableHTTPServerTransport } from '../../server/streamableHttp.js';
import * as z from 'zod/v4';
import { CallToolResult, GetPromptResult, ReadResourceResult } from '../../types.js';
import { createMcpExpressApp } from '../../server/index.js';
import { createMcpExpressApp } from '../../server/express.js';
const getServer = () => {
// Create an MCP server with implementation details

View file

@ -5,7 +5,7 @@ import { McpServer } from '../../server/mcp.js';
import { StreamableHTTPServerTransport } from '../../server/streamableHttp.js';
import { getOAuthProtectedResourceMetadataUrl, mcpAuthMetadataRouter } from '../../server/auth/router.js';
import { requireBearerAuth } from '../../server/auth/middleware/bearerAuth.js';
import { createMcpExpressApp } from '../../server/index.js';
import { createMcpExpressApp } from '../../server/express.js';
import {
CallToolResult,
ElicitResultSchema,

View file

@ -11,7 +11,8 @@
import { Request, Response } from 'express';
import { randomUUID } from 'node:crypto';
import { createMcpExpressApp, Server } from '../../server/index.js';
import { Server } from '../../server/index.js';
import { createMcpExpressApp } from '../../server/express.js';
import { StreamableHTTPServerTransport } from '../../server/streamableHttp.js';
import {
CallToolResult,

View file

@ -6,7 +6,7 @@ import { SSEServerTransport } from '../../server/sse.js';
import * as z from 'zod/v4';
import { CallToolResult, isInitializeRequest } from '../../types.js';
import { InMemoryEventStore } from '../shared/inMemoryEventStore.js';
import { createMcpExpressApp } from '../../server/index.js';
import { createMcpExpressApp } from '../../server/express.js';
/**
* This example server demonstrates backwards compatibility with both:

View file

@ -15,7 +15,7 @@
import { Request, Response } from 'express';
import { randomUUID } from 'node:crypto';
import { McpServer } from '../../server/mcp.js';
import { createMcpExpressApp } from '../../server/index.js';
import { createMcpExpressApp } from '../../server/express.js';
import { StreamableHTTPServerTransport } from '../../server/streamableHttp.js';
import { CallToolResult } from '../../types.js';
import { InMemoryEventStore } from '../shared/inMemoryEventStore.js';

View file

@ -3,7 +3,7 @@ import { randomUUID } from 'node:crypto';
import { McpServer } from '../../server/mcp.js';
import { StreamableHTTPServerTransport } from '../../server/streamableHttp.js';
import { isInitializeRequest, ReadResourceResult } from '../../types.js';
import { createMcpExpressApp } from '../../server/index.js';
import { createMcpExpressApp } from '../../server/express.js';
// Create an MCP server with implementation details
const server = new McpServer({

74
src/server/express.ts Normal file
View file

@ -0,0 +1,74 @@
import express, { Express } from 'express';
import { hostHeaderValidation, localhostHostValidation } from './middleware/hostHeaderValidation.js';
/**
* 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;
}

View file

@ -3,7 +3,7 @@ import supertest from 'supertest';
import { Client } from '../client/index.js';
import { InMemoryTransport } from '../inMemory.js';
import type { Transport } from '../shared/transport.js';
import { createMcpExpressApp } from './index.js';
import { createMcpExpressApp } from './express.js';
import {
CreateMessageRequestSchema,
CreateMessageResultSchema,

View file

@ -1,6 +1,4 @@
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,
@ -669,75 +667,3 @@ 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;
}

View file

@ -17,7 +17,7 @@ export interface SSEServerTransportOptions {
* List of allowed host header values for DNS rebinding protection.
* If not specified, host validation is disabled.
* @deprecated Use the `hostHeaderValidation` middleware from `@modelcontextprotocol/sdk/server/middleware/hostHeaderValidation.js` instead,
* or use `createMcpExpressApp` from `@modelcontextprotocol/sdk/server/index.js` which includes localhost protection by default.
* or use `createMcpExpressApp` from `@modelcontextprotocol/sdk/server/express.js` which includes localhost protection by default.
*/
allowedHosts?: string[];
@ -25,7 +25,7 @@ export interface SSEServerTransportOptions {
* List of allowed origin header values for DNS rebinding protection.
* If not specified, origin validation is disabled.
* @deprecated Use the `hostHeaderValidation` middleware from `@modelcontextprotocol/sdk/server/middleware/hostHeaderValidation.js` instead,
* or use `createMcpExpressApp` from `@modelcontextprotocol/sdk/server/index.js` which includes localhost protection by default.
* or use `createMcpExpressApp` from `@modelcontextprotocol/sdk/server/express.js` which includes localhost protection by default.
*/
allowedOrigins?: string[];
@ -33,7 +33,7 @@ export interface SSEServerTransportOptions {
* Enable DNS rebinding protection (requires allowedHosts and/or allowedOrigins to be configured).
* Default is false for backwards compatibility.
* @deprecated Use the `hostHeaderValidation` middleware from `@modelcontextprotocol/sdk/server/middleware/hostHeaderValidation.js` instead,
* or use `createMcpExpressApp` from `@modelcontextprotocol/sdk/server/index.js` which includes localhost protection by default.
* or use `createMcpExpressApp` from `@modelcontextprotocol/sdk/server/express.js` which includes localhost protection by default.
*/
enableDnsRebindingProtection?: boolean;
}

View file

@ -105,7 +105,7 @@ export interface StreamableHTTPServerTransportOptions {
* List of allowed host header values for DNS rebinding protection.
* If not specified, host validation is disabled.
* @deprecated Use the `hostHeaderValidation` middleware from `@modelcontextprotocol/sdk/server/middleware/hostHeaderValidation.js` instead,
* or use `createMcpExpressApp` from `@modelcontextprotocol/sdk/server/index.js` which includes localhost protection by default.
* or use `createMcpExpressApp` from `@modelcontextprotocol/sdk/server/express.js` which includes localhost protection by default.
*/
allowedHosts?: string[];
@ -113,7 +113,7 @@ export interface StreamableHTTPServerTransportOptions {
* List of allowed origin header values for DNS rebinding protection.
* If not specified, origin validation is disabled.
* @deprecated Use the `hostHeaderValidation` middleware from `@modelcontextprotocol/sdk/server/middleware/hostHeaderValidation.js` instead,
* or use `createMcpExpressApp` from `@modelcontextprotocol/sdk/server/index.js` which includes localhost protection by default.
* or use `createMcpExpressApp` from `@modelcontextprotocol/sdk/server/express.js` which includes localhost protection by default.
*/
allowedOrigins?: string[];
@ -121,7 +121,7 @@ export interface StreamableHTTPServerTransportOptions {
* Enable DNS rebinding protection (requires allowedHosts and/or allowedOrigins to be configured).
* Default is false for backwards compatibility.
* @deprecated Use the `hostHeaderValidation` middleware from `@modelcontextprotocol/sdk/server/middleware/hostHeaderValidation.js` instead,
* or use `createMcpExpressApp` from `@modelcontextprotocol/sdk/server/index.js` which includes localhost protection by default.
* or use `createMcpExpressApp` from `@modelcontextprotocol/sdk/server/express.js` which includes localhost protection by default.
*/
enableDnsRebindingProtection?: boolean;