From 9968f2c45cba28d40a688f61e1b994e79a5c43f7 Mon Sep 17 00:00:00 2001 From: vishal-vanam Date: Mon, 13 Apr 2026 20:03:27 -0400 Subject: [PATCH 1/3] fix: consume response body on unexpected content type to prevent client hang Fixes #2432 --- src/mcp/client/streamable_http.py | 1 + tests/shared/test_streamable_http.py | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/src/mcp/client/streamable_http.py b/src/mcp/client/streamable_http.py index 9a119c633..a6a69d780 100644 --- a/src/mcp/client/streamable_http.py +++ b/src/mcp/client/streamable_http.py @@ -295,6 +295,7 @@ async def _handle_post_request(self, ctx: RequestContext) -> None: elif content_type.startswith("text/event-stream"): await self._handle_sse_response(response, ctx, is_initialization) else: + await response.aread() # consume body to prevent stream hang logger.error(f"Unexpected content type: {content_type}") error_data = ErrorData(code=INVALID_REQUEST, message=f"Unexpected content type: {content_type}") error_msg = SessionMessage(JSONRPCError(jsonrpc="2.0", id=message.id, error=error_data)) diff --git a/tests/shared/test_streamable_http.py b/tests/shared/test_streamable_http.py index 3d5770fb6..4913e9631 100644 --- a/tests/shared/test_streamable_http.py +++ b/tests/shared/test_streamable_http.py @@ -2318,3 +2318,22 @@ async def test_streamable_http_client_preserves_custom_with_mcp_headers( assert "content-type" in headers_data assert headers_data["content-type"] == "application/json" + + +@pytest.mark.anyio +async def test_unexpected_content_type_does_not_hang(): + """Test that client raises MCPError instead of hanging when server returns unexpected content type.""" + + def return_plain_text(request: httpx.Request) -> httpx.Response: + return httpx.Response(200, headers={"content-type": "text/plain"}, content=b"this is not json") + + mock_client = httpx.AsyncClient(transport=httpx.MockTransport(return_plain_text)) + + async with streamable_http_client("http://test/mcp", http_client=mock_client) as ( + read_stream, + write_stream, + ): + async with ClientSession(read_stream, write_stream) as session: + with pytest.raises(MCPError, match="Unexpected content type"): + await session.initialize() + From 37c95df29b5b1f545198bbe926500b39648a2e6e Mon Sep 17 00:00:00 2001 From: vishal-vanam Date: Mon, 13 Apr 2026 20:08:45 -0400 Subject: [PATCH 2/3] style: format test file with ruff --- tests/shared/test_streamable_http.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/shared/test_streamable_http.py b/tests/shared/test_streamable_http.py index 4913e9631..d738d7af6 100644 --- a/tests/shared/test_streamable_http.py +++ b/tests/shared/test_streamable_http.py @@ -2336,4 +2336,3 @@ def return_plain_text(request: httpx.Request) -> httpx.Response: async with ClientSession(read_stream, write_stream) as session: with pytest.raises(MCPError, match="Unexpected content type"): await session.initialize() - From d75d3067b1ea9709d5aef389d256f52437fc47b3 Mon Sep 17 00:00:00 2001 From: vishal-vanam Date: Mon, 13 Apr 2026 21:06:01 -0400 Subject: [PATCH 3/3] fix: add pragma no branch for coverage on pytest.raises --- tests/shared/test_streamable_http.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/shared/test_streamable_http.py b/tests/shared/test_streamable_http.py index d738d7af6..52109e758 100644 --- a/tests/shared/test_streamable_http.py +++ b/tests/shared/test_streamable_http.py @@ -2334,5 +2334,5 @@ def return_plain_text(request: httpx.Request) -> httpx.Response: write_stream, ): async with ClientSession(read_stream, write_stream) as session: - with pytest.raises(MCPError, match="Unexpected content type"): + with pytest.raises(MCPError, match="Unexpected content type"): # pragma: no branch await session.initialize()