@@ -1609,3 +1609,112 @@ async def bad_client():
16091609 assert isinstance (result , InitializeResult )
16101610 tools = await session .list_tools ()
16111611 assert tools .tools
1612+
1613+
1614+ @pytest .mark .anyio
1615+ async def test_streamable_http_client_does_not_mutate_provided_client (
1616+ basic_server : None , basic_server_url : str
1617+ ) -> None :
1618+ """Test that streamable_http_client does not mutate the provided httpx client's headers."""
1619+ # Create a client with custom headers
1620+ original_headers = {
1621+ "X-Custom-Header" : "custom-value" ,
1622+ "Authorization" : "Bearer test-token" ,
1623+ }
1624+
1625+ async with httpx .AsyncClient (headers = original_headers , follow_redirects = True ) as custom_client :
1626+ # Use the client with streamable_http_client
1627+ async with streamable_http_client (f"{ basic_server_url } /mcp" , http_client = custom_client ) as (
1628+ read_stream ,
1629+ write_stream ,
1630+ _ ,
1631+ ):
1632+ async with ClientSession (read_stream , write_stream ) as session :
1633+ result = await session .initialize ()
1634+ assert isinstance (result , InitializeResult )
1635+
1636+ # Verify client headers were not mutated with MCP protocol headers
1637+ # If accept header exists, it should still be httpx default, not MCP's
1638+ if "accept" in custom_client .headers :
1639+ assert custom_client .headers .get ("accept" ) == "*/*"
1640+ # MCP content-type should not have been added
1641+ assert custom_client .headers .get ("content-type" ) != "application/json"
1642+
1643+ # Verify custom headers are still present and unchanged
1644+ assert custom_client .headers .get ("X-Custom-Header" ) == "custom-value"
1645+ assert custom_client .headers .get ("Authorization" ) == "Bearer test-token"
1646+
1647+
1648+ @pytest .mark .anyio
1649+ async def test_streamable_http_client_mcp_headers_override_defaults (
1650+ context_aware_server : None , basic_server_url : str
1651+ ) -> None :
1652+ """Test that MCP protocol headers override httpx.AsyncClient default headers."""
1653+ # httpx.AsyncClient has default "accept: */*" header
1654+ # We need to verify that our MCP accept header overrides it in actual requests
1655+
1656+ async with httpx .AsyncClient (follow_redirects = True ) as client :
1657+ # Verify client has default accept header
1658+ assert client .headers .get ("accept" ) == "*/*"
1659+
1660+ async with streamable_http_client (f"{ basic_server_url } /mcp" , http_client = client ) as (
1661+ read_stream ,
1662+ write_stream ,
1663+ _ ,
1664+ ):
1665+ async with ClientSession (read_stream , write_stream ) as session :
1666+ await session .initialize ()
1667+
1668+ # Use echo_headers tool to see what headers the server actually received
1669+ tool_result = await session .call_tool ("echo_headers" , {})
1670+ assert len (tool_result .content ) == 1
1671+ assert isinstance (tool_result .content [0 ], TextContent )
1672+ headers_data = json .loads (tool_result .content [0 ].text )
1673+
1674+ # Verify MCP protocol headers were sent (not httpx defaults)
1675+ assert "accept" in headers_data
1676+ assert "application/json" in headers_data ["accept" ]
1677+ assert "text/event-stream" in headers_data ["accept" ]
1678+
1679+ assert "content-type" in headers_data
1680+ assert headers_data ["content-type" ] == "application/json"
1681+
1682+
1683+ @pytest .mark .anyio
1684+ async def test_streamable_http_client_preserves_custom_with_mcp_headers (
1685+ context_aware_server : None , basic_server_url : str
1686+ ) -> None :
1687+ """Test that both custom headers and MCP protocol headers are sent in requests."""
1688+ custom_headers = {
1689+ "X-Custom-Header" : "custom-value" ,
1690+ "X-Request-Id" : "req-123" ,
1691+ "Authorization" : "Bearer test-token" ,
1692+ }
1693+
1694+ async with httpx .AsyncClient (headers = custom_headers , follow_redirects = True ) as client :
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 verify both custom and MCP headers are present
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 custom headers are present
1710+ assert headers_data .get ("x-custom-header" ) == "custom-value"
1711+ assert headers_data .get ("x-request-id" ) == "req-123"
1712+ assert headers_data .get ("authorization" ) == "Bearer test-token"
1713+
1714+ # Verify MCP protocol headers are also present
1715+ assert "accept" in headers_data
1716+ assert "application/json" in headers_data ["accept" ]
1717+ assert "text/event-stream" in headers_data ["accept" ]
1718+
1719+ assert "content-type" in headers_data
1720+ assert headers_data ["content-type" ] == "application/json"
0 commit comments