Skip to content

Commit 3b6b9d3

Browse files
committed
fix: validate_scope allows any scope when client has no registered scope restriction
When OAuthClientMetadata.scope is None it means the client was registered without any scope restriction. The previous code built allowed_scopes as an empty list in that case, causing every requested scope to raise InvalidScopeError — the opposite of the intended behavior. Fix: return requested_scopes immediately when self.scope is None, bypassing the per-scope membership check. Also remove the now-unreachable pragma comments that suppressed coverage for the success path of the loop. Github-Issue: #2216
1 parent 5cbd259 commit 3b6b9d3

2 files changed

Lines changed: 39 additions & 4 deletions

File tree

src/mcp/shared/auth.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,11 +89,14 @@ def validate_scope(self, requested_scope: str | None) -> list[str] | None:
8989
if requested_scope is None:
9090
return None
9191
requested_scopes = requested_scope.split(" ")
92-
allowed_scopes = [] if self.scope is None else self.scope.split(" ")
92+
if self.scope is None:
93+
# Client registered without scope restrictions — allow any requested scope
94+
return requested_scopes
95+
allowed_scopes = self.scope.split(" ")
9396
for scope in requested_scopes:
94-
if scope not in allowed_scopes: # pragma: no branch
97+
if scope not in allowed_scopes:
9598
raise InvalidScopeError(f"Client was not registered with scope {scope}")
96-
return requested_scopes # pragma: no cover
99+
return requested_scopes
97100

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

tests/shared/test_auth.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,39 @@
33
import pytest
44
from pydantic import ValidationError
55

6-
from mcp.shared.auth import OAuthClientInformationFull, OAuthClientMetadata, OAuthMetadata
6+
from mcp.shared.auth import InvalidScopeError, OAuthClientInformationFull, OAuthClientMetadata, OAuthMetadata
7+
8+
9+
def _make_client(scope: str | None) -> OAuthClientInformationFull:
10+
return OAuthClientInformationFull(
11+
redirect_uris=["https://example.com/callback"],
12+
scope=scope,
13+
client_id="test-client",
14+
)
15+
16+
17+
def test_validate_scope_returns_none_when_no_scope_requested():
18+
client = _make_client("read write")
19+
assert client.validate_scope(None) is None
20+
21+
22+
def test_validate_scope_allows_registered_scopes():
23+
client = _make_client("read write")
24+
assert client.validate_scope("read") == ["read"]
25+
assert client.validate_scope("read write") == ["read", "write"]
26+
27+
28+
def test_validate_scope_raises_for_unregistered_scope():
29+
client = _make_client("read")
30+
with pytest.raises(InvalidScopeError):
31+
client.validate_scope("read admin")
32+
33+
34+
def test_validate_scope_allows_any_scope_when_client_has_no_scope_restriction():
35+
"""When client.scope is None, any requested scope should be allowed (issue #2216)."""
36+
client = _make_client(None)
37+
assert client.validate_scope("read") == ["read"]
38+
assert client.validate_scope("read write admin") == ["read", "write", "admin"]
739

840

941
def test_oauth():

0 commit comments

Comments
 (0)