From 20d6ec6e65e96d34cd349dece4fdd3d3df87df7a Mon Sep 17 00:00:00 2001 From: Paul Carleton Date: Wed, 25 Feb 2026 19:28:09 +0000 Subject: [PATCH 1/5] docs: make DNS rebinding protection more prominent in server.md - Add WARNING callout after the first Streamable HTTP example noting that NodeStreamableHTTPServerTransport does not include DNS rebinding protection - Link to createMcpExpressApp()/createMcpHonoApp() as recommended alternatives - Expand the DNS rebinding section with context on the attack vector - Mention createMcpHonoApp() for Web Standard runtimes - Add guidance for custom framework users to implement Host header validation Closes PCP-57 --- docs/server.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/docs/server.md b/docs/server.md index 7a6a5d9fe..fba7a8c44 100644 --- a/docs/server.md +++ b/docs/server.md @@ -37,6 +37,9 @@ const transport = new NodeStreamableHTTPServerTransport({ await server.connect(transport); ``` +> [!WARNING] +> `NodeStreamableHTTPServerTransport` does **not** include DNS rebinding protection. If your server listens on localhost, use [`createMcpExpressApp()`](#dns-rebinding-protection) or [`createMcpHonoApp()`](#dns-rebinding-protection) instead — they enable protection by default. See [DNS rebinding protection](#dns-rebinding-protection) for details. + > [!NOTE] > For full runnable examples, see [`simpleStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/simpleStreamableHttp.ts) (sessions, logging, tasks, elicitation, auth hooks), [`jsonResponseStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/jsonResponseStreamableHttp.ts) (`enableJsonResponse: true`, no SSE), and [`standaloneSseWithGetStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/standaloneSseWithGetStreamableHttp.ts) (notifications with Streamable HTTP GET + SSE). > @@ -438,7 +441,9 @@ Task-based execution enables "call-now, fetch-later" patterns for long-running o ### DNS rebinding protection -MCP servers running on localhost are vulnerable to DNS rebinding attacks. Use `createMcpExpressApp()` from `@modelcontextprotocol/express` to create an Express app with DNS rebinding protection enabled by default: +MCP servers running on localhost are vulnerable to [DNS rebinding attacks](https://en.wikipedia.org/wiki/DNS_rebinding), which allow malicious websites to bypass browser same-origin policies and interact with local services. **All localhost MCP servers should use DNS rebinding protection.** + +The recommended approach is to use `createMcpExpressApp()` (from `@modelcontextprotocol/express`) or `createMcpHonoApp()` (from `@modelcontextprotocol/hono`), which enable Host header validation by default: ```ts source="../examples/server/src/serverGuide.examples.ts#dnsRebinding_basic" // Default: DNS rebinding protection auto-enabled (host is 127.0.0.1) @@ -460,6 +465,10 @@ const app = createMcpExpressApp({ }); ``` +`createMcpHonoApp()` from `@modelcontextprotocol/hono` provides the same protection for Hono-based servers and Web Standard runtimes (Cloudflare Workers, Deno, Bun). + +If you use `NodeStreamableHTTPServerTransport` directly with your own HTTP framework, you must implement Host header validation yourself. See the [`hostHeaderValidation`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/packages/middleware/express/src/express.ts) middleware source for reference. + ## More server features The sections above cover the essentials. The table below links to additional capabilities demonstrated in the runnable examples. From ba83a11a20f8e6e155a84dbd1bd5b48ba5cd1620 Mon Sep 17 00:00:00 2001 From: Paul Carleton Date: Wed, 25 Feb 2026 19:32:51 +0000 Subject: [PATCH 2/5] docs: reword DNS rebinding warning to avoid implying missing protection --- docs/server.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/server.md b/docs/server.md index fba7a8c44..5423e8619 100644 --- a/docs/server.md +++ b/docs/server.md @@ -38,7 +38,7 @@ await server.connect(transport); ``` > [!WARNING] -> `NodeStreamableHTTPServerTransport` does **not** include DNS rebinding protection. If your server listens on localhost, use [`createMcpExpressApp()`](#dns-rebinding-protection) or [`createMcpHonoApp()`](#dns-rebinding-protection) instead — they enable protection by default. See [DNS rebinding protection](#dns-rebinding-protection) for details. +> If your server listens on localhost, use [`createMcpExpressApp()`](#dns-rebinding-protection) or [`createMcpHonoApp()`](#dns-rebinding-protection) instead — they include [DNS rebinding protection](#dns-rebinding-protection) by default. > [!NOTE] > For full runnable examples, see [`simpleStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/simpleStreamableHttp.ts) (sessions, logging, tasks, elicitation, auth hooks), [`jsonResponseStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/jsonResponseStreamableHttp.ts) (`enableJsonResponse: true`, no SSE), and [`standaloneSseWithGetStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/standaloneSseWithGetStreamableHttp.ts) (notifications with Streamable HTTP GET + SSE). From 02e6a6fef4c23260c36c7fdd63142c4c66966e87 Mon Sep 17 00:00:00 2001 From: Paul Carleton Date: Wed, 25 Feb 2026 19:38:32 +0000 Subject: [PATCH 3/5] docs: lead with createMcpExpressApp() example in Streamable HTTP section Replace the minimal stateful example (which wasn't self-contained anyway) with a minimal stateless example using createMcpExpressApp(), which includes DNS rebinding protection by default. Link to simpleStreamableHttp.ts for the full stateful pattern. --- docs/server.md | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/docs/server.md b/docs/server.md index 5423e8619..760dcbbbe 100644 --- a/docs/server.md +++ b/docs/server.md @@ -25,20 +25,24 @@ Streamable HTTP is the HTTP‑based transport. It supports: - Optional JSON‑only response mode with no SSE - Session management and resumability -A minimal stateful setup: +A minimal stateless server using `createMcpExpressApp()`, which includes [DNS rebinding protection](#dns-rebinding-protection) by default: -```ts source="../examples/server/src/serverGuide.examples.ts#streamableHttp_stateful" -const server = new McpServer({ name: 'my-server', version: '1.0.0' }); +```ts +const app = createMcpExpressApp(); -const transport = new NodeStreamableHTTPServerTransport({ - sessionIdGenerator: () => randomUUID() +app.post('/mcp', async (req, res) => { + const server = new McpServer({ name: 'my-server', version: '1.0.0' }); + const transport = new NodeStreamableHTTPServerTransport({ + sessionIdGenerator: undefined // stateless + }); + await server.connect(transport); + await transport.handleRequest(req, res, req.body); }); -await server.connect(transport); +app.listen(3000, '127.0.0.1'); ``` -> [!WARNING] -> If your server listens on localhost, use [`createMcpExpressApp()`](#dns-rebinding-protection) or [`createMcpHonoApp()`](#dns-rebinding-protection) instead — they include [DNS rebinding protection](#dns-rebinding-protection) by default. +For stateful servers with session management, see [`simpleStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/simpleStreamableHttp.ts). > [!NOTE] > For full runnable examples, see [`simpleStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/simpleStreamableHttp.ts) (sessions, logging, tasks, elicitation, auth hooks), [`jsonResponseStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/jsonResponseStreamableHttp.ts) (`enableJsonResponse: true`, no SSE), and [`standaloneSseWithGetStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/standaloneSseWithGetStreamableHttp.ts) (notifications with Streamable HTTP GET + SSE). From eb9ccf6573222f10508cffc32d6b9473daaae74a Mon Sep 17 00:00:00 2001 From: Paul Carleton Date: Wed, 25 Feb 2026 19:40:06 +0000 Subject: [PATCH 4/5] docs: add DNS rebinding warning before raw transport examples --- docs/server.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/server.md b/docs/server.md index 760dcbbbe..eb79961d9 100644 --- a/docs/server.md +++ b/docs/server.md @@ -49,6 +49,9 @@ For stateful servers with session management, see [`simpleStreamableHttp.ts`](ht > > For protocol details, see [Transports](https://modelcontextprotocol.io/specification/latest/basic/transports) in the MCP specification. +> [!WARNING] +> If your server listens on localhost, use [`createMcpExpressApp()`](#dns-rebinding-protection) or [`createMcpHonoApp()`](#dns-rebinding-protection) instead of using `NodeStreamableHTTPServerTransport` directly — they include [DNS rebinding protection](#dns-rebinding-protection) by default. + #### Stateless vs stateful sessions Streamable HTTP can run: From 077286e47eab0a9d2acf40849eb2256e5dc87c1f Mon Sep 17 00:00:00 2001 From: Paul Carleton Date: Thu, 26 Feb 2026 14:19:42 +0000 Subject: [PATCH 5/5] Apply suggestion from Jenn --- docs/server.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/server.md b/docs/server.md index eb79961d9..3c246ac12 100644 --- a/docs/server.md +++ b/docs/server.md @@ -448,7 +448,7 @@ Task-based execution enables "call-now, fetch-later" patterns for long-running o ### DNS rebinding protection -MCP servers running on localhost are vulnerable to [DNS rebinding attacks](https://en.wikipedia.org/wiki/DNS_rebinding), which allow malicious websites to bypass browser same-origin policies and interact with local services. **All localhost MCP servers should use DNS rebinding protection.** +Under normal circumstances, cross-origin browser restrictions limit what a malicious website can do to your localhost server. [DNS rebinding attacks](https://en.wikipedia.org/wiki/DNS_rebinding) get around those restrictions entirely by making the requests appear as same-origin, since the attacking domain resolves to localhost. Validating the host header on the server side protects against this scenario. **All localhost MCP servers should use DNS rebinding protection.** The recommended approach is to use `createMcpExpressApp()` (from `@modelcontextprotocol/express`) or `createMcpHonoApp()` (from `@modelcontextprotocol/hono`), which enable Host header validation by default: