diff --git a/src/mcp/shared/auth.py b/src/mcp/shared/auth.py index ca5b7b45a..5749dc45e 100644 --- a/src/mcp/shared/auth.py +++ b/src/mcp/shared/auth.py @@ -71,11 +71,12 @@ 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(" ") - for scope in requested_scopes: - if scope not in allowed_scopes: # pragma: no branch - raise InvalidScopeError(f"Client was not registered with scope {scope}") - return requested_scopes # pragma: no cover + if self.scope is not None: + allowed_scopes = self.scope.split(" ") + for scope in requested_scopes: + if scope not in allowed_scopes: + raise InvalidScopeError(f"Client was not registered with scope {scope}") + return requested_scopes def validate_redirect_uri(self, redirect_uri: AnyUrl | None) -> AnyUrl: if redirect_uri is not None: diff --git a/tests/shared/test_auth.py b/tests/shared/test_auth.py index cd3c35332..7b85a88da 100644 --- a/tests/shared/test_auth.py +++ b/tests/shared/test_auth.py @@ -58,3 +58,53 @@ def test_oauth_with_jarm(): "token_endpoint_auth_methods_supported": ["client_secret_basic", "client_secret_post"], } ) + + +def test_validate_scope_none_client_scope_allows_any_requested_scope(): + """When client.scope is None, any requested scope should be allowed.""" + from mcp.shared.auth import OAuthClientMetadata + + client = OAuthClientMetadata( + redirect_uris=["https://example.com/callback"], + scope=None, + ) + result = client.validate_scope("read write admin") + assert result == ["read", "write", "admin"] + + +def test_validate_scope_with_client_scope_rejects_unregistered(): + """When client.scope is set, unregistered scopes should be rejected.""" + import pytest + + from mcp.shared.auth import InvalidScopeError, OAuthClientMetadata + + client = OAuthClientMetadata( + redirect_uris=["https://example.com/callback"], + scope="read write", + ) + with pytest.raises(InvalidScopeError): + client.validate_scope("admin") + + +def test_validate_scope_with_client_scope_allows_registered(): + """When client.scope is set, registered scopes should be allowed.""" + from mcp.shared.auth import OAuthClientMetadata + + client = OAuthClientMetadata( + redirect_uris=["https://example.com/callback"], + scope="read write", + ) + result = client.validate_scope("read") + assert result == ["read"] + + +def test_validate_scope_none_requested_returns_none(): + """When requested_scope is None, should return None.""" + from mcp.shared.auth import OAuthClientMetadata + + client = OAuthClientMetadata( + redirect_uris=["https://example.com/callback"], + scope=None, + ) + result = client.validate_scope(None) + assert result is None