@@ -1891,7 +1891,7 @@ async def test_close_sse_stream_callback_not_provided_for_old_protocol_version()
18911891
18921892
18931893@pytest .mark .anyio
1894- async def test_streamablehttp_client_receives_priming_event (
1894+ async def test_streamable_http_client_receives_priming_event (
18951895 event_server : tuple [SimpleEventStore , str ],
18961896) -> None :
18971897 """Client should receive priming event (resumption token update) on POST SSE stream."""
@@ -1902,7 +1902,7 @@ async def test_streamablehttp_client_receives_priming_event(
19021902 async def on_resumption_token_update (token : str ) -> None :
19031903 captured_resumption_tokens .append (token )
19041904
1905- async with streamablehttp_client (f"{ server_url } /mcp" ) as (
1905+ async with streamable_http_client (f"{ server_url } /mcp" ) as (
19061906 read_stream ,
19071907 write_stream ,
19081908 _ ,
@@ -1943,7 +1943,7 @@ async def test_server_close_sse_stream_via_context(
19431943 """Server tool can call ctx.close_sse_stream() to close connection."""
19441944 _ , server_url = event_server
19451945
1946- async with streamablehttp_client (f"{ server_url } /mcp" ) as (
1946+ async with streamable_http_client (f"{ server_url } /mcp" ) as (
19471947 read_stream ,
19481948 write_stream ,
19491949 _ ,
@@ -1964,7 +1964,7 @@ async def test_server_close_sse_stream_via_context(
19641964
19651965
19661966@pytest .mark .anyio
1967- async def test_streamablehttp_client_auto_reconnects (
1967+ async def test_streamable_http_client_auto_reconnects (
19681968 event_server : tuple [SimpleEventStore , str ],
19691969) -> None :
19701970 """Client should auto-reconnect with Last-Event-ID when server closes after priming event."""
@@ -1980,7 +1980,7 @@ async def message_handler(
19801980 if isinstance (message .root , types .LoggingMessageNotification ): # pragma: no branch
19811981 captured_notifications .append (str (message .root .params .data ))
19821982
1983- async with streamablehttp_client (f"{ server_url } /mcp" ) as (
1983+ async with streamable_http_client (f"{ server_url } /mcp" ) as (
19841984 read_stream ,
19851985 write_stream ,
19861986 _ ,
@@ -2009,13 +2009,13 @@ async def message_handler(
20092009
20102010
20112011@pytest .mark .anyio
2012- async def test_streamablehttp_client_respects_retry_interval (
2012+ async def test_streamable_http_client_respects_retry_interval (
20132013 event_server : tuple [SimpleEventStore , str ],
20142014) -> None :
20152015 """Client MUST respect retry field, waiting specified ms before reconnecting."""
20162016 _ , server_url = event_server
20172017
2018- async with streamablehttp_client (f"{ server_url } /mcp" ) as (
2018+ async with streamable_http_client (f"{ server_url } /mcp" ) as (
20192019 read_stream ,
20202020 write_stream ,
20212021 _ ,
@@ -2040,7 +2040,7 @@ async def test_streamablehttp_client_respects_retry_interval(
20402040
20412041
20422042@pytest .mark .anyio
2043- async def test_streamablehttp_sse_polling_full_cycle (
2043+ async def test_streamable_http_sse_polling_full_cycle (
20442044 event_server : tuple [SimpleEventStore , str ],
20452045) -> None :
20462046 """End-to-end test: server closes stream, client reconnects, receives all events."""
@@ -2056,7 +2056,7 @@ async def message_handler(
20562056 if isinstance (message .root , types .LoggingMessageNotification ): # pragma: no branch
20572057 all_notifications .append (str (message .root .params .data ))
20582058
2059- async with streamablehttp_client (f"{ server_url } /mcp" ) as (
2059+ async with streamable_http_client (f"{ server_url } /mcp" ) as (
20602060 read_stream ,
20612061 write_stream ,
20622062 _ ,
@@ -2088,7 +2088,7 @@ async def message_handler(
20882088
20892089
20902090@pytest .mark .anyio
2091- async def test_streamablehttp_events_replayed_after_disconnect (
2091+ async def test_streamable_http_events_replayed_after_disconnect (
20922092 event_server : tuple [SimpleEventStore , str ],
20932093) -> None :
20942094 """Events sent while client is disconnected should be replayed on reconnect."""
@@ -2104,7 +2104,7 @@ async def message_handler(
21042104 if isinstance (message .root , types .LoggingMessageNotification ): # pragma: no branch
21052105 notification_data .append (str (message .root .params .data ))
21062106
2107- async with streamablehttp_client (f"{ server_url } /mcp" ) as (
2107+ async with streamable_http_client (f"{ server_url } /mcp" ) as (
21082108 read_stream ,
21092109 write_stream ,
21102110 _ ,
@@ -2136,7 +2136,7 @@ async def message_handler(
21362136
21372137
21382138@pytest .mark .anyio
2139- async def test_streamablehttp_multiple_reconnections (
2139+ async def test_streamable_http_multiple_reconnections (
21402140 event_server : tuple [SimpleEventStore , str ],
21412141):
21422142 """Verify multiple close_sse_stream() calls each trigger a client reconnect.
@@ -2156,7 +2156,7 @@ async def test_streamablehttp_multiple_reconnections(
21562156 async def on_resumption_token (token : str ) -> None :
21572157 resumption_tokens .append (token )
21582158
2159- async with streamablehttp_client (f"{ server_url } /mcp" ) as (read_stream , write_stream , _ ):
2159+ async with streamable_http_client (f"{ server_url } /mcp" ) as (read_stream , write_stream , _ ):
21602160 async with ClientSession (read_stream , write_stream ) as session :
21612161 await session .initialize ()
21622162
@@ -2216,7 +2216,7 @@ async def message_handler(
22162216 if isinstance (message .root , types .ResourceUpdatedNotification ): # pragma: no branch
22172217 received_notifications .append (str (message .root .params .uri ))
22182218
2219- async with streamablehttp_client (f"{ server_url } /mcp" ) as (
2219+ async with streamable_http_client (f"{ server_url } /mcp" ) as (
22202220 read_stream ,
22212221 write_stream ,
22222222 _ ,
@@ -2247,3 +2247,112 @@ async def message_handler(
22472247 assert "http://notification_2/" in received_notifications , (
22482248 f"Should receive notification 2 after reconnect, got: { received_notifications } "
22492249 )
2250+
2251+
2252+ @pytest .mark .anyio
2253+ async def test_streamable_http_client_does_not_mutate_provided_client (
2254+ basic_server : None , basic_server_url : str
2255+ ) -> None :
2256+ """Test that streamable_http_client does not mutate the provided httpx client's headers."""
2257+ # Create a client with custom headers
2258+ original_headers = {
2259+ "X-Custom-Header" : "custom-value" ,
2260+ "Authorization" : "Bearer test-token" ,
2261+ }
2262+
2263+ async with httpx .AsyncClient (headers = original_headers , follow_redirects = True ) as custom_client :
2264+ # Use the client with streamable_http_client
2265+ async with streamable_http_client (f"{ basic_server_url } /mcp" , http_client = custom_client ) as (
2266+ read_stream ,
2267+ write_stream ,
2268+ _ ,
2269+ ):
2270+ async with ClientSession (read_stream , write_stream ) as session :
2271+ result = await session .initialize ()
2272+ assert isinstance (result , InitializeResult )
2273+
2274+ # Verify client headers were not mutated with MCP protocol headers
2275+ # If accept header exists, it should still be httpx default, not MCP's
2276+ if "accept" in custom_client .headers : # pragma: no branch
2277+ assert custom_client .headers .get ("accept" ) == "*/*"
2278+ # MCP content-type should not have been added
2279+ assert custom_client .headers .get ("content-type" ) != "application/json"
2280+
2281+ # Verify custom headers are still present and unchanged
2282+ assert custom_client .headers .get ("X-Custom-Header" ) == "custom-value"
2283+ assert custom_client .headers .get ("Authorization" ) == "Bearer test-token"
2284+
2285+
2286+ @pytest .mark .anyio
2287+ async def test_streamable_http_client_mcp_headers_override_defaults (
2288+ context_aware_server : None , basic_server_url : str
2289+ ) -> None :
2290+ """Test that MCP protocol headers override httpx.AsyncClient default headers."""
2291+ # httpx.AsyncClient has default "accept: */*" header
2292+ # We need to verify that our MCP accept header overrides it in actual requests
2293+
2294+ async with httpx .AsyncClient (follow_redirects = True ) as client :
2295+ # Verify client has default accept header
2296+ assert client .headers .get ("accept" ) == "*/*"
2297+
2298+ async with streamable_http_client (f"{ basic_server_url } /mcp" , http_client = client ) as (
2299+ read_stream ,
2300+ write_stream ,
2301+ _ ,
2302+ ):
2303+ async with ClientSession (read_stream , write_stream ) as session :
2304+ await session .initialize ()
2305+
2306+ # Use echo_headers tool to see what headers the server actually received
2307+ tool_result = await session .call_tool ("echo_headers" , {})
2308+ assert len (tool_result .content ) == 1
2309+ assert isinstance (tool_result .content [0 ], TextContent )
2310+ headers_data = json .loads (tool_result .content [0 ].text )
2311+
2312+ # Verify MCP protocol headers were sent (not httpx defaults)
2313+ assert "accept" in headers_data
2314+ assert "application/json" in headers_data ["accept" ]
2315+ assert "text/event-stream" in headers_data ["accept" ]
2316+
2317+ assert "content-type" in headers_data
2318+ assert headers_data ["content-type" ] == "application/json"
2319+
2320+
2321+ @pytest .mark .anyio
2322+ async def test_streamable_http_client_preserves_custom_with_mcp_headers (
2323+ context_aware_server : None , basic_server_url : str
2324+ ) -> None :
2325+ """Test that both custom headers and MCP protocol headers are sent in requests."""
2326+ custom_headers = {
2327+ "X-Custom-Header" : "custom-value" ,
2328+ "X-Request-Id" : "req-123" ,
2329+ "Authorization" : "Bearer test-token" ,
2330+ }
2331+
2332+ async with httpx .AsyncClient (headers = custom_headers , follow_redirects = True ) as client :
2333+ async with streamable_http_client (f"{ basic_server_url } /mcp" , http_client = client ) as (
2334+ read_stream ,
2335+ write_stream ,
2336+ _ ,
2337+ ):
2338+ async with ClientSession (read_stream , write_stream ) as session :
2339+ await session .initialize ()
2340+
2341+ # Use echo_headers tool to verify both custom and MCP headers are present
2342+ tool_result = await session .call_tool ("echo_headers" , {})
2343+ assert len (tool_result .content ) == 1
2344+ assert isinstance (tool_result .content [0 ], TextContent )
2345+ headers_data = json .loads (tool_result .content [0 ].text )
2346+
2347+ # Verify custom headers are present
2348+ assert headers_data .get ("x-custom-header" ) == "custom-value"
2349+ assert headers_data .get ("x-request-id" ) == "req-123"
2350+ assert headers_data .get ("authorization" ) == "Bearer test-token"
2351+
2352+ # Verify MCP protocol headers are also present
2353+ assert "accept" in headers_data
2354+ assert "application/json" in headers_data ["accept" ]
2355+ assert "text/event-stream" in headers_data ["accept" ]
2356+
2357+ assert "content-type" in headers_data
2358+ assert headers_data ["content-type" ] == "application/json"
0 commit comments