Skip to content

Commit 37fee41

Browse files
docs: add transport security troubleshooting guide
1 parent 19fe9fa commit 37fee41

2 files changed

Lines changed: 78 additions & 0 deletions

File tree

docs/transport-security.md

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# Transport Security
2+
3+
HTTP-based MCP transports include optional DNS rebinding protection. This protects local servers from browser-based requests that try to reach a local MCP endpoint through an unexpected `Host` or `Origin` header.
4+
5+
## When protection is enabled automatically
6+
7+
For Streamable HTTP and SSE apps, the SDK enables DNS rebinding protection automatically when the app is created for a loopback host:
8+
9+
- `127.0.0.1`
10+
- `localhost`
11+
- `::1`
12+
13+
In that mode, the SDK allows loopback hosts and origins with any port, such as `localhost:*` and `127.0.0.1:*`.
14+
15+
## Understanding `421 Invalid Host header`
16+
17+
A `421 Invalid Host header` response means DNS rebinding protection is enabled and the incoming `Host` header is not in `allowed_hosts`.
18+
19+
This commonly happens when a local server is reached through a different hostname, for example:
20+
21+
- a reverse proxy
22+
- a tunnel
23+
- a container or Kubernetes ingress
24+
- a custom development domain
25+
26+
Configure the expected external host instead of disabling protection by default.
27+
28+
```python
29+
from mcp.server.transport_security import TransportSecuritySettings
30+
31+
transport_security = TransportSecuritySettings(
32+
enable_dns_rebinding_protection=True,
33+
allowed_hosts=["mcp.example.com"],
34+
allowed_origins=["https://mcp.example.com"],
35+
)
36+
37+
app = server.streamable_http_app(
38+
host="0.0.0.0",
39+
transport_security=transport_security,
40+
)
41+
```
42+
43+
## Allowing development ports
44+
45+
Use a `:*` suffix when the port changes between runs:
46+
47+
```python
48+
from mcp.server.transport_security import TransportSecuritySettings
49+
50+
transport_security = TransportSecuritySettings(
51+
enable_dns_rebinding_protection=True,
52+
allowed_hosts=["localhost:*", "127.0.0.1:*"],
53+
allowed_origins=["http://localhost:*", "http://127.0.0.1:*"],
54+
)
55+
```
56+
57+
The wildcard only matches the port portion. For example, `localhost:*` allows `localhost:8000`, not arbitrary hostnames.
58+
59+
## Origin handling
60+
61+
Requests without an `Origin` header are allowed because same-origin and non-browser clients may omit it.
62+
63+
If an `Origin` header is present and not allowed, the SDK returns `403 Invalid Origin header`.
64+
65+
## Disabling protection
66+
67+
Only disable DNS rebinding protection when another trusted layer already validates requests, such as a reverse proxy or gateway that enforces the expected host and origin.
68+
69+
```python
70+
from mcp.server.transport_security import TransportSecuritySettings
71+
72+
transport_security = TransportSecuritySettings(
73+
enable_dns_rebinding_protection=False,
74+
)
75+
```
76+
77+
Prefer configuring `allowed_hosts` and `allowed_origins` whenever possible.

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ nav:
1818
- Concepts: concepts.md
1919
- Low-Level Server: low-level-server.md
2020
- Authorization: authorization.md
21+
- Transport Security: transport-security.md
2122
- Testing: testing.md
2223
- API Reference: api/
2324

0 commit comments

Comments
 (0)