@@ -500,6 +500,39 @@ async def list_tools(ctx: ServerRequestContext, params: PaginatedRequestParams |
500500 assert events [2 ]["result" ]["tools" ] == []
501501
502502
503+ async def test_sse_mode_headers_are_not_shared_across_responses () -> None :
504+ """SSE mode: each response gets its own header list, so middleware appending in place
505+ cannot leak headers into later responses."""
506+
507+ async def list_tools (ctx : ServerRequestContext , params : PaginatedRequestParams | None ) -> ListToolsResult :
508+ await ctx .session .report_progress (1.0 )
509+ return ListToolsResult (tools = [], ttl_ms = 0 , cache_scope = "public" )
510+
511+ server = Server ("test" , on_list_tools = list_tools )
512+
513+ async def app (scope : Scope , receive : Receive , send : Send ) -> None :
514+ async def append_header (message : Message ) -> None :
515+ if message ["type" ] == "http.response.start" :
516+ message ["headers" ].append ((b"x-id" , b"abc" ))
517+ await send (message )
518+
519+ async with server .lifespan (server ) as lifespan_state :
520+ await handle_modern_request (server , None , False , lifespan_state , scope , receive , append_header )
521+
522+ async with httpx2 .AsyncClient (
523+ transport = StreamingASGITransport (app ),
524+ base_url = "http://testserver" ,
525+ headers = {MCP_PROTOCOL_VERSION_HEADER : LATEST_MODERN_VERSION , "accept" : "application/json, text/event-stream" },
526+ ) as http :
527+ for _ in range (2 ):
528+ with anyio .fail_after (5 ):
529+ response = await http .post (
530+ "/mcp" , json = _list_tools_body_with_token ("tok" ), headers = {MCP_METHOD_HEADER : "tools/list" }
531+ )
532+ assert response .headers ["content-type" ].split (";" , 1 )[0 ] == "text/event-stream"
533+ assert response .headers .get_list ("x-id" ) == ["abc" ]
534+
535+
503536@pytest .mark .parametrize (
504537 "anyio_backend" ,
505538 [pytest .param (("trio" , {"clock" : MockClock (autojump_threshold = 0 )}), id = "trio-mockclock" )],
0 commit comments