Skip to content

Commit 9ac305e

Browse files
committed
update tests with url discovery behavior
1 parent 140c701 commit 9ac305e

File tree

1 file changed

+68
-12
lines changed

1 file changed

+68
-12
lines changed

tests/client/test_auth.py

Lines changed: 68 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -325,9 +325,14 @@ async def test_oauth_discovery_legacy_fallback_when_no_prm(self):
325325
# When auth_server_url is None (PRM failed), we use server_url and only try root
326326
discovery_urls = build_oauth_authorization_server_metadata_discovery_urls(None, "https://mcp.linear.app/sse")
327327

328-
# Should only try the root URL (legacy behavior)
328+
# Current behavior: try path-aware variants first, then root fallbacks
329329
assert discovery_urls == [
330+
"https://mcp.linear.app/.well-known/oauth-authorization-server/sse",
331+
"https://mcp.linear.app/.well-known/openid-configuration/sse",
332+
"https://mcp.linear.app/sse/.well-known/oauth-authorization-server",
333+
"https://mcp.linear.app/sse/.well-known/openid-configuration",
330334
"https://mcp.linear.app/.well-known/oauth-authorization-server",
335+
"https://mcp.linear.app/.well-known/openid-configuration",
331336
]
332337

333338
@pytest.mark.anyio
@@ -337,11 +342,14 @@ async def test_oauth_discovery_path_aware_when_auth_server_has_path(self):
337342
"https://auth.example.com/tenant1", "https://api.example.com/mcp"
338343
)
339344

340-
# Should try path-based URLs only (no root URLs)
345+
# Current behavior: path-aware variants (including symmetric OAuth path-local), then root fallbacks
341346
assert discovery_urls == [
342347
"https://auth.example.com/.well-known/oauth-authorization-server/tenant1",
343348
"https://auth.example.com/.well-known/openid-configuration/tenant1",
349+
"https://auth.example.com/tenant1/.well-known/oauth-authorization-server",
344350
"https://auth.example.com/tenant1/.well-known/openid-configuration",
351+
"https://auth.example.com/.well-known/oauth-authorization-server",
352+
"https://auth.example.com/.well-known/openid-configuration",
345353
]
346354

347355
@pytest.mark.anyio
@@ -351,7 +359,7 @@ async def test_oauth_discovery_root_when_auth_server_has_no_path(self):
351359
"https://auth.example.com", "https://api.example.com/mcp"
352360
)
353361

354-
# Should try root URLs only
362+
# Should try root URLs only (no path-aware when no path)
355363
assert discovery_urls == [
356364
"https://auth.example.com/.well-known/oauth-authorization-server",
357365
"https://auth.example.com/.well-known/openid-configuration",
@@ -364,7 +372,7 @@ async def test_oauth_discovery_root_when_auth_server_has_only_slash(self):
364372
"https://auth.example.com/", "https://api.example.com/mcp"
365373
)
366374

367-
# Should try root URLs only
375+
# Should try root URLs only (trailing slash treated as root)
368376
assert discovery_urls == [
369377
"https://auth.example.com/.well-known/oauth-authorization-server",
370378
"https://auth.example.com/.well-known/openid-configuration",
@@ -383,7 +391,10 @@ async def test_oauth_discovery_fallback_order(self, oauth_provider: OAuthClientP
383391
assert discovery_urls == [
384392
"https://api.example.com/.well-known/oauth-authorization-server/v1/mcp",
385393
"https://api.example.com/.well-known/openid-configuration/v1/mcp",
394+
"https://api.example.com/v1/mcp/.well-known/oauth-authorization-server",
386395
"https://api.example.com/v1/mcp/.well-known/openid-configuration",
396+
"https://api.example.com/.well-known/oauth-authorization-server",
397+
"https://api.example.com/.well-known/openid-configuration",
387398
]
388399

389400
@pytest.mark.anyio
@@ -459,9 +470,12 @@ async def test_oauth_discovery_fallback_conditions(self, oauth_provider: OAuthCl
459470
request=oauth_metadata_request_2,
460471
)
461472

462-
# Next request should be OIDC path-appended URL
473+
# Next request should be OAuth path-local URL (symmetric), then OIDC path-local
463474
oauth_metadata_request_3 = await auth_flow.asend(oauth_metadata_response_2)
464-
assert str(oauth_metadata_request_3.url) == "https://auth.example.com/v1/mcp/.well-known/openid-configuration"
475+
assert (
476+
str(oauth_metadata_request_3.url)
477+
== "https://auth.example.com/v1/mcp/.well-known/oauth-authorization-server"
478+
)
465479
assert oauth_metadata_request_3.method == "GET"
466480

467481
# Send a 500 response
@@ -471,12 +485,12 @@ async def test_oauth_discovery_fallback_conditions(self, oauth_provider: OAuthCl
471485
request=oauth_metadata_request_3,
472486
)
473487

474-
# Mock the authorization process to minimize unnecessary state in this test
488+
# Mock the authorization process to minimize unnecessary state before authorization triggers
475489
oauth_provider._perform_authorization_code_grant = mock.AsyncMock(
476490
return_value=("test_auth_code", "test_code_verifier")
477491
)
478492

479-
# All path-based URLs failed, flow continues with default endpoints
493+
# All path-based URLs failed; flow continues with default endpoints
480494
# Next request should be token exchange using MCP server base URL (fallback when OAuth metadata not found)
481495
token_request = await auth_flow.asend(oauth_metadata_response_3)
482496
assert str(token_request.url) == "https://api.example.com/token"
@@ -505,6 +519,46 @@ async def test_oauth_discovery_fallback_conditions(self, oauth_provider: OAuthCl
505519
except StopAsyncIteration:
506520
pass # Expected - generator should complete
507521

522+
@pytest.mark.anyio
523+
async def test_oauth_discovery_path_aware_issuer_with_origin_only_metadata(self):
524+
"""Servers may publish metadata only at the origin even when issuer has a path;
525+
ensure we include origin fallbacks."""
526+
# Path-aware issuer URL
527+
discovery_urls = build_oauth_authorization_server_metadata_discovery_urls(
528+
"https://auth.example.com/tenant1", "https://api.example.com/v1/mcp"
529+
)
530+
531+
# Must include origin-based fallbacks at the end, after path-aware variants
532+
assert discovery_urls[-2:] == [
533+
"https://auth.example.com/.well-known/oauth-authorization-server",
534+
"https://auth.example.com/.well-known/openid-configuration",
535+
]
536+
537+
@pytest.mark.anyio
538+
async def test_oauth_discovery_direct_metadata_url_precedence(self):
539+
"""If a direct metadata URL is provided, it should be tried first before derived well-known locations."""
540+
# Simulate PRM providing a direct OIDC configuration URL
541+
direct_metadata_url = "https://auth.example.com/.well-known/openid-configuration"
542+
discovery_urls = build_oauth_authorization_server_metadata_discovery_urls(
543+
direct_metadata_url, "https://api.example.com/v1/mcp"
544+
)
545+
546+
# First entry should be the provided URL exactly
547+
assert discovery_urls[0] == direct_metadata_url
548+
549+
@pytest.mark.anyio
550+
async def test_oauth_discovery_oidc_only_metadata(self):
551+
"""Some servers expose only OIDC metadata; ensure OIDC paths are included in order and allow fallback."""
552+
discovery_urls = build_oauth_authorization_server_metadata_discovery_urls(
553+
"https://auth.example.com/tenant1", "https://api.example.com/v1/mcp"
554+
)
555+
556+
# Ensure OIDC path-aware and path-local are present early
557+
assert "https://auth.example.com/.well-known/openid-configuration/tenant1" in discovery_urls[:3]
558+
assert "https://auth.example.com/tenant1/.well-known/openid-configuration" in discovery_urls[:5]
559+
560+
# No flow needed here; presence in discovery list is sufficient
561+
508562
@pytest.mark.anyio
509563
async def test_handle_metadata_response_success(self, oauth_provider: OAuthClientProvider):
510564
"""Test successful metadata response handling."""
@@ -1311,9 +1365,9 @@ async def callback_handler() -> tuple[str, str | None]:
13111365
# PRM returns 404 again - all PRM URLs failed
13121366
prm_response_2 = httpx.Response(404, request=prm_request_2)
13131367

1314-
# Should fall back to root OAuth discovery (March 2025 spec behavior)
1368+
# Current behavior: fall back to path-aware OAuth discovery first using server path
13151369
oauth_metadata_request = await auth_flow.asend(prm_response_2)
1316-
assert str(oauth_metadata_request.url) == "https://mcp.linear.app/.well-known/oauth-authorization-server"
1370+
assert str(oauth_metadata_request.url) == "https://mcp.linear.app/.well-known/oauth-authorization-server/sse"
13171371
assert oauth_metadata_request.method == "GET"
13181372

13191373
# Send successful OAuth metadata response
@@ -1419,9 +1473,11 @@ async def callback_handler() -> tuple[str, str | None]:
14191473
# Also returns 404 - all PRM URLs failed
14201474
prm_response_3 = httpx.Response(404, request=prm_request_3)
14211475

1422-
# Should fall back to root OAuth discovery
1476+
# Current behavior: fall back to path-aware OAuth discovery based on server path
14231477
oauth_metadata_request = await auth_flow.asend(prm_response_3)
1424-
assert str(oauth_metadata_request.url) == "https://api.example.com/.well-known/oauth-authorization-server"
1478+
assert (
1479+
str(oauth_metadata_request.url) == "https://api.example.com/.well-known/oauth-authorization-server/v1/mcp"
1480+
)
14251481

14261482
# Complete the flow
14271483
oauth_metadata_response = httpx.Response(

0 commit comments

Comments
 (0)