From e65bd538261665754f0099927572dc7c323f8dde Mon Sep 17 00:00:00 2001 From: goingforstudying-ctrl Date: Sun, 8 Mar 2026 05:11:06 +0000 Subject: [PATCH 1/2] fix: validate_scope should allow all scopes when no restriction When client registration has scope=None (no restriction), validate_scope was incorrectly rejecting all requested scopes by treating None as an empty allowed list. Now when scope is None, all requested scopes are allowed, which matches the expected behavior described in the auth flow. Fixes #2216 --- src/mcp/shared/auth.py | 5 +++- test_validate_scope_fix.py | 48 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 test_validate_scope_fix.py diff --git a/src/mcp/shared/auth.py b/src/mcp/shared/auth.py index ca5b7b45a..583acc9b2 100644 --- a/src/mcp/shared/auth.py +++ b/src/mcp/shared/auth.py @@ -71,7 +71,10 @@ def validate_scope(self, requested_scope: str | None) -> list[str] | None: if requested_scope is None: return None requested_scopes = requested_scope.split(" ") - allowed_scopes = [] if self.scope is None else self.scope.split(" ") + # When no scope is required (None), allow all requested scopes + if self.scope is None: + return requested_scopes + allowed_scopes = self.scope.split(" ") for scope in requested_scopes: if scope not in allowed_scopes: # pragma: no branch raise InvalidScopeError(f"Client was not registered with scope {scope}") diff --git a/test_validate_scope_fix.py b/test_validate_scope_fix.py new file mode 100644 index 000000000..bb84f42e0 --- /dev/null +++ b/test_validate_scope_fix.py @@ -0,0 +1,48 @@ +"""Test for validate_scope fix when self.scope is None""" +import pytest +from mcp.shared.auth import ClientRegistration, InvalidScopeError + + +def test_validate_scope_with_none_scope_allows_all(): + """When client has no scope restriction (None), all requested scopes should be allowed.""" + client = ClientRegistration( + client_id="test-client", + client_secret="secret", + scope=None, # No scope restriction + redirect_uris=["http://localhost/callback"], + ) + + # Should not raise - all scopes allowed when no restriction + result = client.validate_scope("read write admin") + assert result == ["read", "write", "admin"] + + +def test_validate_scope_with_empty_requested_returns_none(): + """When requested_scope is None, return None.""" + client = ClientRegistration( + client_id="test-client", + client_secret="secret", + scope="read write", + redirect_uris=["http://localhost/callback"], + ) + + result = client.validate_scope(None) + assert result is None + + +def test_validate_scope_with_restrictions_enforced(): + """When client has scope restrictions, only allowed scopes pass.""" + client = ClientRegistration( + client_id="test-client", + client_secret="secret", + scope="read write", + redirect_uris=["http://localhost/callback"], + ) + + # Allowed scope + result = client.validate_scope("read") + assert result == ["read"] + + # Disallowed scope should raise + with pytest.raises(InvalidScopeError): + client.validate_scope("admin") From 9efde4ac46d6d3d9ff3c81263e9e2e35ad8937fa Mon Sep 17 00:00:00 2001 From: goingforstudying-ctrl Date: Sun, 8 Mar 2026 05:16:24 +0000 Subject: [PATCH 2/2] docs: add DNS rebinding protection guide Adds comprehensive documentation for resolving "421 Invalid Host Header" errors when using MCP behind proxies or gateways. Includes: - Two resolution options (allowlist vs disable) - Common scenarios (Nginx, Docker) - Security considerations Fixes #1798 --- docs/guides/dns-rebinding-protection.md | 89 +++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 docs/guides/dns-rebinding-protection.md diff --git a/docs/guides/dns-rebinding-protection.md b/docs/guides/dns-rebinding-protection.md new file mode 100644 index 000000000..88437a921 --- /dev/null +++ b/docs/guides/dns-rebinding-protection.md @@ -0,0 +1,89 @@ +# DNS Rebinding Protection + +The MCP Python SDK includes DNS rebinding protection to prevent DNS rebinding attacks. While this improves security, it may cause existing setups to fail with a **421 Misdirected Request / Invalid Host Header** error if the host header doesn't match the allowed list. + +This commonly occurs when using: +- Reverse proxies (Nginx, Caddy, etc.) +- API gateways +- Custom domains +- Docker/Kubernetes networking + +## Resolving the Error + +Depending on your security requirements, you can resolve this in two ways: + +### Option 1: Explicitly Allow Specific Hosts (Recommended for Production) + +Use this approach if you are running in production or through a gateway. You can wildcard the ports using `*`. + +```python +from mcp.server.fastmcp import FastMCP +from mcp.server.transport_security import TransportSecuritySettings + +mcp = FastMCP( + "MyServer", + transport_security=TransportSecuritySettings( + enable_dns_rebinding_protection=True, + # Add your specific gateway or domain here + allowed_hosts=["localhost:*", "127.0.0.1:*", "your-gateway-host:*"], + allowed_origins=["http://localhost:*", "http://your-gateway-host:*"], + ) +) +``` + +### Option 2: Disable DNS Rebinding Protection (Development Only) + +Use this approach for local development or if you are managing security at a different layer of your infrastructure. + +```python +from mcp.server.fastmcp import FastMCP +from mcp.server.transport_security import TransportSecuritySettings + +mcp = FastMCP( + "MyServer", + transport_security=TransportSecuritySettings( + enable_dns_rebinding_protection=False, + ) +) +``` + +## Common Scenarios + +### Using with Nginx + +If you're using Nginx as a reverse proxy, ensure it's passing the correct headers: + +```nginx +location / { + proxy_pass http://localhost:8000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; +} +``` + +And configure your MCP server to allow the Nginx host: + +```python +allowed_hosts=["localhost:*", "your-domain.com:*"] +``` + +### Using with Docker + +When running in Docker, you may need to allow the container hostname: + +```python +allowed_hosts=["localhost:*", "127.0.0.1:*", "mcp-server:*"] +``` + +## Security Considerations + +- **Production**: Always use Option 1 with explicit host allowlisting +- **Development**: Option 2 is acceptable for local testing +- **Never** disable DNS rebinding protection in production environments exposed to the internet + +## Related Issues + +- Original implementation: [#861](https://github.com/modelcontextprotocol/python-sdk/pull/861) +- Common errors: [#1797](https://github.com/modelcontextprotocol/python-sdk/issues/1797)