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..52109e758 100644 --- a/tests/shared/test_streamable_http.py +++ b/tests/shared/test_streamable_http.py @@ -2318,3 +2318,21 @@ 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"): # pragma: no branch + await session.initialize()