Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/mcp/client/streamable_http.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
18 changes: 18 additions & 0 deletions tests/shared/test_streamable_http.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Loading