@@ -76,9 +76,9 @@ async def test_handle_request_without_run_raises_error():
7676 manager = StreamableHTTPSessionManager (app = app )
7777
7878 # Mock ASGI parameters
79- scope = {"type" : "http" , "method" : "POST" , "path" : "/test" }
79+ scope : Scope = {"type" : "http" , "method" : "POST" , "path" : "/test" , "headers" : [] }
8080
81- async def receive (): # pragma: no cover
81+ async def receive () -> Message :
8282 return {"type" : "http.request" , "body" : b"" }
8383
8484 async def send (message : Message ): # pragma: no cover
@@ -108,7 +108,7 @@ async def send(message: Message) -> None:
108108 "headers" : [(b"content-length" , b"9" )],
109109 }
110110 async with manager .run ():
111- await manager .asgi_app (scope , receive , send )
111+ await manager .handle_request (scope , receive , send )
112112 assert manager ._server_instances == {}
113113
114114 response_start = next (message for message in sent_messages if message ["type" ] == "http.response.start" )
@@ -150,6 +150,33 @@ async def send(message: Message) -> None:
150150async def test_client_disconnect_while_streaming_request_body_is_replayed () -> None :
151151 """SDK-defined: raw ASGI is required to prove a disconnect before body completion reaches the transport."""
152152 disconnect : Message = {"type" : "http.disconnect" }
153+ request_messages : Iterator [Message ] = iter (
154+ [{"type" : "http.request" , "body" : b"1234" , "more_body" : True }, disconnect ]
155+ )
156+ received_messages : list [Message ] = []
157+
158+ async def receive () -> Message :
159+ return next (request_messages )
160+
161+ async def app (scope : Scope , receive : Receive , send : Send ) -> None :
162+ received_messages .append (await receive ())
163+ received_messages .append (await receive ())
164+
165+ scope : Scope = {"type" : "http" , "method" : "POST" , "path" : "/mcp" , "headers" : []}
166+ middleware = RequestBodyLimitMiddleware (app , max_body_size = 8 )
167+
168+ await middleware (scope , receive , AsyncMock ())
169+
170+ assert received_messages == [
171+ {"type" : "http.request" , "body" : b"1234" , "more_body" : True },
172+ disconnect ,
173+ ]
174+
175+
176+ @pytest .mark .anyio
177+ async def test_client_disconnect_before_request_body_is_replayed () -> None :
178+ """SDK-defined: raw ASGI proves a disconnect before the first body message reaches the transport."""
179+ disconnect : Message = {"type" : "http.disconnect" }
153180 received_messages : list [Message ] = []
154181
155182 async def receive () -> Message :
@@ -166,6 +193,32 @@ async def app(scope: Scope, receive: Receive, send: Send) -> None:
166193 assert received_messages == [disconnect ]
167194
168195
196+ @pytest .mark .anyio
197+ async def test_request_body_chunks_are_replayed_as_one_message () -> None :
198+ """SDK-defined: raw ASGI proves chunk overhead is discarded before the body reaches the transport."""
199+ request_messages : Iterator [Message ] = iter (
200+ [
201+ {"type" : "http.request" , "body" : b"12" , "more_body" : True },
202+ {"type" : "http.request" , "body" : b"34" , "more_body" : True },
203+ {"type" : "http.request" , "body" : b"56" , "more_body" : False },
204+ ]
205+ )
206+ received_messages : list [Message ] = []
207+
208+ async def receive () -> Message :
209+ return next (request_messages )
210+
211+ async def app (scope : Scope , receive : Receive , send : Send ) -> None :
212+ received_messages .append (await receive ())
213+
214+ scope : Scope = {"type" : "http" , "method" : "POST" , "path" : "/mcp" , "headers" : []}
215+ middleware = RequestBodyLimitMiddleware (app , max_body_size = 8 )
216+
217+ await middleware (scope , receive , AsyncMock ())
218+
219+ assert received_messages == [{"type" : "http.request" , "body" : b"123456" , "more_body" : False }]
220+
221+
169222def test_request_body_limit_defaults_to_four_mib () -> None :
170223 """SDK-defined: Streamable HTTP request bodies are limited to 4 MiB by default."""
171224 manager = StreamableHTTPSessionManager (app = Server ("test-default-size-limit" ))
@@ -216,7 +269,7 @@ async def mock_send(message: Message):
216269 "headers" : [(b"content-type" , b"application/json" )],
217270 }
218271
219- async def mock_receive (): # pragma: no cover
272+ async def mock_receive ():
220273 return {"type" : "http.request" , "body" : b"" , "more_body" : False }
221274
222275 # Trigger session creation
@@ -274,7 +327,7 @@ async def mock_send(message: Message):
274327 "headers" : [(b"content-type" , b"application/json" )],
275328 }
276329
277- async def mock_receive (): # pragma: no cover
330+ async def mock_receive ():
278331 return {"type" : "http.request" , "body" : b"" , "more_body" : False }
279332
280333 # Trigger session creation
@@ -392,7 +445,7 @@ async def mock_send(message: Message):
392445 }
393446
394447 async def mock_receive ():
395- return {"type" : "http.request" , "body" : b"{}" , "more_body" : False } # pragma: no cover
448+ return {"type" : "http.request" , "body" : b"{}" , "more_body" : False }
396449
397450 with caplog .at_level (logging .INFO ):
398451 await manager .handle_request (scope , mock_receive , mock_send )
@@ -472,7 +525,7 @@ async def mock_send(message: Message):
472525 "headers" : [(b"content-type" , b"application/json" )],
473526 }
474527
475- async def mock_receive (): # pragma: no cover
528+ async def mock_receive ():
476529 return {"type" : "http.request" , "body" : b"" , "more_body" : False }
477530
478531 await manager .handle_request (scope , mock_receive , mock_send )
0 commit comments