Skip to content

Commit 2bc58db

Browse files
committed
Liniting issues fixed
1 parent b7e88d8 commit 2bc58db

File tree

2 files changed

+89
-88
lines changed

2 files changed

+89
-88
lines changed

src/mcp/client/auth.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -207,36 +207,36 @@ def __init__(
207207
def _extract_resource_metadata_from_www_auth(self, init_response: httpx.Response) -> str | None:
208208
"""
209209
Extract protected resource metadata URL from WWW-Authenticate header as per RFC9728.
210-
210+
211211
Returns:
212212
Resource metadata URL if found in WWW-Authenticate header, None otherwise
213213
"""
214214
if not init_response or init_response.status_code != 401:
215215
return None
216-
216+
217217
www_auth_header = init_response.headers.get("WWW-Authenticate")
218218
if not www_auth_header:
219219
return None
220-
220+
221221
# Pattern matches: resource_metadata="url" or resource_metadata=url (unquoted)
222222
pattern = r'resource_metadata=(?:"([^"]+)"|([^\s,]+))'
223223
match = re.search(pattern, www_auth_header)
224-
224+
225225
if match:
226226
# Return quoted value if present, otherwise unquoted value
227227
return match.group(1) or match.group(2)
228-
228+
229229
return None
230230

231-
async def _discover_protected_resource(self, init_response: httpx.Response | None = None) -> httpx.Request:
231+
async def _discover_protected_resource(self, init_response: httpx.Response) -> httpx.Request:
232232
# RFC9728: Try to extract resource_metadata URL from WWW-Authenticate header of the initial response
233-
url = self._extract_resource_metadata_from_www_auth(init_response) if init_response else None
234-
233+
url = self._extract_resource_metadata_from_www_auth(init_response)
234+
235235
if not url:
236236
# Fallback to well-known discovery
237237
auth_base_url = self.context.get_authorization_base_url(self.context.server_url)
238238
url = urljoin(auth_base_url, "/.well-known/oauth-protected-resource")
239-
239+
240240
return httpx.Request("GET", url, headers={MCP_PROTOCOL_VERSION: LATEST_PROTOCOL_VERSION})
241241

242242
async def _handle_protected_resource_response(self, response: httpx.Response) -> None:
@@ -521,7 +521,7 @@ async def async_auth_flow(self, request: httpx.Request) -> AsyncGenerator[httpx.
521521

522522
if self.context.is_token_valid():
523523
self._add_auth_header(request)
524-
524+
525525
response = yield request
526526

527527
if response.status_code == 401:

tests/client/test_auth.py

Lines changed: 79 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ class TestOAuthFlow:
198198
@pytest.mark.anyio
199199
async def test_discover_protected_resource_request(self, client_metadata, mock_storage):
200200
"""Test protected resource discovery request building maintains backward compatibility."""
201+
201202
async def redirect_handler(url: str) -> None:
202203
pass
203204

@@ -211,30 +212,24 @@ async def callback_handler() -> tuple[str, str | None]:
211212
redirect_handler=redirect_handler,
212213
callback_handler=callback_handler,
213214
)
214-
215-
# Test without response (backward compatibility)
216-
request = await provider._discover_protected_resource()
217-
assert request.method == "GET"
218-
assert str(request.url) == "https://api.example.com/.well-known/oauth-protected-resource"
219-
assert "mcp-protocol-version" in request.headers
220-
221-
# Test with response but no WWW-Authenticate (fallback)
215+
216+
# Test without WWW-Authenticate (fallback)
222217
init_response = httpx.Response(
223-
status_code=401,
224-
headers={},
225-
request=httpx.Request("GET", "https://request-api.example.com")
218+
status_code=401, headers={}, request=httpx.Request("GET", "https://request-api.example.com")
226219
)
227220

228221
request = await provider._discover_protected_resource(init_response)
229-
assert request.method == "GET"
222+
assert request.method == "GET"
230223
assert str(request.url) == "https://api.example.com/.well-known/oauth-protected-resource"
231224
assert "mcp-protocol-version" in request.headers
232225

233226
# Test with WWW-Authenticate header
234-
init_response.headers["WWW-Authenticate"] = 'Bearer resource_metadata="https://prm.example.com/.well-known/oauth-protected-resource/path"'
235-
227+
init_response.headers["WWW-Authenticate"] = (
228+
'Bearer resource_metadata="https://prm.example.com/.well-known/oauth-protected-resource/path"'
229+
)
230+
236231
request = await provider._discover_protected_resource(init_response)
237-
assert request.method == "GET"
232+
assert request.method == "GET"
238233
assert str(request.url) == "https://prm.example.com/.well-known/oauth-protected-resource/path"
239234
assert "mcp-protocol-version" in request.headers
240235

@@ -580,28 +575,42 @@ async def test_auth_flow_with_valid_tokens(self, oauth_provider, mock_storage, v
580575
pass # Expected
581576

582577

583-
class TestRFC9728WWWAuthenticate:
584-
"""Test RFC9728 WWW-Authenticate header parsing functionality."""
585-
586-
@pytest.mark.parametrize("www_auth_header,expected_url", [
587-
# Quoted URL
588-
('Bearer resource_metadata="https://api.example.com/.well-known/oauth-protected-resource"',
589-
"https://api.example.com/.well-known/oauth-protected-resource"),
590-
# Unquoted URL
591-
("Bearer resource_metadata=https://api.example.com/.well-known/oauth-protected-resource",
592-
"https://api.example.com/.well-known/oauth-protected-resource"),
593-
# Complex header with multiple parameters
594-
('Bearer realm="api", resource_metadata="https://api.example.com/.well-known/oauth-protected-resource", error="insufficient_scope"',
595-
"https://api.example.com/.well-known/oauth-protected-resource"),
596-
# Different URL format
597-
('Bearer resource_metadata="https://custom.domain.com/metadata"',
598-
"https://custom.domain.com/metadata"),
599-
# With path and query params
600-
('Bearer resource_metadata="https://api.example.com/auth/metadata?version=1"',
601-
"https://api.example.com/auth/metadata?version=1"),
602-
])
603-
def test_extract_resource_metadata_from_www_auth_valid_cases(self, client_metadata, mock_storage, www_auth_header, expected_url):
578+
class TestProtectedResourceWWWAuthenticate:
579+
"""Test RFC9728 WWW-Authenticate header parsing functionality for protected resource."""
580+
581+
@pytest.mark.parametrize(
582+
"www_auth_header,expected_url",
583+
[
584+
# Quoted URL
585+
(
586+
'Bearer resource_metadata="https://api.example.com/.well-known/oauth-protected-resource"',
587+
"https://api.example.com/.well-known/oauth-protected-resource",
588+
),
589+
# Unquoted URL
590+
(
591+
"Bearer resource_metadata=https://api.example.com/.well-known/oauth-protected-resource",
592+
"https://api.example.com/.well-known/oauth-protected-resource",
593+
),
594+
# Complex header with multiple parameters
595+
(
596+
'Bearer realm="api", resource_metadata="https://api.example.com/.well-known/oauth-protected-resource", '
597+
'error="insufficient_scope"',
598+
"https://api.example.com/.well-known/oauth-protected-resource",
599+
),
600+
# Different URL format
601+
('Bearer resource_metadata="https://custom.domain.com/metadata"', "https://custom.domain.com/metadata"),
602+
# With path and query params
603+
(
604+
'Bearer resource_metadata="https://api.example.com/auth/metadata?version=1"',
605+
"https://api.example.com/auth/metadata?version=1",
606+
),
607+
],
608+
)
609+
def test_extract_resource_metadata_from_www_auth_valid_cases(
610+
self, client_metadata, mock_storage, www_auth_header, expected_url
611+
):
604612
"""Test extraction of resource_metadata URL from various valid WWW-Authenticate headers."""
613+
605614
async def redirect_handler(url: str) -> None:
606615
pass
607616

@@ -615,31 +624,45 @@ async def callback_handler() -> tuple[str, str | None]:
615624
redirect_handler=redirect_handler,
616625
callback_handler=callback_handler,
617626
)
618-
627+
619628
init_response = httpx.Response(
620629
status_code=401,
621630
headers={"WWW-Authenticate": www_auth_header},
622-
request=httpx.Request("GET", "https://api.example.com/test")
631+
request=httpx.Request("GET", "https://api.example.com/test"),
623632
)
624-
633+
625634
result = provider._extract_resource_metadata_from_www_auth(init_response)
626635
assert result == expected_url
627636

628-
@pytest.mark.parametrize("status_code,www_auth_header,description", [
629-
# No header
630-
(401, None, "no WWW-Authenticate header"),
631-
# Empty header
632-
(401, "", "empty WWW-Authenticate header"),
633-
# Header without resource_metadata
634-
(401, 'Bearer realm="api", error="insufficient_scope"', "no resource_metadata parameter"),
635-
# Malformed header
636-
(401, "Bearer resource_metadata=", "malformed resource_metadata parameter"),
637-
# Non-401 status code
638-
(200, 'Bearer resource_metadata="https://api.example.com/.well-known/oauth-protected-resource"', "200 OK response"),
639-
(500, 'Bearer resource_metadata="https://api.example.com/.well-known/oauth-protected-resource"', "500 error response"),
640-
])
641-
def test_extract_resource_metadata_from_www_auth_invalid_cases(self, client_metadata, mock_storage, status_code, www_auth_header, description):
637+
@pytest.mark.parametrize(
638+
"status_code,www_auth_header,description",
639+
[
640+
# No header
641+
(401, None, "no WWW-Authenticate header"),
642+
# Empty header
643+
(401, "", "empty WWW-Authenticate header"),
644+
# Header without resource_metadata
645+
(401, 'Bearer realm="api", error="insufficient_scope"', "no resource_metadata parameter"),
646+
# Malformed header
647+
(401, "Bearer resource_metadata=", "malformed resource_metadata parameter"),
648+
# Non-401 status code
649+
(
650+
200,
651+
'Bearer resource_metadata="https://api.example.com/.well-known/oauth-protected-resource"',
652+
"200 OK response",
653+
),
654+
(
655+
500,
656+
'Bearer resource_metadata="https://api.example.com/.well-known/oauth-protected-resource"',
657+
"500 error response",
658+
),
659+
],
660+
)
661+
def test_extract_resource_metadata_from_www_auth_invalid_cases(
662+
self, client_metadata, mock_storage, status_code, www_auth_header, description
663+
):
642664
"""Test extraction returns None for invalid cases."""
665+
643666
async def redirect_handler(url: str) -> None:
644667
pass
645668

@@ -653,33 +676,11 @@ async def callback_handler() -> tuple[str, str | None]:
653676
redirect_handler=redirect_handler,
654677
callback_handler=callback_handler,
655678
)
656-
679+
657680
headers = {"WWW-Authenticate": www_auth_header} if www_auth_header is not None else {}
658681
init_response = httpx.Response(
659-
status_code=status_code,
660-
headers=headers,
661-
request=httpx.Request("GET", "https://api.example.com/test")
682+
status_code=status_code, headers=headers, request=httpx.Request("GET", "https://api.example.com/test")
662683
)
663-
684+
664685
result = provider._extract_resource_metadata_from_www_auth(init_response)
665686
assert result is None, f"Should return None for {description}"
666-
667-
def test_extract_resource_metadata_from_www_auth_none_response(self, client_metadata, mock_storage):
668-
"""Test extraction with None response returns None."""
669-
async def redirect_handler(url: str) -> None:
670-
pass
671-
672-
async def callback_handler() -> tuple[str, str | None]:
673-
return "test_auth_code", "test_state"
674-
675-
provider = OAuthClientProvider(
676-
server_url="https://api.example.com/v1/mcp",
677-
client_metadata=client_metadata,
678-
storage=mock_storage,
679-
redirect_handler=redirect_handler,
680-
callback_handler=callback_handler,
681-
)
682-
683-
result = provider._extract_resource_metadata_from_www_auth(None)
684-
assert result is None
685-

0 commit comments

Comments
 (0)