|
54 | 54 | - [Claude Desktop Integration](#claude-desktop-integration) |
55 | 55 | - [Direct Execution](#direct-execution) |
56 | 56 | - [Streamable HTTP Transport](#streamable-http-transport) |
| 57 | + - [DNS Rebinding Protection](#dns-rebinding-protection) |
57 | 58 | - [CORS Configuration for Browser-Based Clients](#cors-configuration-for-browser-based-clients) |
58 | 59 | - [Mounting to an Existing ASGI Server](#mounting-to-an-existing-asgi-server) |
59 | 60 | - [StreamableHTTP servers](#streamablehttp-servers) |
@@ -1280,6 +1281,75 @@ if __name__ == "__main__": |
1280 | 1281 | _Full example: [examples/snippets/servers/streamable_config.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/streamable_config.py)_ |
1281 | 1282 | <!-- /snippet-source --> |
1282 | 1283 |
|
| 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 | + |
1283 | 1353 | You can mount multiple FastMCP servers in a Starlette application: |
1284 | 1354 |
|
1285 | 1355 | <!-- snippet-source examples/snippets/servers/streamable_starlette_mount.py --> |
|
0 commit comments