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
This commit is contained in:
Koichi ITO 2026-04-09 04:52:32 +09:00
commit f7952e8aaf
3 changed files with 41 additions and 1 deletions

View file

@ -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:

View file

@ -7,9 +7,10 @@ module MCP
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 @@ module MCP
headers.each do |key, value|
faraday.headers[key] = value
end
@faraday_customizer&.call(faraday)
end
end

View file

@ -242,6 +242,31 @@ module MCP
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",