Skip to content

Parse HTTP client response bodies without Faraday's JSON middleware - #546

Merged
koic merged 2 commits into
modelcontextprotocol:mainfrom
koic:parse_json_response_bodies_in_the_client
Sep 8, 2026
Merged

Parse HTTP client response bodies without Faraday's JSON middleware#546
koic merged 2 commits into
modelcontextprotocol:mainfrom
koic:parse_json_response_bodies_in_the_client

Conversation

@koic

@koic koic commented Sep 8, 2026

Copy link
Copy Markdown
Member

Motivation and Context

json 3.0, released on 2026-09-07, accepts the options of JSON.parse as keywords only, and Faraday's json response middleware, through 2.14.3, hands them over as a positional Hash, which Ruby 3 no longer converts. With both installed, every JSON body the client receives fails inside that middleware, and MCP::Client::HTTP reports each request as an internal error. Faraday's main branch passes the options as keywords, but no release carries that yet.

The client already parses streamed bodies and buffered SSE bodies itself; only a JSON body delivered whole by an adapter without streaming support still went through the middleware. The client now parses that body too and no longer registers the middleware, so every body takes parse_json_buffer and a malformed one answers :parse_error whichever way it arrived. A JSON middleware added through the Faraday customizer block still works, since a body it has parsed already is used as is.

json 3.0 also declares Ruby 2.7 as its floor, but its load_file forwards arguments after a leading parameter, def load_file(filespec, ...), syntax Ruby 2.7.3 was the first to parse. On Ruby 2.7.0 through 2.7.2, requiring json 3.0.1 is a SyntaxError, so require "mcp" fails before any test runs, and the CI job pinned to 2.7.0, the minimum the gemspec supports, fails the whole suite at load time. The Gemfile keeps json below 3.0 on those three patch levels only. Later 2.7 releases and Ruby 3 parse the syntax and stay on whatever json the resolver picks. The line carries a FIXME to drop it once a json release includes ruby/json#1072, whichever version that turns out to be; excluding only the two broken releases would silently trust a 3.0.2 that may not carry the fix.

How Has This Been Tested?

A new test drives a buffered JSON response through a stand-in JSON.parse with the json 3.0 signature, options as keywords only, so on Ruby 3 it failed with the middleware registered whatever json version is installed and passes now. The existing buffered-body test keeps covering the plain path. The full suite passes on Ruby 3.0.7 with json 3.0.1 and faraday 2.14.3, where five client tests failed before.

gem "json", "3.0.1"; require "json" raises the SyntaxError on Ruby 2.7.0 and loads on 2.7.3 and 2.7.8. With the pin, a fresh bundle install on Ruby 2.7.2 resolves json 2.x and bundle exec rake test passes.

Breaking Changes

None. A malformed JSON body delivered by an adapter without streaming support now raises RequestHandlerError with error_type: :parse_error, as the streamed path already did, instead of :internal_error wrapping a Faraday::ParsingError. The Gemfile is not part of the gem.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

@koic
koic force-pushed the parse_json_response_bodies_in_the_client branch 2 times, most recently from 164e971 to 40c0e71 Compare September 8, 2026 09:07
koic added 2 commits September 8, 2026 18:09
## Motivation and Context

json 3.0, released on 2026-09-07, accepts the options of `JSON.parse` as keywords only, and Faraday's json response middleware,
through 2.14.3, hands them over as a positional Hash, which Ruby 3 no longer converts. With both installed, every JSON body
the client receives fails inside that middleware, and `MCP::Client::HTTP` reports each request as an internal error.
Faraday's main branch passes the options as keywords, but no release carries that yet.

The client already parses streamed bodies and buffered SSE bodies itself; only a JSON body delivered
whole by an adapter without streaming support still went through the middleware. The client now parses that body too
and no longer registers the middleware, so every body takes `parse_json_buffer` and a malformed one answers
`:parse_error` whichever way it arrived. A JSON middleware added through the Faraday customizer block still works,
since a body it has parsed already is used as is.

## How Has This Been Tested?

A new test drives a buffered JSON response through a stand-in `JSON.parse` with the json 3.0 signature, options as keywords only,
so on Ruby 3 it failed with the middleware registered whatever json version is installed and passes now.
The existing buffered-body test keeps covering the plain path. The full suite passes on Ruby 3.0.7 with json 3.0.1 and faraday 2.14.3,
where five client tests failed before.

## Breaking Changes

None. A malformed JSON body delivered by an adapter without streaming support now raises `RequestHandlerError` with `error_type: :parse_error`,
as the streamed path already did, instead of `:internal_error` wrapping a `Faraday::ParsingError`.
## Motivation and Context

json 3.0 declares Ruby 2.7 as its floor, but its `load_file` forwards arguments after a leading parameter,
`def load_file(filespec, ...)`, syntax Ruby 2.7.3 was the first to parse. On Ruby 2.7.0 through 2.7.2, requiring
json 3.0.1 is a `SyntaxError`, so `require "mcp"` fails before any test runs, and the CI job pinned to 2.7.0,
the minimum the gemspec supports, fails the whole suite at load time.

The Gemfile keeps json below 3.0 on those three patch levels only. Later 2.7 releases and Ruby 3 parse the syntax
and stay on whatever json the resolver picks.
The line carries a FIXME to drop it once a json release includes ruby/json#1072, whichever version that turns out to be;
excluding only the two broken releases would silently trust a 3.0.2 that may not carry the fix.

## How Has This Been Tested?

`gem "json", "3.0.1"; require "json"` raises the `SyntaxError` on Ruby 2.7.0 and loads on 2.7.3 and 2.7.8.
With the pin, a fresh `bundle install` on Ruby 2.7.2 resolves json 2.x and `bundle exec rake test` passes.

## Breaking Changes

None. The Gemfile is not part of the gem.
@koic
koic force-pushed the parse_json_response_bodies_in_the_client branch from 40c0e71 to d40d901 Compare September 8, 2026 09:09
@koic

koic commented Sep 8, 2026

Copy link
Copy Markdown
Member Author

I'll merge this due to the CI failure.

@koic
koic merged commit d0b22ef into modelcontextprotocol:main Sep 8, 2026
11 checks passed
@koic
koic deleted the parse_json_response_bodies_in_the_client branch September 8, 2026 09:13
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.

1 participant