Per the Streamable HTTP spec, if the server returns an Mcp-Session-Id header during `initialize`, the client MUST include it on subsequent requests. Capture the session ID and protocol version from the `initialize` response and attach them automatically on subsequent POSTs. Per the Protocol Version Header section of the spec, the client MUST also include `MCP-Protocol-Version` on all subsequent requests so the server can respond based on the negotiated protocol version. Map 404 responses to a new `SessionExpiredError` (a subclass of `RequestHandlerError` for backward compatibility) and clear local session state so callers can start a fresh session with a new `initialize` request, as required by the spec. - https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#session-management - https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#protocol-version-header Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
3.7 KiB
| layout | title | nav_order |
|---|---|---|
| default | Building Clients | 4 |
Building an MCP Client
The MCP::Client class provides an interface for interacting with MCP servers.
Supported operations:
- Tool listing (
MCP::Client#tools) and invocation (MCP::Client#call_tool) - Resource listing (
MCP::Client#resources) and reading (MCP::Client#read_resources) - Resource template listing (
MCP::Client#resource_templates) - Prompt listing (
MCP::Client#prompts) and retrieval (MCP::Client#get_prompt) - Completion requests (
MCP::Client#complete)
Stdio Transport
Use MCP::Client::Stdio to interact with MCP servers running as subprocesses:
stdio_transport = MCP::Client::Stdio.new(
command: "bundle",
args: ["exec", "ruby", "path/to/server.rb"],
env: { "API_KEY" => "my_secret_key" },
read_timeout: 30
)
client = MCP::Client.new(transport: stdio_transport)
tools = client.tools
tools.each do |tool|
puts "Tool: #{tool.name} - #{tool.description}"
end
response = client.call_tool(
tool: tools.first,
arguments: { message: "Hello, world!" }
)
stdio_transport.close
| Parameter | Required | Description |
|---|---|---|
command: |
Yes | The command to spawn the server process. |
args: |
No | An array of arguments passed to the command. Defaults to []. |
env: |
No | A hash of environment variables for the server process. Defaults to nil. |
read_timeout: |
No | Timeout in seconds for waiting for a server response. Defaults to nil. |
HTTP Transport
Use MCP::Client::HTTP to interact with MCP servers over HTTP. Requires the faraday gem, plus event_stream_parser if the server uses SSE (text/event-stream) responses:
gem 'mcp'
gem 'faraday', '>= 2.0'
gem 'event_stream_parser', '>= 1.0' # optional, required only for SSE responses
http_transport = MCP::Client::HTTP.new(url: "https://api.example.com/mcp")
client = MCP::Client.new(transport: http_transport)
tools = client.tools
tools.each do |tool|
puts "Tool: #{tool.name} - #{tool.description}"
end
response = client.call_tool(
tool: tools.first,
arguments: { message: "Hello, world!" }
)
Sessions
After a successful initialize request, the transport captures the Mcp-Session-Id header and protocolVersion from the response and includes the session ID on subsequent requests. Both are exposed on the transport:
http_transport.session_id # => "abc123..."
http_transport.protocol_version # => "2025-11-25"
If the server terminates the session, subsequent requests return HTTP 404 and the transport raises MCP::Client::SessionExpiredError (a subclass of RequestHandlerError). Session state is cleared automatically; callers should start a new session by sending a fresh initialize request.
Authorization
Provide custom headers for authentication:
http_transport = MCP::Client::HTTP.new(
url: "https://api.example.com/mcp",
headers: {
"Authorization" => "Bearer my_token"
}
)
client = MCP::Client.new(transport: http_transport)
Customizing the Faraday Connection
Pass a block to customize the underlying Faraday connection:
http_transport = MCP::Client::HTTP.new(url: "https://api.example.com/mcp") do |faraday|
faraday.use MyApp::Middleware::HttpRecorder
faraday.adapter :typhoeus
end
Custom Transport
If the built-in transports do not fit your needs, you can implement your own:
class CustomTransport
def send_request(request:)
# Your transport-specific logic here.
# Returns a Hash modeling a JSON-RPC response object.
end
end
client = MCP::Client.new(transport: CustomTransport.new)
For more details, see the full README.