diff --git a/src/mcp/shared/auth.py b/src/mcp/shared/auth.py index 4d2d57221..b899bb5e0 100644 --- a/src/mcp/shared/auth.py +++ b/src/mcp/shared/auth.py @@ -71,6 +71,8 @@ class OAuthClientMetadata(BaseModel): def validate_scope(self, requested_scope: str | None) -> list[str] | None: if requested_scope is None: return None + if requested_scope == "": + return [] requested_scopes = requested_scope.split(" ") allowed_scopes = [] if self.scope is None else self.scope.split(" ") for scope in requested_scopes: diff --git a/tests/client/test_auth.py b/tests/client/test_auth.py index de4eb70af..4ad254513 100644 --- a/tests/client/test_auth.py +++ b/tests/client/test_auth.py @@ -760,6 +760,17 @@ async def test_scope_priority_no_scope(self, oauth_provider, oauth_client_info): # No scope should be set assert "scope" not in auth_params + @pytest.mark.anyio + async def test_client_metadata_validate_scopes_none(self, client_metadata): + """Test that validate_scopes method handles None and empty string correctly.""" + # Should return None + requested_scopes = client_metadata.validate_scope(None) + assert requested_scopes is None + + # No scopes should be requested; this can happen when a client authorizes with "&scope=". + requested_scopes = client_metadata.validate_scope("") + assert requested_scopes == [] + @pytest.mark.anyio async def test_state_parameter_validation_uses_constant_time( self, oauth_provider, oauth_metadata, oauth_client_info