Skip to content

Commit 456692c

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 d5b9155 commit 456692c

2 files changed

Lines changed: 41 additions & 4 deletions

File tree

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+
# Client registered without scope restrictions — allow any requested scope
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: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,40 @@
11
"""Tests for OAuth 2.0 shared code."""
22

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

539

640
def test_oauth():

0 commit comments

Comments
 (0)