@@ -686,6 +686,15 @@ async def run(
686686 # but also make tracing exceptions much easier during testing and when using
687687 # in-process servers.
688688 raise_exceptions : bool = False ,
689+ # When True, the server is stateless and
690+ # clients can perform initialization with any node. The client must still follow
691+ # the initialization lifecycle, but can do so with any available node
692+ # rather than requiring initialization for each connection.
693+ stateless : bool = False ,
694+ # When True, treat read EOF as a half-close and allow in-flight handlers
695+ # to drain their responses via the still-open write stream (e.g. stdio
696+ # with bash-redirected stdin).
697+ drain_on_read_close : bool = False ,
689698 ) -> None :
690699 """Serve a single connection over the given streams until the read side closes.
691700
@@ -696,15 +705,20 @@ async def run(
696705 streamable-HTTP manager) call `serve_loop` directly instead.
697706 """
698707 async with self .lifespan (self ) as lifespan_context :
699- await serve_dual_era_loop (
700- self ,
701- read_stream ,
702- write_stream ,
703- lifespan_state = lifespan_context ,
704- init_options = initialization_options ,
705- raise_exceptions = raise_exceptions ,
706- close_write_stream_on_read_close = False ,
707- )
708+ try :
709+ await serve_dual_era_loop (
710+ self ,
711+ read_stream ,
712+ write_stream ,
713+ lifespan_state = lifespan_context ,
714+ init_options = initialization_options ,
715+ raise_exceptions = raise_exceptions ,
716+ session_id = None ,
717+ close_write_stream_on_read_close = not drain_on_read_close ,
718+ )
719+ finally :
720+ if drain_on_read_close :
721+ await write_stream .aclose ()
708722
709723 def streamable_http_app (
710724 self ,
0 commit comments