diff --git a/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py b/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py index c17e83afcb9..2a9da964887 100644 --- a/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py +++ b/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py @@ -2925,8 +2925,10 @@ class MCPServerManager: handler = get_async_httpx_client(llm_provider=httpxSpecialProvider.MCP) response = await handler.get(server_url, stream=True) status_code = response.status_code - response.raise_for_status() - await response.aclose() + try: + response.raise_for_status() + finally: + await response.aclose() ( authorization_servers, resource_scopes, diff --git a/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py index 2e030d54245..975ca7c060c 100644 --- a/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py +++ b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py @@ -7097,6 +7097,38 @@ class TestOBOEndpointDiscovery: mock_fetch_auth.assert_not_awaited() assert result is None or result.token_url is None + @pytest.mark.asyncio + async def test_descovery_metadata_closes_stream_on_401_oauth_challenge(self): + manager = MCPServerManager() + server_url = "https://example.com/mcp" + request = httpx.Request("GET", server_url) + error_response = MagicMock() + error_response.headers = {} + + streaming_response = MagicMock() + streaming_response.status_code = 401 + streaming_response.raise_for_status = MagicMock( + side_effect=httpx.HTTPStatusError("unauthorized", request=request, response=error_response) + ) + streaming_response.aclose = AsyncMock() + + mock_handler = MagicMock() + mock_handler.get = AsyncMock(return_value=streaming_response) + + with ( + patch( + "litellm.proxy._experimental.mcp_server.mcp_server_manager.get_async_httpx_client", + return_value=mock_handler, + ), + patch.object(manager, "_attempt_well_known_discovery", AsyncMock(return_value=([], None))), + patch.object(manager, "_fetch_oauth_metadata_from_resource", AsyncMock(return_value=([], None))), + patch.object(manager, "_fetch_authorization_server_metadata", AsyncMock(return_value=None)), + ): + result = await manager._descovery_metadata(server_url) + + streaming_response.aclose.assert_awaited_once() + assert result is None + @pytest.mark.asyncio async def test_descovery_metadata_returns_none_for_no_auth_sse_server(self): manager = MCPServerManager()