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