Skip to content

Commit 8e02cda

Browse files
committed
docs: explain transport security for proxies
1 parent 161834d commit 8e02cda

1 file changed

Lines changed: 70 additions & 0 deletions

File tree

README.md

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
- [Claude Desktop Integration](#claude-desktop-integration)
5555
- [Direct Execution](#direct-execution)
5656
- [Streamable HTTP Transport](#streamable-http-transport)
57+
- [DNS Rebinding Protection](#dns-rebinding-protection)
5758
- [CORS Configuration for Browser-Based Clients](#cors-configuration-for-browser-based-clients)
5859
- [Mounting to an Existing ASGI Server](#mounting-to-an-existing-asgi-server)
5960
- [StreamableHTTP servers](#streamablehttp-servers)
@@ -1280,6 +1281,75 @@ if __name__ == "__main__":
12801281
_Full example: [examples/snippets/servers/streamable_config.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/streamable_config.py)_
12811282
<!-- /snippet-source -->
12821283

1284+
#### DNS Rebinding Protection
1285+
1286+
Streamable HTTP and SSE transports can validate incoming `Host` and `Origin`
1287+
headers to protect local servers from DNS rebinding attacks. This protection is
1288+
enabled automatically for localhost apps. If a request is rejected with
1289+
`421 Invalid Host header`, configure the expected external host or disable the
1290+
check only when another trusted layer already provides the boundary, such as a
1291+
private network, reverse proxy, or platform ingress.
1292+
1293+
When you run the server directly, pass `transport_security` to `run()`:
1294+
1295+
```python
1296+
from mcp.server.fastmcp import FastMCP
1297+
from mcp.server.transport_security import TransportSecuritySettings
1298+
1299+
mcp = FastMCP("Proxy App")
1300+
1301+
1302+
if __name__ == "__main__":
1303+
mcp.run(
1304+
transport="streamable-http",
1305+
host="0.0.0.0",
1306+
transport_security=TransportSecuritySettings(
1307+
enable_dns_rebinding_protection=True,
1308+
allowed_hosts=["mcp.example.com", "mcp.example.com:*"],
1309+
allowed_origins=["https://mcp.example.com"],
1310+
),
1311+
)
1312+
```
1313+
1314+
For mounted ASGI apps, remember that `uvicorn --host` controls where the web
1315+
server listens, while `streamable_http_app()` and `sse_app()` use their `host`
1316+
argument only for transport security defaults. Pass `transport_security`
1317+
directly when mounting:
1318+
1319+
```python
1320+
from starlette.applications import Starlette
1321+
from starlette.routing import Mount
1322+
1323+
from mcp.server.fastmcp import FastMCP
1324+
from mcp.server.transport_security import TransportSecuritySettings
1325+
1326+
mcp = FastMCP("Mounted App")
1327+
1328+
transport_security = TransportSecuritySettings(
1329+
enable_dns_rebinding_protection=True,
1330+
allowed_hosts=["mcp.example.com"],
1331+
allowed_origins=["https://mcp.example.com"],
1332+
)
1333+
1334+
app = Starlette(
1335+
routes=[
1336+
Mount(
1337+
"/",
1338+
app=mcp.streamable_http_app(transport_security=transport_security),
1339+
),
1340+
],
1341+
)
1342+
```
1343+
1344+
If your MCP server is only reachable through a trusted reverse proxy or
1345+
container network, you can disable this check explicitly:
1346+
1347+
```python
1348+
transport_security = TransportSecuritySettings(
1349+
enable_dns_rebinding_protection=False,
1350+
)
1351+
```
1352+
12831353
You can mount multiple FastMCP servers in a Starlette application:
12841354

12851355
<!-- snippet-source examples/snippets/servers/streamable_starlette_mount.py -->

0 commit comments

Comments
 (0)