@@ -261,6 +261,109 @@ async def test_oauth_discovery_fallback_order(self, oauth_provider):
261
261
"https://api.example.com/v1/mcp/.well-known/openid-configuration" ,
262
262
]
263
263
264
+ @pytest .mark .anyio
265
+ async def test_oauth_discovery_fallback_conditions (self , oauth_provider ):
266
+ """Test the conditions during which an AS metadata discovery fallback will be attempted."""
267
+ # Ensure no tokens are stored
268
+ oauth_provider .context .current_tokens = None
269
+ oauth_provider .context .token_expiry_time = None
270
+ oauth_provider ._initialized = True
271
+
272
+ # Mock client info to skip DCR
273
+ oauth_provider .context .client_info = OAuthClientInformationFull (
274
+ client_id = "existing_client" ,
275
+ redirect_uris = [AnyUrl ("http://localhost:3030/callback" )],
276
+ )
277
+
278
+ # Create a test request
279
+ test_request = httpx .Request ("GET" , "https://api.example.com/v1/mcp" )
280
+
281
+ # Mock the auth flow
282
+ auth_flow = oauth_provider .async_auth_flow (test_request )
283
+
284
+ # First request should be the original request without auth header
285
+ request = await auth_flow .__anext__ ()
286
+ assert "Authorization" not in request .headers
287
+
288
+ # Send a 401 response to trigger the OAuth flow
289
+ response = httpx .Response (
290
+ 401 ,
291
+ headers = {
292
+ "WWW-Authenticate" : 'Bearer resource_metadata="https://api.example.com/.well-known/oauth-protected-resource"'
293
+ },
294
+ request = test_request ,
295
+ )
296
+
297
+ # Next request should be to discover protected resource metadata
298
+ discovery_request = await auth_flow .asend (response )
299
+ assert str (discovery_request .url ) == "https://api.example.com/.well-known/oauth-protected-resource"
300
+ assert discovery_request .method == "GET"
301
+
302
+ # Send a successful discovery response with minimal protected resource metadata
303
+ discovery_response = httpx .Response (
304
+ 200 ,
305
+ content = b'{"resource": "https://api.example.com/v1/mcp", "authorization_servers": ["https://auth.example.com/v1/mcp"]}' ,
306
+ request = discovery_request ,
307
+ )
308
+
309
+ # Next request should be to discover OAuth metadata
310
+ oauth_metadata_request_1 = await auth_flow .asend (discovery_response )
311
+ assert (
312
+ str (oauth_metadata_request_1 .url )
313
+ == "https://auth.example.com/.well-known/oauth-authorization-server/v1/mcp"
314
+ )
315
+ assert oauth_metadata_request_1 .method == "GET"
316
+
317
+ # Send a 404 response
318
+ oauth_metadata_response_1 = httpx .Response (
319
+ 404 ,
320
+ content = b"Not Found" ,
321
+ request = oauth_metadata_request_1 ,
322
+ )
323
+
324
+ # Next request should be to discover OAuth metadata at the next endpoint
325
+ oauth_metadata_request_2 = await auth_flow .asend (oauth_metadata_response_1 )
326
+ assert str (oauth_metadata_request_2 .url ) == "https://auth.example.com/.well-known/oauth-authorization-server"
327
+ assert oauth_metadata_request_2 .method == "GET"
328
+
329
+ # Send a 400 response
330
+ oauth_metadata_response_2 = httpx .Response (
331
+ 400 ,
332
+ content = b"Bad Request" ,
333
+ request = oauth_metadata_request_2 ,
334
+ )
335
+
336
+ # Next request should be to discover OAuth metadata at the next endpoint
337
+ oauth_metadata_request_3 = await auth_flow .asend (oauth_metadata_response_2 )
338
+ assert str (oauth_metadata_request_3 .url ) == "https://auth.example.com/.well-known/openid-configuration/v1/mcp"
339
+ assert oauth_metadata_request_3 .method == "GET"
340
+
341
+ # Send a 500 response
342
+ oauth_metadata_response_3 = httpx .Response (
343
+ 500 ,
344
+ content = b"Internal Server Error" ,
345
+ request = oauth_metadata_request_3 ,
346
+ )
347
+
348
+ # Mock the authorization process to minimize unnecessary state in this test
349
+ oauth_provider ._perform_authorization = mock .AsyncMock (return_value = ("test_auth_code" , "test_code_verifier" ))
350
+
351
+ # Next request should fall back to legacy behavior and auth with the RS (mocked /authorize, next is /token)
352
+ token_request = await auth_flow .asend (oauth_metadata_response_3 )
353
+ assert str (token_request .url ) == "https://api.example.com/token"
354
+ assert token_request .method == "POST"
355
+
356
+ # Send a successful token response
357
+ token_response = httpx .Response (
358
+ 200 ,
359
+ content = (
360
+ b'{"access_token": "new_access_token", "token_type": "Bearer", "expires_in": 3600, '
361
+ b'"refresh_token": "new_refresh_token"}'
362
+ ),
363
+ request = token_request ,
364
+ )
365
+ token_request = await auth_flow .asend (token_response )
366
+
264
367
@pytest .mark .anyio
265
368
async def test_handle_metadata_response_success (self , oauth_provider ):
266
369
"""Test successful metadata response handling."""
0 commit comments