@@ -329,6 +329,7 @@ async def test_oauth_discovery_legacy_fallback_when_no_prm(self):
329329 # Should only try the root URL (legacy behavior)
330330 assert discovery_urls == [
331331 "https://mcp.linear.app/.well-known/oauth-authorization-server" ,
332+ "https://mcp.linear.app/.well-known/openid-configuration" ,
332333 ]
333334
334335 @pytest .mark .anyio
@@ -1046,6 +1047,87 @@ def test_falls_back_when_metadata_has_no_registration_endpoint(self):
10461047 assert request .method == "POST"
10471048
10481049
1050+ @pytest .mark .anyio
1051+ async def test_oauth_flow_discovers_oidc_metadata_when_prm_is_absent (
1052+ client_metadata : OAuthClientMetadata , mock_storage : MockTokenStorage
1053+ ):
1054+ """Test OIDC metadata discovery after PRM and OAuth metadata are absent."""
1055+ captured_auth_url : str | None = None
1056+ captured_state : str | None = None
1057+
1058+ async def redirect_handler (url : str ) -> None :
1059+ nonlocal captured_auth_url , captured_state
1060+ captured_auth_url = url
1061+ parsed = urlparse (url )
1062+ params = parse_qs (parsed .query )
1063+ captured_state = params .get ("state" , [None ])[0 ]
1064+
1065+ async def callback_handler () -> tuple [str , str | None ]:
1066+ return "test_auth_code" , captured_state
1067+
1068+ provider = OAuthClientProvider (
1069+ server_url = "https://auth.example.com/v1/mcp" ,
1070+ client_metadata = client_metadata ,
1071+ storage = mock_storage ,
1072+ redirect_handler = redirect_handler ,
1073+ callback_handler = callback_handler ,
1074+ )
1075+ provider .context .current_tokens = None
1076+ provider .context .token_expiry_time = None
1077+ provider .context .client_info = OAuthClientInformationFull (
1078+ client_id = "test_client" ,
1079+ redirect_uris = [AnyUrl ("http://localhost:3030/callback" )],
1080+ )
1081+ provider ._initialized = True
1082+
1083+ test_request = httpx .Request ("GET" , "https://auth.example.com/v1/mcp" )
1084+ auth_flow = provider .async_auth_flow (test_request )
1085+
1086+ await auth_flow .__anext__ ()
1087+ response = httpx .Response (401 , headers = {}, request = test_request )
1088+
1089+ prm_request_1 = await auth_flow .asend (response )
1090+ assert str (prm_request_1 .url ) == "https://auth.example.com/.well-known/oauth-protected-resource/v1/mcp"
1091+
1092+ prm_request_2 = await auth_flow .asend (httpx .Response (404 , request = prm_request_1 ))
1093+ assert str (prm_request_2 .url ) == "https://auth.example.com/.well-known/oauth-protected-resource"
1094+
1095+ oauth_metadata_request = await auth_flow .asend (httpx .Response (404 , request = prm_request_2 ))
1096+ assert str (oauth_metadata_request .url ) == "https://auth.example.com/.well-known/oauth-authorization-server"
1097+
1098+ oidc_metadata_request = await auth_flow .asend (httpx .Response (404 , request = oauth_metadata_request ))
1099+ assert str (oidc_metadata_request .url ) == "https://auth.example.com/.well-known/openid-configuration"
1100+
1101+ oidc_metadata_response = httpx .Response (
1102+ 200 ,
1103+ content = (
1104+ b'{"issuer": "https://auth.example.com",'
1105+ b' "authorization_endpoint": "https://auth.example.com/authorize",'
1106+ b' "token_endpoint": "https://auth.example.com/token"}'
1107+ ),
1108+ request = oidc_metadata_request ,
1109+ )
1110+
1111+ token_request = await auth_flow .asend (oidc_metadata_response )
1112+ assert captured_auth_url is not None
1113+ assert captured_auth_url .startswith ("https://auth.example.com/authorize?" )
1114+ assert str (token_request .url ) == "https://auth.example.com/token"
1115+
1116+ token_response = httpx .Response (
1117+ 200 ,
1118+ content = b'{"access_token": "new_access_token", "token_type": "Bearer", "expires_in": 3600}' ,
1119+ request = token_request ,
1120+ )
1121+ final_request = await auth_flow .asend (token_response )
1122+ assert final_request .headers ["Authorization" ] == "Bearer new_access_token"
1123+
1124+ final_response = httpx .Response (200 , request = final_request )
1125+ try :
1126+ await auth_flow .asend (final_response )
1127+ except StopAsyncIteration :
1128+ pass
1129+
1130+
10491131class TestAuthFlow :
10501132 """Test the auth flow in httpx."""
10511133
0 commit comments