Handle 202 Accepted response in HTTP client#323
Handle 202 Accepted response in HTTP client#323koic merged 1 commit intomodelcontextprotocol:mainfrom
Conversation
f7ecdf3 to
58cb5f9
Compare
| elsif response.status == 202 | ||
| # Server accepted the request and will deliver the response via an SSE stream. | ||
| # https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#sending-messages-to-the-server | ||
| { "accepted" => true } |
There was a problem hiding this comment.
I think the spec defines 202 as the ACK for a notification/response, rather than "request accepted, SSE coming later". The TypeScript and Python SDKs also check status == 202 before Content-Type and return void rather than a Hash sentinel. In the current diff, the 202 branch comes last, so a 202 with any Content-Type header would skip it; the existing test happens to pass only because WebMock leaves Content-Type unset. Also, { "accepted" => true } overlaps with the Hash shape of real JSON-RPC responses. What do you think about something closer to this?
def parse_response_body(response, method, params)
+ # 202 Accepted is the ACK for a notification/response; no body is expected.
+ # https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#sending-messages-to-the-server
+ return if response.status == 202
+
content_type = response.headers["Content-Type"]
if content_type&.include?("text/event-stream")
parse_sse_response(response.body, method, params)
elsif content_type&.include?("application/json")
response.body
- elsif response.status == 202
- # Server accepted the request and will deliver the response via an SSE stream.
- # https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#sending-messages-to-the-server
- { "accepted" => true }
else
raise RequestHandlerError.new(...)
end
endThe tests will need a matching update as well.
There was a problem hiding this comment.
Verified against the spec and the TS/Python SDKs. Updated the diff to check status == 202 before Content-Type dispatch and return nil instead of the Hash sentinel. Also corrected the commit message (202 is the ACK for a notification/response, not "SSE coming later") and cleaned up a stale doc string in examples/streamable_http_server.rb. Thanks for catching this!
Per the Streamable HTTP spec, a server returns 202 Accepted with no body as the ACK for a JSON-RPC notification or response. The client previously errored on 202 because it fell through Content-Type dispatch. Short-circuit to return nil before Content-Type handling so fire-and-forget messages (e.g. notifications/initialized) no longer raise. https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#sending-messages-to-the-server Co-Authored-By: Claude Opus 4.7 <[email protected]>
58cb5f9 to
cfc42db
Compare
|
Thanks! |
Summary
Second slice of #321 (Streamable HTTP client support).
The Streamable HTTP spec allows a server to respond
202 Acceptedwhen it has received the request but will deliver the response later via an SSE stream. The client currently errors on 202 because the response has no parseableContent-Type. Return{ "accepted" => true }so callers can proceed rather than hard-fail on a valid server response.Actually picking up the deferred response requires listening on an SSE stream (GET-for-SSE), which is a TODO in the main PR and out of scope here. This change is just the graceful-no-hard-error part.
Changes
lib/mcp/client/http.rb: addelsif response.status == 202branch toparse_response_bodyreturning{ "accepted" => true }test/mcp/client/http_test.rb: test for 202 response with empty bodyTest plan
rake testpasses (738 runs, 1848 assertions)rake rubocopno new offenses on changed filesStack
connect, session-ID tracking,close/DELETE🤖 Generated with Claude Code