Skip to content

Commit 21d1778

Browse files
committed
fix: handle None required_scopes in validate_scope
When required_scopes is None (meaning no restrictions), skip validation entirely instead of treating it as an empty set of allowed scopes which rejects all client-requested scopes. Github-Issue: #2216
1 parent fb2276b commit 21d1778

File tree

2 files changed

+50
-4
lines changed

2 files changed

+50
-4
lines changed

src/mcp/shared/auth.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,14 @@ def validate_scope(self, requested_scope: str | None) -> list[str] | None:
7171
if requested_scope is None:
7272
return None
7373
requested_scopes = requested_scope.split(" ")
74-
allowed_scopes = [] if self.scope is None else self.scope.split(" ")
74+
if self.scope is None:
75+
# No scope restrictions registered; all requested scopes are allowed
76+
return requested_scopes
77+
allowed_scopes = self.scope.split(" ")
7578
for scope in requested_scopes:
76-
if scope not in allowed_scopes: # pragma: no branch
79+
if scope not in allowed_scopes:
7780
raise InvalidScopeError(f"Client was not registered with scope {scope}")
78-
return requested_scopes # pragma: no cover
81+
return requested_scopes
7982

8083
def validate_redirect_uri(self, redirect_uri: AnyUrl | None) -> AnyUrl:
8184
if redirect_uri is not None:

tests/shared/test_auth.py

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

3-
from mcp.shared.auth import OAuthMetadata
3+
import pytest
4+
from pydantic import AnyUrl
5+
6+
from mcp.shared.auth import InvalidScopeError, OAuthClientMetadata, OAuthMetadata
47

58

69
def test_oauth():
@@ -58,3 +61,43 @@ def test_oauth_with_jarm():
5861
"token_endpoint_auth_methods_supported": ["client_secret_basic", "client_secret_post"],
5962
}
6063
)
64+
65+
66+
def test_validate_scope_none_required_scopes_accepts_all():
67+
"""When client has no scope restrictions (scope=None), all requested scopes should be accepted."""
68+
client = OAuthClientMetadata(
69+
redirect_uris=[AnyUrl("http://localhost:3030/callback")],
70+
scope=None,
71+
)
72+
result = client.validate_scope("read write admin")
73+
assert result == ["read", "write", "admin"]
74+
75+
76+
def test_validate_scope_none_requested_scope_returns_none():
77+
"""When no scope is requested, validate_scope should return None."""
78+
client = OAuthClientMetadata(
79+
redirect_uris=[AnyUrl("http://localhost:3030/callback")],
80+
scope="read write",
81+
)
82+
result = client.validate_scope(None)
83+
assert result is None
84+
85+
86+
def test_validate_scope_rejects_unauthorized_scope():
87+
"""When client has specific allowed scopes, unauthorized scopes should be rejected."""
88+
client = OAuthClientMetadata(
89+
redirect_uris=[AnyUrl("http://localhost:3030/callback")],
90+
scope="read",
91+
)
92+
with pytest.raises(InvalidScopeError, match="write"):
93+
client.validate_scope("read write")
94+
95+
96+
def test_validate_scope_accepts_authorized_scope():
97+
"""When client has specific allowed scopes, authorized scopes should be accepted."""
98+
client = OAuthClientMetadata(
99+
redirect_uris=[AnyUrl("http://localhost:3030/callback")],
100+
scope="read write",
101+
)
102+
result = client.validate_scope("read write")
103+
assert result == ["read", "write"]

0 commit comments

Comments
 (0)