Skip to content

Commit 45b6f28

Browse files
author
Jah-yee
committed
fix: validate_scope treats None registered scopes as no restrictions
When self.scope is None (no scopes registered), the validate_scope method incorrectly treated it as an empty allowed list, rejecting all requested scopes with InvalidScopeError. Now None is correctly treated as 'no restrictions', allowing any requested scope through. Fixes modelcontextprotocol#2216 Closes modelcontextprotocol#2224
1 parent dd52713 commit 45b6f28

2 files changed

Lines changed: 39 additions & 2 deletions

File tree

src/mcp/shared/auth.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,11 @@ class OAuthClientMetadata(BaseModel):
7070
def validate_scope(self, requested_scope: str | None) -> list[str] | None:
7171
if requested_scope is None:
7272
return None
73+
# None means no restrictions - allow any requested scope
74+
if self.scope is None:
75+
return requested_scope.split(" ")
7376
requested_scopes = requested_scope.split(" ")
74-
allowed_scopes = [] if self.scope is None else self.scope.split(" ")
77+
allowed_scopes = self.scope.split(" ")
7578
for scope in requested_scopes:
7679
if scope not in allowed_scopes: # pragma: no branch
7780
raise InvalidScopeError(f"Client was not registered with scope {scope}")

tests/shared/test_auth.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Tests for OAuth 2.0 shared code."""
22

3-
from mcp.shared.auth import OAuthMetadata
3+
from mcp.shared.auth import OAuthClientMetadata, OAuthMetadata, InvalidScopeError
44

55

66
def test_oauth():
@@ -58,3 +58,37 @@ def test_oauth_with_jarm():
5858
"token_endpoint_auth_methods_supported": ["client_secret_basic", "client_secret_post"],
5959
}
6060
)
61+
62+
63+
def test_validate_scope_none_registered_allows_any():
64+
"""None registered scope should mean no restrictions - allow any scope."""
65+
metadata = OAuthClientMetadata(redirect_uris=["https://example.com/callback"], scope=None)
66+
result = metadata.validate_scope("read write")
67+
assert result == ["read", "write"]
68+
69+
70+
def test_validate_scope_matching():
71+
"""Should return requested scopes when they match registered scopes."""
72+
metadata = OAuthClientMetadata(redirect_uris=["https://example.com/callback"], scope="read write")
73+
result = metadata.validate_scope("read")
74+
assert result == ["read"]
75+
76+
77+
def test_validate_scope_rejects_unregistered():
78+
"""Should reject scopes not in registered scopes."""
79+
metadata = OAuthClientMetadata(redirect_uris=["https://example.com/callback"], scope="read")
80+
try:
81+
metadata.validate_scope("write")
82+
assert False, "Expected InvalidScopeError"
83+
except InvalidScopeError:
84+
pass
85+
86+
87+
def test_validate_scope_empty_registered_rejects_all():
88+
"""Empty string registered scope should reject all requested scopes."""
89+
metadata = OAuthClientMetadata(redirect_uris=["https://example.com/callback"], scope="")
90+
try:
91+
metadata.validate_scope("read")
92+
assert False, "Expected InvalidScopeError"
93+
except InvalidScopeError:
94+
pass

0 commit comments

Comments
 (0)