@@ -1644,3 +1644,112 @@ async def test_handle_sse_event_skips_empty_data():
16441644 finally :
16451645 await write_stream .aclose ()
16461646 await read_stream .aclose ()
1647+
1648+
1649+ @pytest .mark .anyio
1650+ async def test_streamable_http_client_does_not_mutate_provided_client (
1651+ basic_server : None , basic_server_url : str
1652+ ) -> None :
1653+ """Test that streamable_http_client does not mutate the provided httpx client's headers."""
1654+ # Create a client with custom headers
1655+ original_headers = {
1656+ "X-Custom-Header" : "custom-value" ,
1657+ "Authorization" : "Bearer test-token" ,
1658+ }
1659+
1660+ async with httpx .AsyncClient (headers = original_headers , follow_redirects = True ) as custom_client :
1661+ # Use the client with streamable_http_client
1662+ async with streamable_http_client (f"{ basic_server_url } /mcp" , http_client = custom_client ) as (
1663+ read_stream ,
1664+ write_stream ,
1665+ _ ,
1666+ ):
1667+ async with ClientSession (read_stream , write_stream ) as session :
1668+ result = await session .initialize ()
1669+ assert isinstance (result , InitializeResult )
1670+
1671+ # Verify client headers were not mutated with MCP protocol headers
1672+ # If accept header exists, it should still be httpx default, not MCP's
1673+ if "accept" in custom_client .headers : # pragma: no branch
1674+ assert custom_client .headers .get ("accept" ) == "*/*"
1675+ # MCP content-type should not have been added
1676+ assert custom_client .headers .get ("content-type" ) != "application/json"
1677+
1678+ # Verify custom headers are still present and unchanged
1679+ assert custom_client .headers .get ("X-Custom-Header" ) == "custom-value"
1680+ assert custom_client .headers .get ("Authorization" ) == "Bearer test-token"
1681+
1682+
1683+ @pytest .mark .anyio
1684+ async def test_streamable_http_client_mcp_headers_override_defaults (
1685+ context_aware_server : None , basic_server_url : str
1686+ ) -> None :
1687+ """Test that MCP protocol headers override httpx.AsyncClient default headers."""
1688+ # httpx.AsyncClient has default "accept: */*" header
1689+ # We need to verify that our MCP accept header overrides it in actual requests
1690+
1691+ async with httpx .AsyncClient (follow_redirects = True ) as client :
1692+ # Verify client has default accept header
1693+ assert client .headers .get ("accept" ) == "*/*"
1694+
1695+ async with streamable_http_client (f"{ basic_server_url } /mcp" , http_client = client ) as (
1696+ read_stream ,
1697+ write_stream ,
1698+ _ ,
1699+ ):
1700+ async with ClientSession (read_stream , write_stream ) as session :
1701+ await session .initialize ()
1702+
1703+ # Use echo_headers tool to see what headers the server actually received
1704+ tool_result = await session .call_tool ("echo_headers" , {})
1705+ assert len (tool_result .content ) == 1
1706+ assert isinstance (tool_result .content [0 ], TextContent )
1707+ headers_data = json .loads (tool_result .content [0 ].text )
1708+
1709+ # Verify MCP protocol headers were sent (not httpx defaults)
1710+ assert "accept" in headers_data
1711+ assert "application/json" in headers_data ["accept" ]
1712+ assert "text/event-stream" in headers_data ["accept" ]
1713+
1714+ assert "content-type" in headers_data
1715+ assert headers_data ["content-type" ] == "application/json"
1716+
1717+
1718+ @pytest .mark .anyio
1719+ async def test_streamable_http_client_preserves_custom_with_mcp_headers (
1720+ context_aware_server : None , basic_server_url : str
1721+ ) -> None :
1722+ """Test that both custom headers and MCP protocol headers are sent in requests."""
1723+ custom_headers = {
1724+ "X-Custom-Header" : "custom-value" ,
1725+ "X-Request-Id" : "req-123" ,
1726+ "Authorization" : "Bearer test-token" ,
1727+ }
1728+
1729+ async with httpx .AsyncClient (headers = custom_headers , follow_redirects = True ) as client :
1730+ async with streamable_http_client (f"{ basic_server_url } /mcp" , http_client = client ) as (
1731+ read_stream ,
1732+ write_stream ,
1733+ _ ,
1734+ ):
1735+ async with ClientSession (read_stream , write_stream ) as session :
1736+ await session .initialize ()
1737+
1738+ # Use echo_headers tool to verify both custom and MCP headers are present
1739+ tool_result = await session .call_tool ("echo_headers" , {})
1740+ assert len (tool_result .content ) == 1
1741+ assert isinstance (tool_result .content [0 ], TextContent )
1742+ headers_data = json .loads (tool_result .content [0 ].text )
1743+
1744+ # Verify custom headers are present
1745+ assert headers_data .get ("x-custom-header" ) == "custom-value"
1746+ assert headers_data .get ("x-request-id" ) == "req-123"
1747+ assert headers_data .get ("authorization" ) == "Bearer test-token"
1748+
1749+ # Verify MCP protocol headers are also present
1750+ assert "accept" in headers_data
1751+ assert "application/json" in headers_data ["accept" ]
1752+ assert "text/event-stream" in headers_data ["accept" ]
1753+
1754+ assert "content-type" in headers_data
1755+ assert headers_data ["content-type" ] == "application/json"
0 commit comments