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