@@ -1589,6 +1589,98 @@ async def callback_handler() -> tuple[str, str | None]:
15891589 except StopAsyncIteration :
15901590 pass
15911591
1592+ @pytest .mark .anyio
1593+ async def test_oauth_flow_forwards_user_agent_to_generated_auth_requests (
1594+ self , client_metadata : OAuthClientMetadata , mock_storage : MockTokenStorage
1595+ ):
1596+ """OAuth discovery, registration, and token requests should preserve the transport User-Agent."""
1597+
1598+ async def redirect_handler (url : str ) -> None :
1599+ pass # pragma: no cover
1600+
1601+ async def callback_handler () -> tuple [str , str | None ]:
1602+ return "test_auth_code" , "test_state" # pragma: no cover
1603+
1604+ provider = OAuthClientProvider (
1605+ server_url = "https://api.example.com/v1/mcp" ,
1606+ client_metadata = client_metadata ,
1607+ storage = mock_storage ,
1608+ redirect_handler = redirect_handler ,
1609+ callback_handler = callback_handler ,
1610+ )
1611+ provider ._initialized = True
1612+ provider ._perform_authorization_code_grant = mock .AsyncMock (
1613+ return_value = ("test_auth_code" , "test_code_verifier" )
1614+ )
1615+
1616+ test_request = httpx .Request (
1617+ "POST" ,
1618+ "https://api.example.com/v1/mcp" ,
1619+ headers = {"User-Agent" : "custom-mcp-client/1.0" },
1620+ )
1621+ auth_flow = provider .async_auth_flow (test_request )
1622+
1623+ try :
1624+ first_request = await auth_flow .__anext__ ()
1625+ assert first_request .headers ["User-Agent" ] == "custom-mcp-client/1.0"
1626+
1627+ response = httpx .Response (
1628+ 401 ,
1629+ headers = {
1630+ "WWW-Authenticate" : (
1631+ 'Bearer resource_metadata="https://api.example.com/.well-known/oauth-protected-resource"'
1632+ )
1633+ },
1634+ request = test_request ,
1635+ )
1636+
1637+ discovery_request = await auth_flow .asend (response )
1638+ assert discovery_request .headers ["User-Agent" ] == "custom-mcp-client/1.0"
1639+
1640+ discovery_response = httpx .Response (
1641+ 200 ,
1642+ content = (
1643+ b'{"resource": "https://api.example.com/v1/mcp", '
1644+ b'"authorization_servers": ["https://auth.example.com"]}'
1645+ ),
1646+ request = discovery_request ,
1647+ )
1648+
1649+ oauth_metadata_request = await auth_flow .asend (discovery_response )
1650+ assert oauth_metadata_request .headers ["User-Agent" ] == "custom-mcp-client/1.0"
1651+
1652+ oauth_metadata_response = httpx .Response (
1653+ 200 ,
1654+ content = (
1655+ b'{"issuer": "https://auth.example.com", '
1656+ b'"authorization_endpoint": "https://auth.example.com/authorize", '
1657+ b'"token_endpoint": "https://auth.example.com/token", '
1658+ b'"registration_endpoint": "https://auth.example.com/register"}'
1659+ ),
1660+ request = oauth_metadata_request ,
1661+ )
1662+
1663+ registration_request = await auth_flow .asend (oauth_metadata_response )
1664+ assert registration_request .headers ["User-Agent" ] == "custom-mcp-client/1.0"
1665+
1666+ registration_response = httpx .Response (
1667+ 201 ,
1668+ content = (
1669+ b'{"client_id": "test_client", '
1670+ b'"client_secret": "test_secret", '
1671+ b'"redirect_uris": ["http://localhost:3030/callback"], '
1672+ b'"token_endpoint_auth_method": "client_secret_post", '
1673+ b'"grant_types": ["authorization_code"], '
1674+ b'"response_types": ["code"]}'
1675+ ),
1676+ request = registration_request ,
1677+ )
1678+
1679+ token_request = await auth_flow .asend (registration_response )
1680+ assert token_request .headers ["User-Agent" ] == "custom-mcp-client/1.0"
1681+ finally :
1682+ await auth_flow .aclose ()
1683+
15921684 @pytest .mark .anyio
15931685 async def test_legacy_server_with_different_prm_and_root_urls (
15941686 self , client_metadata : OAuthClientMetadata , mock_storage : MockTokenStorage
0 commit comments