@@ -39,7 +39,7 @@ async def main():
3939import contextvars
4040import logging
4141import warnings
42- from collections .abc import AsyncIterator , Awaitable , Callable
42+ from collections .abc import AsyncGenerator , Awaitable , Callable
4343from contextlib import AbstractAsyncContextManager , AsyncExitStack , asynccontextmanager
4444from importlib .metadata import version as importlib_version
4545from typing import Any , Generic , cast
@@ -85,7 +85,7 @@ def __init__(self, prompts_changed: bool = False, resources_changed: bool = Fals
8585
8686
8787@asynccontextmanager
88- async def lifespan (_ : Server [LifespanResultT ]) -> AsyncIterator [dict [str , Any ]]:
88+ async def lifespan (_ : Server [LifespanResultT ]) -> AsyncGenerator [dict [str , Any ]]:
8989 """Default lifespan context manager that does nothing.
9090
9191 Returns:
@@ -371,6 +371,10 @@ async def run(
371371 # the initialization lifecycle, but can do so with any available node
372372 # rather than requiring initialization for each connection.
373373 stateless : bool = False ,
374+ # When True, stdin/file-style EOF is treated as "no more inbound messages";
375+ # accepted request handlers are allowed to finish and flush their responses.
376+ drain_in_flight_on_read_eof : bool = False ,
377+ drain_in_flight_on_read_eof_timeout_seconds : float = 5.0 ,
374378 ):
375379 async with AsyncExitStack () as stack :
376380 lifespan_context = await stack .enter_async_context (self .lifespan (self ))
@@ -380,6 +384,7 @@ async def run(
380384 write_stream ,
381385 initialization_options ,
382386 stateless = stateless ,
387+ close_write_stream_on_read_end = not drain_in_flight_on_read_eof ,
383388 )
384389 )
385390
@@ -408,11 +413,14 @@ async def run(
408413 raise_exceptions ,
409414 )
410415 finally :
411- # Transport closed: cancel in-flight handlers. Without this the
412- # TG join waits for them, and when they eventually try to
413- # respond they hit a closed write stream (the session's
414- # _receive_loop closed it when the read stream ended).
415- tg .cancel_scope .cancel ()
416+ if not drain_in_flight_on_read_eof :
417+ # Transport closed: cancel in-flight handlers. Without this the
418+ # TG join waits for them, and when they eventually try to
419+ # respond they hit a closed write stream (the session's
420+ # _receive_loop closed it when the read stream ended).
421+ tg .cancel_scope .cancel ()
422+ else :
423+ tg .cancel_scope .deadline = anyio .current_time () + drain_in_flight_on_read_eof_timeout_seconds
416424
417425 async def _handle_message (
418426 self ,
0 commit comments