Merge pull request #546 from koic/parse_json_response_bodies_in_the_client
Parse HTTP client response bodies without Faraday's JSON middleware
This commit is contained in:
commit
d0b22ef722
3 changed files with 27 additions and 3 deletions
3
Gemfile
3
Gemfile
|
|
@ -19,6 +19,9 @@ gem "activesupport"
|
||||||
gem "debug" if RUBY_VERSION >= "3.1"
|
gem "debug" if RUBY_VERSION >= "3.1"
|
||||||
# Avoid i18n 1.15.0, which breaks on Ruby 3.1 (ruby-i18n/i18n#735).
|
# Avoid i18n 1.15.0, which breaks on Ruby 3.1 (ruby-i18n/i18n#735).
|
||||||
gem "i18n", "!= 1.15.0"
|
gem "i18n", "!= 1.15.0"
|
||||||
|
# FIXME: Drop this once a json release includes https://github.com/ruby/json/pull/1072. json 3.0.0 and 3.0.1
|
||||||
|
# forward arguments after a leading parameter, syntax Ruby 2.7.3 was the first to parse, while allowing Ruby 2.7.0.
|
||||||
|
gem "json", "< 3" if RUBY_VERSION < "2.7.3"
|
||||||
gem "rake", "~> 13.0"
|
gem "rake", "~> 13.0"
|
||||||
gem "sorbet-static-and-runtime" if RUBY_VERSION >= "3.0"
|
gem "sorbet-static-and-runtime" if RUBY_VERSION >= "3.0"
|
||||||
gem "yard", "~> 0.9"
|
gem "yard", "~> 0.9"
|
||||||
|
|
|
||||||
|
|
@ -723,7 +723,8 @@ module MCP
|
||||||
require_faraday!
|
require_faraday!
|
||||||
@client ||= Faraday.new(url) do |faraday|
|
@client ||= Faraday.new(url) do |faraday|
|
||||||
faraday.request(:json)
|
faraday.request(:json)
|
||||||
faraday.response(:json)
|
# The client parses response bodies itself (`resolve_response_body`), so streamed and buffered responses
|
||||||
|
# take the same path; Faraday's json response middleware is deliberately left out.
|
||||||
faraday.response(:raise_error)
|
faraday.response(:raise_error)
|
||||||
|
|
||||||
faraday.headers["Accept"] = ACCEPT_HEADER
|
faraday.headers["Accept"] = ACCEPT_HEADER
|
||||||
|
|
@ -1074,8 +1075,8 @@ module MCP
|
||||||
elsif content_type&.include?("application/json")
|
elsif content_type&.include?("application/json")
|
||||||
return parse_json_buffer(stream.buffer, method, params) unless stream.buffer.empty?
|
return parse_json_buffer(stream.buffer, method, params) unless stream.buffer.empty?
|
||||||
|
|
||||||
# Adapters without `on_data` support deliver the body via `response.body`,
|
# Adapters without `on_data` support deliver the body via `response.body`; a JSON middleware
|
||||||
# already parsed by the json response middleware.
|
# added by the Faraday customizer may have parsed it already.
|
||||||
response.body.is_a?(String) ? parse_json_buffer(response.body, method, params) : response.body
|
response.body.is_a?(String) ? parse_json_buffer(response.body, method, params) : response.body
|
||||||
else
|
else
|
||||||
raise RequestHandlerError.new(
|
raise RequestHandlerError.new(
|
||||||
|
|
|
||||||
|
|
@ -940,6 +940,26 @@ module MCP
|
||||||
assert_equal({ "result" => { "tools" => [] } }, response)
|
assert_equal({ "result" => { "tools" => [] } }, response)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_send_request_parses_a_json_body_with_a_parser_taking_keyword_options_only
|
||||||
|
# json 3.0 accepts parser options as keywords only, and Faraday's json response middleware
|
||||||
|
# passes them as a positional Hash, which Ruby 3 no longer converts. The stand-in parser has
|
||||||
|
# the json 3.0 signature, so the body must reach `JSON.parse` through the client's own call.
|
||||||
|
stubs = Faraday::Adapter::Test::Stubs.new do |stub|
|
||||||
|
stub.post("/") do
|
||||||
|
[200, { "Content-Type" => "application/json" }, { result: { tools: [] } }.to_json]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
client = HTTP.new(url: url) { |faraday| faraday.adapter(:test, stubs) }
|
||||||
|
parse = JSON.method(:parse)
|
||||||
|
keyword_options_only_parse = ->(source, **options) { parse.call(source, **options) }
|
||||||
|
|
||||||
|
response = JSON.stub(:parse, keyword_options_only_parse) do
|
||||||
|
client.send_request(request: { jsonrpc: "2.0", id: "test_id", method: "tools/list" })
|
||||||
|
end
|
||||||
|
|
||||||
|
assert_equal({ "result" => { "tools" => [] } }, response)
|
||||||
|
end
|
||||||
|
|
||||||
def test_send_request_mirrors_x_mcp_header_params_into_mcp_param_headers
|
def test_send_request_mirrors_x_mcp_header_params_into_mcp_param_headers
|
||||||
# SEP-2243: on a modern connection, `tools/list` teaches the transport the `x-mcp-header`
|
# SEP-2243: on a modern connection, `tools/list` teaches the transport the `x-mcp-header`
|
||||||
# declarations, and the following `tools/call` mirrors the annotated arguments into
|
# declarations, and the following `tools/call` mirrors the annotated arguments into
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue