@@ -1346,6 +1346,73 @@ async def run_tool():
1346
1346
assert len (request_state_manager_2 ._response_streams ) == 0
1347
1347
1348
1348
1349
+ @pytest .mark .anyio
1350
+ async def test_streamablehttp_client_non_blocking_timeout (event_server : tuple [SimpleEventStore , str ]):
1351
+ """Test client session start timeout."""
1352
+ _ , server_url = event_server
1353
+
1354
+ with anyio .fail_after (10 ):
1355
+ # Variables to track the state
1356
+ captured_notifications : list [types .ServerNotification ] = []
1357
+ tool_started = anyio .Event ()
1358
+ tool_cancelled = anyio .Event ()
1359
+
1360
+ request_state_manager = InMemoryRequestStateManager [types .ClientRequest , types .ClientResult ]()
1361
+
1362
+ async def message_handler (
1363
+ message : RequestResponder [types .ServerRequest , types .ClientResult ] | types .ServerNotification | Exception ,
1364
+ ) -> None :
1365
+ if isinstance (message , types .ServerNotification ):
1366
+ captured_notifications .append (message )
1367
+ # Look for our special notification that indicates the tool is running
1368
+ if isinstance (message .root , types .LoggingMessageNotification ):
1369
+ if message .root .params .data == "Tool started" :
1370
+ nonlocal tool_started
1371
+ tool_started .set ()
1372
+ else :
1373
+ await tool_cancelled .wait ()
1374
+
1375
+ if isinstance (message .root , types .CancelledNotification ):
1376
+ nonlocal tool_cancelled
1377
+ tool_cancelled .set ()
1378
+
1379
+
1380
+ # First, start the client session and begin the long-running tool
1381
+ async with streamablehttp_client (f"{ server_url } /mcp" , terminate_on_close = False ) as (
1382
+ read_stream ,
1383
+ write_stream ,
1384
+ _ ,
1385
+ ):
1386
+ async with ClientSession (
1387
+ read_stream ,
1388
+ write_stream ,
1389
+ message_handler = message_handler ,
1390
+ request_state_manager = request_state_manager ,
1391
+ ) as session :
1392
+ # Initialize the session
1393
+ result = await session .initialize ()
1394
+ assert isinstance (result , InitializeResult )
1395
+
1396
+ # Start a long-running tool in a task
1397
+ async with anyio .create_task_group () as tg :
1398
+ async def run_tool ():
1399
+ request_id = await session .request_call_tool (
1400
+ "long_running_with_checkpoints" , arguments = {},
1401
+ timeout = 0.01 ,
1402
+ cancel_if_not_resumable = True
1403
+ )
1404
+ assert request_id is None
1405
+
1406
+ tg .start_soon (run_tool )
1407
+
1408
+ await tool_started .wait ()
1409
+ await tool_cancelled .wait ()
1410
+
1411
+ assert tool_started .is_set () and tool_cancelled .is_set ()
1412
+ assert len (request_state_manager ._progress_callbacks ) == 0
1413
+ assert len (request_state_manager ._response_streams ) == 0
1414
+
1415
+
1349
1416
@pytest .mark .anyio
1350
1417
async def test_streamablehttp_client_resumption_timeout (event_server : tuple [SimpleEventStore , str ]):
1351
1418
"""Test client session to resume a long running tool via non blocking api with timeout."""
@@ -1401,6 +1468,7 @@ async def run_tool():
1401
1468
captured_request_id = await session .request_call_tool (
1402
1469
"long_running_with_checkpoints" , arguments = {}
1403
1470
)
1471
+ assert captured_request_id is not None
1404
1472
1405
1473
result = await session .join_call_tool (
1406
1474
captured_request_id ,
0 commit comments