Skip to content

Handle 202 Accepted response in HTTP client#323

Merged
koic merged 1 commit intomodelcontextprotocol:mainfrom
atesgoral:ag/client-202-accepted
Apr 20, 2026
Merged

Handle 202 Accepted response in HTTP client#323
koic merged 1 commit intomodelcontextprotocol:mainfrom
atesgoral:ag/client-202-accepted

Conversation

@atesgoral
Copy link
Copy Markdown
Contributor

@atesgoral atesgoral commented Apr 18, 2026

Summary

Second slice of #321 (Streamable HTTP client support).

The Streamable HTTP spec allows a server to respond 202 Accepted when 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 parseable Content-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: add elsif response.status == 202 branch to parse_response_body returning { "accepted" => true }
  • test/mcp/client/http_test.rb: test for 202 response with empty body

Test plan

  • rake test passes (738 runs, 1848 assertions)
  • rake rubocop no new offenses on changed files
  • New test covers 202 response with empty body

Stack

🤖 Generated with Claude Code

@atesgoral atesgoral marked this pull request as draft April 18, 2026 23:46
@atesgoral atesgoral force-pushed the ag/client-202-accepted branch 3 times, most recently from f7ecdf3 to 58cb5f9 Compare April 19, 2026 16:06
@atesgoral atesgoral marked this pull request as ready for review April 19, 2026 16:07
@atesgoral atesgoral requested a review from koic April 19, 2026 16:07
Comment thread lib/mcp/client/http.rb Outdated
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 }
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
 end

The tests will need a matching update as well.

Copy link
Copy Markdown
Contributor Author

@atesgoral atesgoral Apr 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 <noreply@anthropic.com>
@atesgoral atesgoral force-pushed the ag/client-202-accepted branch from 58cb5f9 to cfc42db Compare April 19, 2026 18:06
@atesgoral atesgoral requested a review from koic April 19, 2026 18:24
@koic koic merged commit cbe8145 into modelcontextprotocol:main Apr 20, 2026
11 checks passed
@koic
Copy link
Copy Markdown
Member

koic commented Apr 20, 2026

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants