MCP Python SDK 1.23.0 introduced automatic DNS rebinding protection that auto-enables when host="127.0.0.1" (the default). This breaks containerized deployments (Kubernetes, Docker) because the protection rejects requests with Host headers like "nextcloud-mcp-server.default.svc.cluster.local:8000". Root cause: - FastMCP defaults to host="127.0.0.1" - SDK auto-enables DNS rebinding protection with allowed_hosts=["127.0.0.1:*", "localhost:*", "[::1]:*"] - K8s/Docker requests use service DNS names or proxied hostnames - Protection middleware rejects these requests (421 Misdirected Request) Solution: - Explicitly pass transport_security=TransportSecuritySettings(enable_dns_rebinding_protection=False) - Applied to all three FastMCP initializations (OAuth, Smithery, BasicAuth) - DNS rebinding attacks mitigated by OAuth authentication and network isolation This fixes issue #373 and enables MCP 1.23.x upgrade in PR #382. For detailed analysis, see docs/MCP-1.23-DNS-REBINDING-FIX.md
4.0 KiB
MCP 1.23.x DNS Rebinding Protection Fix
Problem
MCP Python SDK 1.23.0 introduced automatic DNS rebinding protection that breaks containerized deployments (Kubernetes, Docker) when the protection is unintentionally auto-enabled.
Root Cause
From mcp/server/fastmcp/server.py:177-183 in the Python SDK:
# Auto-enable DNS rebinding protection for localhost (IPv4 and IPv6)
if transport_security is None and host in ("127.0.0.1", "localhost", "::1"):
transport_security = TransportSecuritySettings(
enable_dns_rebinding_protection=True,
allowed_hosts=["127.0.0.1:*", "localhost:*", "[::1]:*"],
allowed_origins=["http://127.0.0.1:*", "http://localhost:*", "http://[::1]:*"],
)
What Was Happening
- FastMCP initialization in
app.pydidn't passhostortransport_securityparameters - Defaults applied:
host="127.0.0.1",transport_security=None - Auto-enablement triggered: Condition
transport_security is None and host == "127.0.0.1"was TRUE - Protection activated with
allowed_hosts=["127.0.0.1:*", "localhost:*", "[::1]:*"] - Kubernetes requests rejected:
Host: nextcloud-mcp-server.default.svc.cluster.local:8000didn't match allowed hosts
Why --host 0.0.0.0 Didn't Help
The --host CLI flag (used in Dockerfile/docker-compose) controls uvicorn's bind address, NOT the FastMCP host parameter. These are separate concerns:
- Uvicorn bind address (
--host 0.0.0.0): Where the HTTP server listens - FastMCP host parameter (defaulted to
"127.0.0.1"): Used for auto-enablement logic
Solution
Explicitly disable DNS rebinding protection by passing transport_security=TransportSecuritySettings(enable_dns_rebinding_protection=False) to all FastMCP instances.
Changes Made
Modified nextcloud_mcp_server/app.py:
- Import
TransportSecuritySettingsfrommcp.server.transport_security - Updated all three FastMCP initializations:
- OAuth mode (line 1015)
- Smithery stateless mode (line 1030)
- BasicAuth mode (line 1040)
Each now includes:
transport_security=TransportSecuritySettings(enable_dns_rebinding_protection=False)
Impact
✅ What This Fixes
- Kubernetes deployments: Requests with k8s service DNS names now work
- Docker deployments: Port-mapped requests (localhost:8000 → container) now work
- Reverse proxy deployments: Proxied requests with various Host headers now work
- Ingress controllers: Requests via ingress hostnames now work
🔒 Security Considerations
DNS rebinding protection defends against attacks where:
- Attacker controls a DNS domain (e.g.,
evil.com) - DNS initially resolves to attacker's IP
- After victim's browser caches the origin, DNS changes to victim's localhost
- Attacker's page can now make requests to victim's localhost services
Why it's safe to disable for this deployment:
- OAuth authentication required in production deployments (ADR-002, ADR-004)
- Network-level isolation in containerized environments (k8s network policies, Docker networks)
- MCP is server-to-server, not exposed to browsers (no CORS concerns)
- Host header validation inappropriate for multi-tenant k8s environments
If DNS rebinding protection is needed for specific deployments, it can be re-enabled with a custom allowed hosts list:
transport_security=TransportSecuritySettings(
enable_dns_rebinding_protection=True,
allowed_hosts=[
"nextcloud-mcp-server.default.svc.cluster.local:*",
"mcp.example.com:*",
# Add all your expected Host header values
]
)
Testing
- ✅ Ruff linting passes
- ✅ Type checking passes (pre-existing warnings unrelated)
- ✅ Module imports successfully
- ✅ Compatible with MCP 1.23.x
References
- MCP Python SDK 1.23.0 Release
- Commit:
d3a1841- "Auto-enable DNS rebinding protection for localhost servers" - Issue #373 (original report of k8s breakage)
- PR #382 (MCP 1.23.x upgrade)