@@ -1043,6 +1043,106 @@ async def test_streamable_http_client_error_handling(initialized_client_session:
10431043 assert "Unknown resource: unknown://test-error" in exc_info .value .error .message
10441044
10451045
1046+ @pytest .mark .anyio
1047+ async def test_streamable_http_client_http_error_does_not_cancel_concurrent_request ():
1048+ """Test that one POST HTTP error does not tear down an unrelated request."""
1049+ good_request_started = anyio .Event ()
1050+ allow_good_response = anyio .Event ()
1051+
1052+ async def handler (request : httpx .Request ) -> httpx .Response :
1053+ payload = json .loads (request .content )
1054+ request_id = payload ["id" ]
1055+ uri = payload ["params" ]["uri" ]
1056+
1057+ if uri == "foobar://bad" :
1058+ with anyio .fail_after (5 ):
1059+ await good_request_started .wait ()
1060+ return httpx .Response (400 , request = request , json = {"error" : "boom" })
1061+
1062+ assert uri == "foobar://good"
1063+ good_request_started .set ()
1064+ with anyio .fail_after (5 ):
1065+ await allow_good_response .wait ()
1066+ return httpx .Response (
1067+ 200 ,
1068+ request = request ,
1069+ headers = {"content-type" : "application/json" },
1070+ json = {
1071+ "jsonrpc" : "2.0" ,
1072+ "id" : request_id ,
1073+ "result" : {
1074+ "contents" : [
1075+ {
1076+ "uri" : uri ,
1077+ "mimeType" : "text/plain" ,
1078+ "text" : "good response" ,
1079+ }
1080+ ]
1081+ },
1082+ },
1083+ )
1084+
1085+ good_result : types .ReadResourceResult | None = None
1086+ good_error : Exception | None = None
1087+ bad_error : Exception | None = None
1088+
1089+ async def run_good_request (session : ClientSession ) -> None :
1090+ nonlocal good_result , good_error
1091+ try :
1092+ good_result = await session .send_request (
1093+ types .ClientRequest (
1094+ types .ReadResourceRequest (
1095+ params = types .ReadResourceRequestParams (uri = AnyUrl ("foobar://good" )),
1096+ )
1097+ ),
1098+ types .ReadResourceResult ,
1099+ )
1100+ except Exception as exc :
1101+ good_error = exc
1102+
1103+ async def run_bad_request (session : ClientSession ) -> None :
1104+ nonlocal bad_error
1105+ try :
1106+ await session .send_request (
1107+ types .ClientRequest (
1108+ types .ReadResourceRequest (
1109+ params = types .ReadResourceRequestParams (uri = AnyUrl ("foobar://bad" )),
1110+ )
1111+ ),
1112+ types .ReadResourceResult ,
1113+ )
1114+ except Exception as exc :
1115+ bad_error = exc
1116+
1117+ transport = httpx .MockTransport (handler )
1118+ async with httpx .AsyncClient (transport = transport ) as http_client :
1119+ async with streamable_http_client ("http://test/mcp" , http_client = http_client ) as (
1120+ read_stream ,
1121+ write_stream ,
1122+ _ ,
1123+ ):
1124+ async with ClientSession (read_stream , write_stream ) as session :
1125+ async with anyio .create_task_group () as tg :
1126+ tg .start_soon (run_good_request , session )
1127+ with anyio .fail_after (5 ):
1128+ await good_request_started .wait ()
1129+
1130+ tg .start_soon (run_bad_request , session )
1131+
1132+ with anyio .fail_after (5 ):
1133+ while bad_error is None :
1134+ await anyio .sleep (0 )
1135+
1136+ allow_good_response .set ()
1137+
1138+ assert isinstance (bad_error , McpError )
1139+ assert bad_error .error .code == types .INTERNAL_ERROR
1140+ assert good_error is None
1141+ assert good_result is not None
1142+ assert isinstance (good_result .contents [0 ], types .TextResourceContents )
1143+ assert good_result .contents [0 ].text == "good response"
1144+
1145+
10461146@pytest .mark .anyio
10471147async def test_streamable_http_client_session_persistence (basic_server : None , basic_server_url : str ):
10481148 """Test that session ID persists across requests."""
0 commit comments