Skip to content

Commit 46006a4

Browse files
committed
test(auth): align interaction fixtures with slash-free issuers
build_metadata now serves path-less issuers without a trailing slash. Update authorize iss defaults, PRM/ASM overrides, and assertions so RFC 9207 string compares match the canonical metadata form.
1 parent c65b029 commit 46006a4

6 files changed

Lines changed: 19 additions & 16 deletions

File tree

tests/interaction/auth/_provider.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,10 @@ def __init__(
6363
) -> None:
6464
self._default_scopes = list(default_scopes) if default_scopes is not None else ["mcp"]
6565
# The authorization-response iss must equal the AS metadata issuer the client recorded
66-
# (RFC 9207 simple string comparison). `real_asm` builds the issuer from an AnyHttpUrl
67-
# object, so it carries the trailing slash; the redirect iss matches it. Path-issuer
68-
# tests pass the recorded issuer explicitly.
69-
self._issuer = issuer if issuer is not None else f"{BASE_URL}/"
66+
# (RFC 9207 simple string comparison). `build_metadata` serves path-less issuers without a
67+
# trailing slash, so the redirect iss matches that canonical form. Path-issuer tests pass
68+
# the recorded issuer explicitly.
69+
self._issuer = issuer if issuer is not None else BASE_URL
7070
self._deny_authorize = deny_authorize
7171
self._issue_expired_first = issue_expired_first
7272
self._fail_next_refresh = fail_next_refresh

tests/interaction/auth/test_authorize_token.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import re
1818
from collections.abc import AsyncIterator
1919
from dataclasses import dataclass
20+
from typing import cast
2021
from urllib.parse import parse_qsl, quote, urlsplit
2122

2223
import anyio
@@ -297,7 +298,7 @@ async def test_the_registered_auth_method_is_used_regardless_of_as_metadata_adve
297298
server = Server("guarded", on_list_tools=list_tools)
298299

299300
override = OAuthMetadata(
300-
issuer=AnyHttpUrl(f"{BASE_URL}/"),
301+
issuer=cast(AnyHttpUrl, BASE_URL),
301302
authorization_endpoint=AnyHttpUrl(f"{BASE_URL}/authorize"),
302303
token_endpoint=AnyHttpUrl(f"{BASE_URL}/token"),
303304
registration_endpoint=AnyHttpUrl(f"{BASE_URL}/register"),
@@ -367,7 +368,7 @@ async def test_pkce_is_still_sent_when_as_metadata_omits_code_challenge_methods_
367368
completes. See the divergence on the requirement.
368369
"""
369370
override = OAuthMetadata(
370-
issuer=AnyHttpUrl(f"{BASE_URL}/"),
371+
issuer=cast(AnyHttpUrl, BASE_URL),
371372
authorization_endpoint=AnyHttpUrl(f"{BASE_URL}/authorize"),
372373
token_endpoint=AnyHttpUrl(f"{BASE_URL}/token"),
373374
registration_endpoint=AnyHttpUrl(f"{BASE_URL}/register"),

tests/interaction/auth/test_discovery.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"""
1212

1313
import json
14+
from typing import cast
1415

1516
import anyio
1617
import mcp_types as types
@@ -54,7 +55,7 @@ def discovery_gets(recorded: list[RecordedRequest]) -> list[str]:
5455
def real_asm() -> OAuthMetadata:
5556
"""Build an authorization-server metadata document pointing at the real co-hosted endpoints."""
5657
return OAuthMetadata(
57-
issuer=AnyHttpUrl(BASE_URL),
58+
issuer=cast(AnyHttpUrl, BASE_URL),
5859
authorization_endpoint=AnyHttpUrl(f"{BASE_URL}/authorize"),
5960
token_endpoint=AnyHttpUrl(f"{BASE_URL}/token"),
6061
registration_endpoint=AnyHttpUrl(f"{BASE_URL}/register"),
@@ -100,7 +101,7 @@ async def test_prm_discovery_falls_back_from_path_well_known_to_root_on_404() ->
100101
server = Server("guarded", on_list_tools=list_tools)
101102

102103
prm = ProtectedResourceMetadata(
103-
resource=AnyHttpUrl(f"{BASE_URL}/mcp"), authorization_servers=[AnyHttpUrl(BASE_URL)]
104+
resource=AnyHttpUrl(f"{BASE_URL}/mcp"), authorization_servers=[cast(AnyHttpUrl, BASE_URL)]
104105
)
105106
app_shim = shim(
106107
not_found=frozenset({PRM_PATH_SUFFIXED}),
@@ -188,7 +189,7 @@ async def test_prm_with_a_mismatched_resource_aborts_the_flow_before_authorize()
188189
server = Server("guarded", on_list_tools=list_tools)
189190

190191
prm = ProtectedResourceMetadata(
191-
resource=AnyHttpUrl(f"{BASE_URL}/other"), authorization_servers=[AnyHttpUrl(BASE_URL)]
192+
resource=AnyHttpUrl(f"{BASE_URL}/other"), authorization_servers=[cast(AnyHttpUrl, BASE_URL)]
192193
)
193194
app_shim = shim(serve={PRM_PATH_SUFFIXED: metadata_body(prm)})
194195

tests/interaction/auth/test_flow.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,4 +237,4 @@ async def test_shimmed_app_serves_overrides_404s_and_otherwise_forwards_to_the_w
237237

238238
forwarded = await http.get("/.well-known/oauth-authorization-server")
239239
assert forwarded.status_code == 200
240-
assert forwarded.json()["issuer"] == "http://127.0.0.1:8000/"
240+
assert forwarded.json()["issuer"] == "http://127.0.0.1:8000"

tests/interaction/auth/test_identity_assertion.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,9 @@
4040
ID_JAG_GRANT_PROFILE = "urn:ietf:params:oauth:grant-profile:id-jag"
4141
CLIENT_ID = "enterprise-mcp-client"
4242
CLIENT_SECRET = "enterprise-secret"
43-
# The AS metadata issuer carries a trailing slash (built from an AnyHttpUrl object); the client
43+
# The AS metadata issuer is path-less and slash-free (matches build_metadata); the client
4444
# pins against exactly that.
45-
EXPECTED_ISSUER = f"{BASE_URL}/"
45+
EXPECTED_ISSUER = BASE_URL
4646

4747

4848
async def list_tools(ctx: ServerRequestContext, params: types.PaginatedRequestParams | None) -> ListToolsResult:
@@ -215,7 +215,7 @@ async def test_unexpected_issuer_aborts_before_sending_credentials() -> None:
215215
provider = InMemoryAuthorizationServerProvider()
216216
preregister_confidential_client(provider)
217217
server = Server("guarded", on_list_tools=list_tools)
218-
# The served AS metadata has issuer BASE_URL/, but the client is configured for a different one.
218+
# The served AS metadata has issuer BASE_URL, but the client is configured for a different one.
219219
auth = identity_assertion_provider(InMemoryTokenStorage(), issuer="https://corp-as.example/", record=record)
220220

221221
with anyio.fail_after(5):

tests/interaction/auth/test_lifecycle.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import base64
1010
from collections import Counter
11+
from typing import cast
1112
from urllib.parse import parse_qsl, urlsplit
1213

1314
import anyio
@@ -69,7 +70,7 @@ def path_counts(recorded: list[RecordedRequest]) -> Counter[tuple[str, str]]:
6970
def cimd_supported_metadata() -> bytes:
7071
"""AS metadata advertising `client_id_metadata_document_supported: true` (the SDK server never sets it)."""
7172
metadata = OAuthMetadata(
72-
issuer=AnyHttpUrl(f"{BASE_URL}/"),
73+
issuer=cast(AnyHttpUrl, BASE_URL),
7374
authorization_endpoint=AnyHttpUrl(f"{BASE_URL}/authorize"),
7475
token_endpoint=AnyHttpUrl(f"{BASE_URL}/token"),
7576
registration_endpoint=AnyHttpUrl(f"{BASE_URL}/register"),
@@ -239,7 +240,7 @@ async def test_credentials_bound_to_a_different_issuer_are_discarded_and_the_cli
239240
# The persisted client is now bound to the current AS.
240241
assert storage.client_info is not None
241242
assert storage.client_info.client_id != "stale-as-client"
242-
assert storage.client_info.issuer == f"{BASE_URL}/"
243+
assert storage.client_info.issuer == BASE_URL
243244

244245

245246
@requirement("client-auth:401-after-auth-throws")
@@ -437,7 +438,7 @@ async def assertion_provider(audience: str) -> str:
437438
result = await client.list_tools()
438439

439440
assert result.tools[0].name == "echo"
440-
assert audiences == [f"{BASE_URL}/"]
441+
assert audiences == [BASE_URL]
441442

442443
[token_req] = find(recorded, "POST", "/token")
443444
body = form_body(token_req)

0 commit comments

Comments
 (0)