@@ -163,6 +163,28 @@ def handler(request: httpx2.Request) -> httpx2.Response:
163163 assert received .message .params == {"data" : payload }
164164
165165
166+ class _ParkedSSEStream (httpx2 .AsyncByteStream ):
167+ """An SSE response body that emits one frame, then parks until closed.
168+
169+ `opened` fires when the body is consumed; `closed` fires when httpx2 releases it.
170+ """
171+
172+ def __init__ (self , content : bytes = b": parked\n \n " ) -> None :
173+ self .opened = anyio .Event ()
174+ self .closed = anyio .Event ()
175+ self ._release = anyio .Event ()
176+ self ._content = content
177+
178+ async def __aiter__ (self ) -> AsyncIterator [bytes ]:
179+ self .opened .set ()
180+ yield self ._content
181+ await self ._release .wait ()
182+
183+ async def aclose (self ) -> None :
184+ self .closed .set ()
185+ self ._release .set ()
186+
187+
166188@pytest .mark .anyio
167189async def test_oversized_get_event_reconnects_and_delivers_later_messages () -> None :
168190 """SDK-defined: a bad GET event does not prevent later server messages or POSTs.
@@ -177,11 +199,14 @@ def handler(request: httpx2.Request) -> httpx2.Response:
177199 if len (gets ) == 1 :
178200 content = b'retry: 0\n \n data: {"jsonrpc":"2.0","method":"notifications/message","params":{"data":"'
179201 content += b"x" * 2048 + b'"}}\n \n '
180- elif len (gets ) == 2 :
181- content = b'data: {"jsonrpc":"2.0","method":"notifications/message","params":{"data":"ok"}}\n \n '
182- else :
183- return httpx2 .Response (500 )
184- return httpx2 .Response (200 , content = content , headers = {"content-type" : "text/event-stream" })
202+ return httpx2 .Response (200 , content = content , headers = {"content-type" : "text/event-stream" })
203+ return httpx2 .Response (
204+ 200 ,
205+ stream = _ParkedSSEStream (
206+ b'data: {"jsonrpc":"2.0","method":"notifications/message","params":{"data":"ok"}}\n \n '
207+ ),
208+ headers = {"content-type" : "text/event-stream" },
209+ )
185210 body = json .loads (request .content )
186211 if body .get ("method" ) == "initialize" :
187212 return httpx2 .Response (
@@ -211,7 +236,7 @@ def handler(request: httpx2.Request) -> httpx2.Response:
211236 assert isinstance (reply , SessionMessage )
212237 assert isinstance (reply .message , JSONRPCResponse )
213238 assert reply .message .id == 2
214- assert len (gets ) > = 2
239+ assert len (gets ) = = 2
215240
216241
217242@pytest .mark .anyio
@@ -500,29 +525,6 @@ def handler(request: httpx2.Request) -> httpx2.Response:
500525 assert recorded [3 ].headers [MCP_PROTOCOL_VERSION_HEADER ] == "2025-11-25"
501526
502527
503- class _ParkedSSEStream (httpx2 .AsyncByteStream ):
504- """An SSE response body that emits one comment line, then parks until closed.
505-
506- `opened` fires once the transport is iterating the body (the POST is truly in
507- flight); `closed` fires when httpx2 tears the body down — the observable proof
508- that an abort, not a response, ended the stream.
509- """
510-
511- def __init__ (self ) -> None :
512- self .opened = anyio .Event ()
513- self .closed = anyio .Event ()
514- self ._release = anyio .Event ()
515-
516- async def __aiter__ (self ) -> AsyncIterator [bytes ]:
517- self .opened .set ()
518- yield b": parked\n \n "
519- await self ._release .wait ()
520-
521- async def aclose (self ) -> None :
522- self .closed .set ()
523- self ._release .set ()
524-
525-
526528def _sse_or_ack_handler (
527529 parked : _ParkedSSEStream , posted : list [dict [str , Any ]], frame_posted : anyio .Event
528530) -> Callable [[httpx2 .Request ], httpx2 .Response ]:
0 commit comments