File tree Expand file tree Collapse file tree
Expand file tree Collapse file tree Original file line number Diff line number Diff line change @@ -1173,8 +1173,12 @@ def sse_app(
11731173
11741174 async def sse_endpoint (request : Request ) -> Response :
11751175 if request .method == "HEAD" :
1176- # The SSE stream never ends, so a HEAD answered with it would never complete.
1177- return Response (status_code = 405 , headers = {"Allow" : "GET" })
1176+ # GET's headers without its never-ending stream; a Content-Length would claim an empty one.
1177+ response = Response (
1178+ media_type = "text/event-stream" , headers = {"Cache-Control" : "no-store" , "X-Accel-Buffering" : "no" }
1179+ )
1180+ del response .headers ["content-length" ]
1181+ return response
11781182 async with sse .connect_sse ( # pragma: no cover
11791183 request .scope ,
11801184 request .receive ,
Original file line number Diff line number Diff line change 1515
1616 # Define handler functions
1717 async def handle_sse(request):
18- # Starlette also routes HEAD here; the stream never ends, so a HEAD would never complete
18+ # Starlette also routes HEAD here: answer with GET's headers, not its never-ending stream
1919 if request.method == "HEAD":
20- return Response(status_code=405, headers={"Allow": "GET"})
20+ response = Response(media_type="text/event-stream", headers={"Cache-Control": "no-store"})
21+ del response.headers["content-length"]
22+ return response
2123 async with sse.connect_sse(
2224 request.scope, request.receive, request._send
2325 ) as streams:
Original file line number Diff line number Diff line change @@ -2708,8 +2708,8 @@ def __post_init__(self) -> None:
27082708 "transport:sse:head" : Requirement (
27092709 source = "issue:#3736" ,
27102710 behavior = (
2711- "A HEAD request to the SSE endpoint of `MCPServer.sse_app()` is answered 405 with `Allow: GET` "
2712- "without opening a session or a stream, so the connection stays usable ."
2711+ "A HEAD request to the SSE endpoint of `MCPServer.sse_app()` is answered at once with the "
2712+ "headers a GET would get and no body or Content-Length, without opening a session or a stream."
27132713 ),
27142714 transports = ("sse" ,),
27152715 note = "Only observable over the legacy SSE transport." ,
Original file line number Diff line number Diff line change @@ -93,12 +93,21 @@ async def test_post_for_an_unknown_session_is_rejected() -> None:
9393
9494
9595@requirement ("transport:sse:head" )
96- async def test_head_on_the_sse_endpoint_is_answered_without_opening_a_stream () -> None :
96+ async def test_head_on_the_sse_endpoint_is_answered_with_headers_only () -> None :
9797 """`MCPServer.sse_app()` answers HEAD at once instead of streaming forever."""
9898 app = MCPServer ("legacy" ).sse_app (
9999 transport_security = TransportSecuritySettings (enable_dns_rebinding_protection = False )
100100 )
101101 async with httpx2 .AsyncClient (transport = StreamingASGITransport (app ), base_url = BASE_URL ) as http :
102102 with anyio .fail_after (5 ):
103103 response = await http .head ("/sse" )
104- assert (response .status_code , response .headers ["allow" ]) == snapshot ((405 , "GET" ))
104+ assert (response .status_code , dict (response .headers )) == snapshot (
105+ (
106+ 200 ,
107+ {
108+ "cache-control" : "no-store" ,
109+ "x-accel-buffering" : "no" ,
110+ "content-type" : "text/event-stream; charset=utf-8" ,
111+ },
112+ )
113+ )
You can’t perform that action at this time.
0 commit comments