@@ -611,3 +611,30 @@ async def mock_aiter_sse() -> AsyncGenerator[ServerSentEvent, None]:
611611 assert not isinstance (msg , Exception )
612612 assert isinstance (msg .message , types .JSONRPCResponse )
613613 assert msg .message .id == 1
614+
615+
616+ @pytest .mark .anyio
617+ async def test_sse_session_cleanup_on_disconnect (server : None , server_url : str ) -> None :
618+ """Regression test for https://github.com/modelcontextprotocol/python-sdk/issues/1227
619+
620+ When a client disconnects, the server should remove the session from
621+ _read_stream_writers. Without this cleanup, stale sessions accumulate and
622+ POST requests to disconnected sessions return 202 Accepted followed by a
623+ ClosedResourceError when the server tries to write to the dead stream.
624+ """
625+ captured : list [str ] = []
626+
627+ # Connect a client session, then disconnect
628+ async with sse_client (server_url + "/sse" , on_session_created = captured .append ) as streams :
629+ async with ClientSession (* streams ) as session :
630+ await session .initialize ()
631+
632+ # After disconnect, POST to the stale session should return 404
633+ # (not 202 as it did before the fix)
634+ async with httpx .AsyncClient () as client :
635+ response = await client .post (
636+ f"{ server_url } /messages/?session_id={ captured [0 ]} " ,
637+ json = {"jsonrpc" : "2.0" , "method" : "ping" , "id" : 99 },
638+ headers = {"Content-Type" : "application/json" },
639+ )
640+ assert response .status_code == 404
0 commit comments