diff --git a/src/mcp/shared/_httpx_utils.py b/src/mcp/shared/_httpx_utils.py index 940b9f08cc..d244e29183 100644 --- a/src/mcp/shared/_httpx_utils.py +++ b/src/mcp/shared/_httpx_utils.py @@ -204,6 +204,14 @@ async def async_auth_flow(self, request: httpx2.Request) -> AsyncGenerator[httpx try: outgoing = await flow.__anext__() while True: + if ( + isinstance(outgoing, httpx2.Request) + and outgoing is not request + and "user-agent" not in outgoing.headers + ): + user_agent = request.headers.get("user-agent") + if user_agent is not None: + outgoing.headers["user-agent"] = user_agent response = yield outgoing if outgoing is not request: for _ in range(_AUTH_REDIRECT_LIMIT): diff --git a/tests/client/test_auth.py b/tests/client/test_auth.py index 18a1566705..6ec23b422f 100644 --- a/tests/client/test_auth.py +++ b/tests/client/test_auth.py @@ -323,6 +323,24 @@ def test_create_oauth_metadata_request(self, oauth_provider: OAuthClientProvider assert str(request.url) == "https://example.com" assert "mcp-protocol-version" in request.headers + @pytest.mark.anyio + async def test_auth_flow_forwards_user_agent_header(self, oauth_provider: OAuthClientProvider): + """Test that auth flow requests inherit User-Agent from the authenticated request.""" + oauth_provider.context.current_tokens = None + oauth_provider.context.token_expiry_time = None + oauth_provider._initialized = True + test_request = httpx2.Request( + "POST", "https://api.example.com/v1/mcp", headers={"User-Agent": "test-agent/1.0"} + ) + flow = oauth_provider.async_auth_flow(test_request) + sent = await flow.__anext__() + assert sent is test_request + + unauthorized = httpx2.Response(401, request=test_request) + discovery_req = await flow.asend(unauthorized) + assert discovery_req.headers.get("user-agent") == "test-agent/1.0" + await flow.aclose() + class TestOAuthFallback: """Test OAuth discovery fallback behavior for legacy (act as AS not RS) servers."""