From f7952e8aafecbc47aca3cfea923948576e35ce54 Mon Sep 17 00:00:00 2001 From: Koichi ITO Date: Thu, 9 Apr 2026 04:52:32 +0900 Subject: [PATCH] Support customizing the Faraday client in `MCP::Client::HTTP` ## Motivation and Context `MCP::Client::HTTP` builds its Faraday connection internally in a private `client` method with no way to customize the middleware stack or adapter. Users who need observability (request/response recording, failure logging) or a different HTTP adapter must override a private method, coupling to an internal API. This accepts an optional block in `MCP::Client::HTTP.new` that yields the Faraday builder after default middleware is configured, allowing users to add custom middleware or swap the HTTP adapter. ## How Has This Been Tested? Added a test that verifies custom headers set via the block are included in requests. All existing tests continue to pass. ## Breaking Changes None. The block argument is optional, so existing code is unaffected. Resolves #303 --- README.md | 12 ++++++++++++ lib/mcp/client/http.rb | 5 ++++- test/mcp/client/http_test.rb | 25 +++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index dba8f942..741d707e 100644 --- a/README.md +++ b/README.md @@ -1405,6 +1405,18 @@ client.tools # will make the call using Bearer auth You can add any custom headers needed for your authentication scheme, or for any other purpose. The client will include these headers on every request. +#### Customizing the Faraday Connection + +You can pass a block to `MCP::Client::HTTP.new` to customize the underlying Faraday connection. +The block is called after the default middleware is configured, so you can add middleware or swap the HTTP adapter: + +```ruby +http_transport = MCP::Client::HTTP.new(url: "https://api.example.com/mcp") do |faraday| + faraday.use MyApp::Middleware::HttpRecorder + faraday.adapter :typhoeus +end +``` + ### Tool Objects The client provides a wrapper class for tools returned by the server: diff --git a/lib/mcp/client/http.rb b/lib/mcp/client/http.rb index 24c7831a..1637b0ee 100644 --- a/lib/mcp/client/http.rb +++ b/lib/mcp/client/http.rb @@ -7,9 +7,10 @@ class HTTP attr_reader :url - def initialize(url:, headers: {}) + def initialize(url:, headers: {}, &block) @url = url @headers = headers + @faraday_customizer = block end def send_request(request:) @@ -78,6 +79,8 @@ def client headers.each do |key, value| faraday.headers[key] = value end + + @faraday_customizer&.call(faraday) end end diff --git a/test/mcp/client/http_test.rb b/test/mcp/client/http_test.rb index fffda7f6..081f0e6a 100644 --- a/test/mcp/client/http_test.rb +++ b/test/mcp/client/http_test.rb @@ -242,6 +242,31 @@ def test_send_request_raises_internal_error assert_equal({ method: "tools/list", params: nil }, error.request) end + def test_block_customizes_faraday_connection + custom_client = HTTP.new(url: url) do |faraday| + faraday.headers["X-Custom"] = "test-value" + end + + request = { + jsonrpc: "2.0", + id: "test_id", + method: "tools/list", + } + + stub_request(:post, url).with( + headers: { + "X-Custom" => "test-value", + "Accept" => "application/json, text/event-stream", + }, + ).to_return( + status: 200, + headers: { "Content-Type" => "application/json" }, + body: { result: { tools: [] } }.to_json, + ) + + custom_client.send_request(request: request) + end + def test_send_request_raises_error_for_non_json_response request = { jsonrpc: "2.0",