@@ -155,7 +155,11 @@ async def _handle_sse_event(
155155 message .id = original_request_id
156156
157157 session_message = SessionMessage (message )
158- await read_stream_writer .send (session_message )
158+ try :
159+ await read_stream_writer .send (session_message )
160+ except (anyio .ClosedResourceError , anyio .BrokenResourceError ): # pragma: no cover
161+ logger .debug ("Read stream closed, stopping SSE event handling" )
162+ return True
159163
160164 # Call resumption token callback if we have an ID
161165 if sse .id and resumption_callback :
@@ -170,9 +174,15 @@ async def _handle_sse_event(
170174 if original_request_id is not None :
171175 error_data = ErrorData (code = PARSE_ERROR , message = f"Failed to parse SSE message: { exc } " )
172176 error_msg = SessionMessage (JSONRPCError (jsonrpc = "2.0" , id = original_request_id , error = error_data ))
173- await read_stream_writer .send (error_msg )
177+ try :
178+ await read_stream_writer .send (error_msg )
179+ except (anyio .ClosedResourceError , anyio .BrokenResourceError ):
180+ pass
174181 return True
175- await read_stream_writer .send (exc )
182+ try :
183+ await read_stream_writer .send (exc )
184+ except (anyio .ClosedResourceError , anyio .BrokenResourceError ):
185+ pass
176186 return False
177187 else : # pragma: no cover
178188 logger .warning (f"Unknown SSE event: { sse .event } " )
@@ -271,14 +281,20 @@ async def _handle_post_request(self, ctx: RequestContext) -> None:
271281 if isinstance (message , JSONRPCRequest ): # pragma: no branch
272282 error_data = ErrorData (code = INVALID_REQUEST , message = "Session terminated" )
273283 session_message = SessionMessage (JSONRPCError (jsonrpc = "2.0" , id = message .id , error = error_data ))
274- await ctx .read_stream_writer .send (session_message )
284+ try :
285+ await ctx .read_stream_writer .send (session_message )
286+ except (anyio .ClosedResourceError , anyio .BrokenResourceError ): # pragma: no cover
287+ pass
275288 return
276289
277290 if response .status_code >= 400 :
278291 if isinstance (message , JSONRPCRequest ):
279292 error_data = ErrorData (code = INTERNAL_ERROR , message = "Server returned an error response" )
280293 session_message = SessionMessage (JSONRPCError (jsonrpc = "2.0" , id = message .id , error = error_data ))
281- await ctx .read_stream_writer .send (session_message )
294+ try :
295+ await ctx .read_stream_writer .send (session_message )
296+ except (anyio .ClosedResourceError , anyio .BrokenResourceError ): # pragma: no cover
297+ pass
282298 return
283299
284300 if is_initialization :
@@ -298,7 +314,10 @@ async def _handle_post_request(self, ctx: RequestContext) -> None:
298314 logger .error (f"Unexpected content type: { content_type } " )
299315 error_data = ErrorData (code = INVALID_REQUEST , message = f"Unexpected content type: { content_type } " )
300316 error_msg = SessionMessage (JSONRPCError (jsonrpc = "2.0" , id = message .id , error = error_data ))
301- await ctx .read_stream_writer .send (error_msg )
317+ try :
318+ await ctx .read_stream_writer .send (error_msg )
319+ except (anyio .ClosedResourceError , anyio .BrokenResourceError ): # pragma: no cover
320+ pass
302321
303322 async def _handle_json_response (
304323 self ,
@@ -318,12 +337,18 @@ async def _handle_json_response(
318337 self ._maybe_extract_protocol_version_from_message (message )
319338
320339 session_message = SessionMessage (message )
321- await read_stream_writer .send (session_message )
340+ try :
341+ await read_stream_writer .send (session_message )
342+ except (anyio .ClosedResourceError , anyio .BrokenResourceError ): # pragma: no cover
343+ return
322344 except (httpx .StreamError , ValidationError ) as exc :
323345 logger .exception ("Error parsing JSON response" )
324346 error_data = ErrorData (code = PARSE_ERROR , message = f"Failed to parse JSON response: { exc } " )
325347 error_msg = SessionMessage (JSONRPCError (jsonrpc = "2.0" , id = request_id , error = error_data ))
326- await read_stream_writer .send (error_msg )
348+ try :
349+ await read_stream_writer .send (error_msg )
350+ except (anyio .ClosedResourceError , anyio .BrokenResourceError ): # pragma: no cover
351+ return
327352
328353 async def _handle_sse_response (
329354 self ,
@@ -533,8 +558,8 @@ async def streamable_http_client(
533558 Example:
534559 See examples/snippets/clients/ for usage patterns.
535560 """
536- read_stream_writer , read_stream = anyio .create_memory_object_stream [SessionMessage | Exception ](0 )
537- write_stream , write_stream_reader = anyio .create_memory_object_stream [SessionMessage ](0 )
561+ read_stream_writer , read_stream = anyio .create_memory_object_stream [SessionMessage | Exception ](1 )
562+ write_stream , write_stream_reader = anyio .create_memory_object_stream [SessionMessage ](1 )
538563
539564 # Determine if we need to create and manage the client
540565 client_provided = http_client is not None
@@ -573,6 +598,10 @@ def start_get_stream() -> None:
573598 finally :
574599 if transport .session_id and terminate_on_close :
575600 await transport .terminate_session (client )
601+ # Close streams before cancelling to unblock tasks
602+ # waiting on stream send/receive during shutdown.
603+ await read_stream_writer .aclose ()
604+ await write_stream .aclose ()
576605 tg .cancel_scope .cancel ()
577606 finally :
578607 await read_stream_writer .aclose ()
0 commit comments