This is a Ruby implementation of MCP (Model Context Protocol) client
This gem provides a Ruby client for the Model Context Protocol (MCP), enabling integration with external tools and services via a standardized protocol.
Add this line to your application's Gemfile:
gem 'ruby-mcp-client'
And then execute:
bundle install
Or install it yourself as:
gem install ruby-mcp-client
MCP enables AI assistants and other services to discover and invoke external tools via different transport mechanisms:
The core client resides in MCPClient::Client
and provides helper methods for integrating
with popular AI services with built-in conversions:
to_openai_tools()
- Formats tools for OpenAI APIto_anthropic_tools()
- Formats tools for Anthropic Claude APIto_google_tools()
- Formats tools for Google Vertex AI API (automatically removes "$schema" keys not accepted by Vertex AI)This Ruby MCP Client implements key features from the latest MCP specification (Protocol Revision: 2025-03-26):
require 'mcp_client'
client = MCPClient.create_client(
mcp_server_configs: [
# Local stdio server
MCPClient.stdio_config(
command: 'npx -y @modelcontextprotocol/server-filesystem /home/user',
name: 'filesystem' # Optional name for this server
),
# Remote HTTP SSE server (with streaming support)
MCPClient.sse_config(
base_url: 'https://api.example.com/sse',
headers: { 'Authorization' => 'Bearer YOUR_TOKEN' },
name: 'sse_api', # Optional name for this server
read_timeout: 30, # Optional timeout in seconds (default: 30)
ping: 10, # Optional ping interval in seconds of inactivity (default: 10)
# Connection closes automatically after inactivity (2.5x ping interval)
retries: 3, # Optional number of retry attempts (default: 0)
retry_backoff: 1, # Optional backoff delay in seconds (default: 1)
# Native support for tool streaming via call_tool_streaming method
logger: Logger.new($stdout, level: Logger::INFO) # Optional logger for this server
),
# Remote HTTP server (request/response without streaming)
MCPClient.http_config(
base_url: 'https://api.example.com',
endpoint: '/rpc', # Optional JSON-RPC endpoint path (default: '/rpc')
headers: { 'Authorization' => 'Bearer YOUR_TOKEN' },
name: 'http_api', # Optional name for this server
read_timeout: 30, # Optional timeout in seconds (default: 30)
retries: 3, # Optional number of retry attempts (default: 3)
retry_backoff: 1, # Optional backoff delay in seconds (default: 1)
logger: Logger.new($stdout, level: Logger::INFO) # Optional logger for this server
)
],
# Optional logger for the client and all servers without explicit loggers
logger: Logger.new($stdout, level: Logger::WARN)
)
# Or load server definitions from a JSON file
client = MCPClient.create_client(
server_definition_file: 'path/to/server_definition.json',
logger: Logger.new($stdout, level: Logger::WARN) # Optional logger for client and servers
)
# MCP server configuration JSON format can be:
# 1. A single server object:
# { "type": "sse", "url": "http://example.com/sse" }
# { "type": "http", "url": "http://example.com", "endpoint": "/rpc" }
# 2. An array of server objects:
# [{ "type": "stdio", "command": "npx server" }, { "type": "sse", "url": "http://..." }, { "type": "http", "url": "http://..." }]
# 3. An object with "mcpServers" key containing named servers:
# { "mcpServers": { "server1": { "type": "sse", "url": "http://..." }, "server2": { "type": "http", "url": "http://..." } } }
# Note: When using this format, server1/server2 will be accessible by name
# List available tools
tools = client.list_tools
# Find a server by name
filesystem_server = client.find_server('filesystem')
# Find tools by name pattern (string or regex)
file_tools = client.find_tools('file')
first_tool = client.find_tool(/^file_/)
# Call a specific tool by name
result = client.call_tool('example_tool', { param1: 'value1', param2: 42 })
# Call a tool on a specific server by name
result = client.call_tool('example_tool', { param1: 'value1' }, server: 'filesystem')
# You can also call a tool on a server directly
result = filesystem_server.call_tool('example_tool', { param1: 'value1' })
# Call multiple tools in batch
results = client.call_tools([
{ name: 'tool1', parameters: { key1: 'value1' } },
{ name: 'tool2', parameters: { key2: 'value2' }, server: 'filesystem' } # Specify server for a specific tool
])
# Stream results (supported by the SSE transport)
# Returns an Enumerator that yields results as they become available
client.call_tool_streaming('streaming_tool', { param: 'value' }, server: 'api').each do |chunk|
# Process each chunk as it arrives
puts chunk
end
# Format tools for specific AI services
openai_tools = client.to_openai_tools
anthropic_tools = client.to_anthropic_tools
google_tools = client.to_google_tools
# Register for server notifications
client.on_notification do |server, method, params|
puts "Server notification: #{server.class}[#{server.name}] - #{method} - #{params}"
# Handle specific notifications based on method name
# 'notifications/tools/list_changed' is handled automatically by the client
end
# Send custom JSON-RPC requests or notifications
client.send_rpc('custom_method', params: { key: 'value' }, server: :sse) # Uses specific server by type
client.send_rpc('custom_method', params: { key: 'value' }, server: 'filesystem') # Uses specific server by name
result = client.send_rpc('another_method', params: { data: 123 }) # Uses first available server
client.send_notification('status_update', params: { status: 'ready' })
# Check server connectivity
client.ping # Basic connectivity check (zero-parameter heartbeat call)
client.ping(server_index: 1) # Ping a specific server by index
# Clear cached tools to force fresh fetch on next list
client.clear_cache
# Clean up connections
client.cleanup
The HTTP transport provides simple request/response communication with MCP servers:
require 'mcp_client'
require 'logger'
# Optional logger for debugging
logger = Logger.new($stdout)
logger.level = Logger::INFO
# Create an MCP client that connects to an HTTP MCP server
http_client = MCPClient.create_client(
mcp_server_configs: [
MCPClient.http_config(
base_url: 'https://api.example.com',
endpoint: '/mcp', # JSON-RPC endpoint path
headers: {
'Authorization' => 'Bearer YOUR_API_TOKEN',
'X-Custom-Header' => 'custom-value'
},
read_timeout: 30, # Timeout in seconds for HTTP requests
retries: 3, # Number of retry attempts on transient errors
retry_backoff: 1, # Base delay in seconds for exponential backoff
logger: logger # Optional logger for debugging HTTP requests
)
]
)
# List available tools
tools = http_client.list_tools
# Call a tool
result = http_client.call_tool('analyze_data', {
dataset: 'sales_2024',
metrics: ['revenue', 'conversion_rate']
})
# HTTP transport also supports streaming (though implemented as single response)
# This provides API compatibility with SSE transport
http_client.call_tool_streaming('process_batch', { batch_id: 123 }).each do |result|
puts "Processing result: #{result}"
end
# Send custom JSON-RPC requests
custom_result = http_client.send_rpc('custom_method', params: { key: 'value' })
# Send notifications (fire-and-forget)
http_client.send_notification('status_update', params: { status: 'processing' })
# Test connectivity
ping_result = http_client.ping
puts "Server is responsive: #{ping_result.inspect}"
# Clean up
http_client.cleanup
The Streamable HTTP transport is designed for servers that use HTTP POST requests but return Server-Sent Event formatted responses. This is commonly used by services like Zapier's MCP implementation:
require 'mcp_client'
require 'logger'
# Optional logger for debugging
logger = Logger.new($stdout)
logger.level = Logger::INFO
# Create an MCP client that connects to a Streamable HTTP MCP server
streamable_client = MCPClient.create_client(
mcp_server_configs: [
MCPClient.streamable_http_config(
base_url: 'https://mcp.zapier.com/api/mcp/s/YOUR_SESSION_ID/mcp',
headers: {
'Authorization' => 'Bearer YOUR_ZAPIER_TOKEN'
},
read_timeout: 60, # Timeout in seconds for HTTP requests
retries: 3, # Number of retry attempts on transient errors
retry_backoff: 2, # Base delay in seconds for exponential backoff
logger: logger # Optional logger for debugging requests
)
]
)
# List available tools (server responds with SSE-formatted JSON)
tools = streamable_client.list_tools
puts "Found #{tools.size} tools:"
tools.each { |tool| puts "- #{tool.name}: #{tool.description}" }
# Call a tool (response will be in SSE format)
result = streamable_client.call_tool('google_calendar_find_event', {
instructions: 'Find today\'s meetings',
calendarid: 'primary'
})
# The client automatically parses SSE responses like:
# event: message
# data: {"jsonrpc":"2.0","id":1,"result":{"content":[...]}}
puts "Tool result: #{result.inspect}"
# Clean up
streamable_client.cleanup
The SSE transport provides robust connection handling for remote MCP servers:
require 'mcp_client'
require 'logger'
# Optional logger for debugging
logger = Logger.new($stdout)
logger.level = Logger::INFO
# Create an MCP client that connects to a Playwright MCP server via SSE
# First run: npx @playwright/mcp@latest --port 8931
sse_client = MCPClient.create_client(
mcp_server_configs: [
MCPClient.sse_config(
base_url: 'http://localhost:8931/sse',
read_timeout: 30, # Timeout in seconds for request fulfillment
ping: 10, # Send ping after 10 seconds of inactivity
# Connection closes automatically after inactivity (2.5x ping interval)
retries: 2, # Number of retry attempts on transient errors
logger: logger # Optional logger for debugging connection issues
)
]
)
# List available tools
tools = sse_client.list_tools
# Launch a browser
result = sse_client.call_tool('browser_install', {})
result = sse_client.call_tool('browser_navigate', { url: 'about:blank' })
# No browser ID needed with these tool names
# Create a new page
page_result = sse_client.call_tool('browser_tab_new', {})
# No page ID needed with these tool names
# Navigate to a website
sse_client.call_tool('browser_navigate', { url: 'https://example.com' })
# Get page title
title_result = sse_client.call_tool('browser_snapshot', {})
puts "Page snapshot: #{title_result}"
# Take a screenshot
screenshot_result = sse_client.call_tool('browser_take_screenshot', {})
# Ping the server to verify connectivity
ping_result = sse_client.ping
puts "Ping successful: #{ping_result.inspect}"
# Clean up
sse_client.cleanup
See examples/mcp_sse_server_example.rb
for the full Playwright SSE example.
The repository includes examples for integrating with popular AI APIs:
Ruby-MCP-Client works with both official and community OpenAI gems:
# Using the openai/openai-ruby gem (official)
require 'mcp_client'
require 'openai'
# Create MCP client
mcp_client = MCPClient.create_client(
mcp_server_configs: [
MCPClient.stdio_config(
command: %W[npx -y @modelcontextprotocol/server-filesystem #{Dir.pwd}]
)
]
)
# Convert tools to OpenAI format
tools = mcp_client.to_openai_tools
# Use with OpenAI client
client = OpenAI::Client.new(api_key: ENV['OPENAI_API_KEY'])
response = client.chat.completions.create(
model: 'gpt-4',
messages: [
{ role: 'user', content: 'List files in current directory' }
],
tools: tools
)
# Process tool calls and results
# See examples directory for complete implementation
# Using the alexrudall/ruby-openai gem (community)
require 'mcp_client'
require 'openai'
# Create MCP client
mcp_client = MCPClient.create_client(
mcp_server_configs: [
MCPClient.stdio_config(command: 'npx @playwright/mcp@latest')
]
)
# Convert tools to OpenAI format
tools = mcp_client.to_openai_tools
# Use with Ruby-OpenAI client
client = OpenAI::Client.new(access_token: ENV['OPENAI_API_KEY'])
# See examples directory for complete implementation
require 'mcp_client'
require 'anthropic'
# Create MCP client
mcp_client = MCPClient.create_client(
mcp_server_configs: [
MCPClient.stdio_config(
command: %W[npx -y @modelcontextprotocol/server-filesystem #{Dir.pwd}]
)
]
)
# Convert tools to Anthropic format
claude_tools = mcp_client.to_anthropic_tools
# Use with Anthropic client
client = Anthropic::Client.new(access_token: ENV['ANTHROPIC_API_KEY'])
# See examples directory for complete implementation
Complete examples can be found in the examples/
directory:
ruby_openai_mcp.rb
- Integration with alexrudall/ruby-openai gemopenai_ruby_mcp.rb
- Integration with official openai/openai-ruby gemruby_anthropic_mcp.rb
- Integration with alexrudall/ruby-anthropic gemgemini_ai_mcp.rb
- Integration with Google Vertex AI and Gemini modelsmcp_sse_server_example.rb
- SSE transport with Playwright MCPThis client works with any MCP-compatible server, including:
You can define MCP server configurations in JSON files for easier management:
{
"mcpServers": {
"playwright": {
"type": "sse",
"url": "http://localhost:8931/sse",
"headers": {
"Authorization": "Bearer TOKEN"
}
},
"api_server": {
"type": "http",
"url": "https://api.example.com",
"endpoint": "/mcp",
"headers": {
"Authorization": "Bearer API_TOKEN",
"X-Custom-Header": "value"
}
},
"filesystem": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/dir"],
"env": {
"DEBUG": "true"
}
}
}
}
A simpler example used in the Playwright demo (found in examples/sample_server_definition.json
):
{
"mcpServers": {
"playwright": {
"url": "http://localhost:8931/sse",
"headers": {},
"comment": "Local Playwright MCP Server running on port 8931"
}
}
}
Load this configuration with:
client = MCPClient.create_client(server_definition_file: 'path/to/definition.json')
The JSON format supports:
{ "type": "sse", "url": "..." }
or { "type": "http", "url": "..." }
[{ "type": "stdio", ... }, { "type": "sse", ... }, { "type": "http", ... }]
mcpServers
key (as shown above)Special configuration options:
comment
and description
are reserved keys that are ignored during parsing and can be used for documentationcommand
(for stdio) or url
(for SSE/HTTP)endpoint
specifies the JSON-RPC endpoint path (defaults to '/rpc' if not specified)args
) are automatically converted to stringsBoth HTTP and Streamable HTTP transports now support session-based MCP servers that require session continuity:
initialize
response headersMcp-Session-Id
header in subsequent requestsThe Streamable HTTP transport provides additional resumability features for reliable message delivery:
Last-Event-ID
header in requests for resuming from disconnection pointsBoth transports implement security best practices:
The session support is transparent to the user - no additional configuration is required. The client will automatically detect and handle session-based servers by:
Mcp-Session-Id
header from the initialize
responseinitialize
)Last-Event-ID
for message replayExample of automatic session termination:
# Session is automatically terminated when client is cleaned up
client = MCPClient.create_client(
mcp_server_configs: [
MCPClient.http_config(base_url: 'https://api.example.com/mcp')
]
)
# Use the client...
tools = client.list_tools
# Session automatically terminated with HTTP DELETE request
client.cleanup
This enables compatibility with MCP servers that maintain state between requests and require session identification.
The Ruby MCP Client includes comprehensive OAuth 2.1 support for secure authentication with MCP servers:
require 'mcp_client'
# Create an OAuth-enabled HTTP server
server = MCPClient::OAuthClient.create_http_server(
server_url: 'https://api.example.com/mcp',
redirect_uri: 'http://localhost:8080/callback',
scope: 'mcp:read mcp:write'
)
# Check if authorization is needed
unless MCPClient::OAuthClient.valid_token?(server)
# Start OAuth flow
auth_url = MCPClient::OAuthClient.start_oauth_flow(server)
puts "Please visit: #{auth_url}"
# After user authorization, complete the flow
# token = MCPClient::OAuthClient.complete_oauth_flow(server, code, state)
end
# Use the server normally
server.connect
tools = server.list_tools
For more control over the OAuth flow:
# Create OAuth provider directly
oauth_provider = MCPClient::Auth::OAuthProvider.new(
server_url: 'https://api.example.com/mcp',
redirect_uri: 'http://localhost:8080/callback',
scope: 'mcp:read mcp:write'
)
# Update configuration at runtime
oauth_provider.scope = 'mcp:read mcp:write admin'
oauth_provider.redirect_uri = 'http://localhost:9000/callback'
# Start authorization flow
auth_url = oauth_provider.start_authorization_flow
# Complete flow after user authorization
token = oauth_provider.complete_authorization_flow(code, state)
.well-known
endpointsFor complete OAuth documentation, see OAUTH.md.
find_server
The SSE client implementation provides these key features:
call_tool_streaming
method, which returns an Enumerator for processing results as they arrivesend_rpc
and send_notification
ping
method to test server connectivity and healthThe HTTP transport provides a simpler, stateless communication mechanism for MCP servers:
application/json
responses (no SSE support)Mcp-Session-Id
) capture and injection for session-based MCP serverscall_tool_streaming
method for API compatibility (returns single response)The Streamable HTTP transport bridges HTTP and Server-Sent Events, designed for servers that use HTTP POST but return SSE-formatted responses:
event:
and data:
lines from SSE responsesMcp-Session-Id
) capture and injection for session-based MCP serversLast-Event-ID
header support for message replay after disconnectionsAccept: text/event-stream, application/json
, Cache-Control: no-cache
)To implement a compatible MCP server you must:
list_tools
requests with a JSON list of toolscall_tool
requests by executing the specified toolThe client supports JSON-RPC notifications from the server:
notifications/tools/list_changed
to automatically clear the tool cacheon_notification
methodEach tool is defined by a name, description, and a JSON Schema for its parameters:
{
"name": "example_tool",
"description": "Does something useful",
"schema": {
"type": "object",
"properties": {
"param1": { "type": "string" },
"param2": { "type": "number" }
},
"required": ["param1"]
}
}
This gem is available as open source under the MIT License.
Bug reports and pull requests are welcome on GitHub at https://github.com/simonx1/ruby-mcp-client.
{ "mcpServers": { "ruby-mcp-client": { "command": "npx", "args": [ "-y", "@modelcontextprotocol/server-filesystem", "/home/user" ] } } }
Related projects feature coming soon
Will recommend related projects based on sub-categories