ruby-mcp-client 0.8.1 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +226 -893
- data/lib/mcp_client/auth/browser_oauth.rb +424 -0
- data/lib/mcp_client/auth/oauth_provider.rb +131 -19
- data/lib/mcp_client/auth.rb +1 -1
- data/lib/mcp_client/client.rb +260 -4
- data/lib/mcp_client/errors.rb +3 -0
- data/lib/mcp_client/http_transport_base.rb +7 -1
- data/lib/mcp_client/json_rpc_common.rb +7 -1
- data/lib/mcp_client/root.rb +63 -0
- data/lib/mcp_client/server_factory.rb +6 -2
- data/lib/mcp_client/server_http.rb +39 -1
- data/lib/mcp_client/server_sse/sse_parser.rb +11 -0
- data/lib/mcp_client/server_sse.rb +256 -5
- data/lib/mcp_client/server_stdio.rb +240 -1
- data/lib/mcp_client/server_streamable_http/json_rpc_transport.rb +8 -1
- data/lib/mcp_client/server_streamable_http.rb +263 -7
- data/lib/mcp_client/tool.rb +40 -4
- data/lib/mcp_client/version.rb +2 -2
- data/lib/mcp_client.rb +317 -4
- metadata +4 -2
data/README.md
CHANGED
|
@@ -1,1055 +1,388 @@
|
|
|
1
1
|
# ruby-mcp-client
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
enabling integration with external tools and services via a standardized protocol.
|
|
3
|
+
A Ruby client for the Model Context Protocol (MCP), enabling integration with external tools and services via a standardized protocol.
|
|
5
4
|
|
|
6
5
|
## Installation
|
|
7
6
|
|
|
8
|
-
Add this line to your application's Gemfile:
|
|
9
|
-
|
|
10
7
|
```ruby
|
|
8
|
+
# Gemfile
|
|
11
9
|
gem 'ruby-mcp-client'
|
|
12
10
|
```
|
|
13
11
|
|
|
14
|
-
And then execute:
|
|
15
|
-
|
|
16
12
|
```bash
|
|
17
13
|
bundle install
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
Or install it yourself as:
|
|
21
|
-
|
|
22
|
-
```bash
|
|
14
|
+
# or
|
|
23
15
|
gem install ruby-mcp-client
|
|
24
16
|
```
|
|
25
17
|
|
|
26
18
|
## Overview
|
|
27
19
|
|
|
28
|
-
MCP enables AI assistants
|
|
29
|
-
via different transport mechanisms:
|
|
30
|
-
|
|
31
|
-
- **Standard I/O**: Local processes implementing the MCP protocol
|
|
32
|
-
- **Server-Sent Events (SSE)**: Remote MCP servers over HTTP with streaming support
|
|
33
|
-
- **HTTP**: Remote MCP servers over HTTP request/response (non-streaming Streamable HTTP)
|
|
34
|
-
- **Streamable HTTP**: Remote MCP servers that use HTTP POST with Server-Sent Event formatted responses
|
|
20
|
+
MCP enables AI assistants to discover and invoke external tools via different transport mechanisms:
|
|
35
21
|
|
|
36
|
-
|
|
37
|
-
|
|
22
|
+
- **stdio** - Local processes implementing the MCP protocol
|
|
23
|
+
- **SSE** - Server-Sent Events with streaming support
|
|
24
|
+
- **HTTP** - Simple request/response (non-streaming)
|
|
25
|
+
- **Streamable HTTP** - HTTP POST with SSE-formatted responses
|
|
38
26
|
|
|
39
|
-
- `to_openai_tools()`
|
|
40
|
-
- `to_anthropic_tools()` - Formats tools for Anthropic Claude API
|
|
41
|
-
- `to_google_tools()` - Formats tools for Google Vertex AI API (automatically removes "$schema" keys not accepted by Vertex AI)
|
|
27
|
+
Built-in API conversions: `to_openai_tools()`, `to_anthropic_tools()`, `to_google_tools()`
|
|
42
28
|
|
|
43
|
-
## MCP
|
|
29
|
+
## MCP Protocol Support
|
|
44
30
|
|
|
45
|
-
|
|
31
|
+
Implements **MCP 2025-06-18** specification:
|
|
46
32
|
|
|
47
|
-
|
|
48
|
-
- **
|
|
49
|
-
- **
|
|
50
|
-
- **
|
|
51
|
-
- **
|
|
52
|
-
- **
|
|
53
|
-
- **
|
|
33
|
+
- **Tools**: list, call, streaming, annotations, structured outputs
|
|
34
|
+
- **Prompts**: list, get with parameters
|
|
35
|
+
- **Resources**: list, read, templates, subscriptions, pagination
|
|
36
|
+
- **Elicitation**: Server-initiated user interactions (stdio, SSE, Streamable HTTP)
|
|
37
|
+
- **Roots**: Filesystem scope boundaries with change notifications
|
|
38
|
+
- **Sampling**: Server-requested LLM completions
|
|
39
|
+
- **Completion**: Autocomplete for prompts/resources
|
|
40
|
+
- **Logging**: Server log messages with level filtering
|
|
41
|
+
- **OAuth 2.1**: PKCE, server discovery, dynamic registration
|
|
54
42
|
|
|
55
|
-
##
|
|
43
|
+
## Quick Connect API (Recommended)
|
|
56
44
|
|
|
57
|
-
|
|
45
|
+
The simplest way to connect to an MCP server:
|
|
58
46
|
|
|
59
47
|
```ruby
|
|
60
48
|
require 'mcp_client'
|
|
61
49
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
name: 'sse_api', # Optional name for this server
|
|
74
|
-
read_timeout: 30, # Optional timeout in seconds (default: 30)
|
|
75
|
-
ping: 10, # Optional ping interval in seconds of inactivity (default: 10)
|
|
76
|
-
# Connection closes automatically after inactivity (2.5x ping interval)
|
|
77
|
-
retries: 3, # Optional number of retry attempts (default: 0)
|
|
78
|
-
retry_backoff: 1, # Optional backoff delay in seconds (default: 1)
|
|
79
|
-
# Native support for tool streaming via call_tool_streaming method
|
|
80
|
-
logger: Logger.new($stdout, level: Logger::INFO) # Optional logger for this server
|
|
81
|
-
),
|
|
82
|
-
# Remote HTTP server (request/response without streaming)
|
|
83
|
-
MCPClient.http_config(
|
|
84
|
-
base_url: 'https://api.example.com',
|
|
85
|
-
endpoint: '/rpc', # Optional JSON-RPC endpoint path (default: '/rpc')
|
|
86
|
-
headers: { 'Authorization' => 'Bearer YOUR_TOKEN' },
|
|
87
|
-
name: 'http_api', # Optional name for this server
|
|
88
|
-
read_timeout: 30, # Optional timeout in seconds (default: 30)
|
|
89
|
-
retries: 3, # Optional number of retry attempts (default: 3)
|
|
90
|
-
retry_backoff: 1, # Optional backoff delay in seconds (default: 1)
|
|
91
|
-
logger: Logger.new($stdout, level: Logger::INFO) # Optional logger for this server
|
|
92
|
-
),
|
|
93
|
-
# Streamable HTTP server (HTTP POST with SSE responses and session management)
|
|
94
|
-
MCPClient.streamable_http_config(
|
|
95
|
-
base_url: 'https://api.example.com/mcp',
|
|
96
|
-
endpoint: '/rpc', # Optional JSON-RPC endpoint path (default: '/rpc')
|
|
97
|
-
headers: { 'Authorization' => 'Bearer YOUR_TOKEN' },
|
|
98
|
-
name: 'streamable_api', # Optional name for this server
|
|
99
|
-
read_timeout: 60, # Optional timeout in seconds (default: 30)
|
|
100
|
-
retries: 3, # Optional number of retry attempts (default: 3)
|
|
101
|
-
retry_backoff: 1, # Optional backoff delay in seconds (default: 1)
|
|
102
|
-
logger: Logger.new($stdout, level: Logger::INFO) # Optional logger for this server
|
|
103
|
-
)
|
|
104
|
-
],
|
|
105
|
-
# Optional logger for the client and all servers without explicit loggers
|
|
106
|
-
logger: Logger.new($stdout, level: Logger::WARN)
|
|
50
|
+
# Auto-detect transport from URL
|
|
51
|
+
client = MCPClient.connect('http://localhost:8000/sse') # SSE
|
|
52
|
+
client = MCPClient.connect('http://localhost:8931/mcp') # Streamable HTTP
|
|
53
|
+
client = MCPClient.connect('npx -y @modelcontextprotocol/server-filesystem /home') # stdio
|
|
54
|
+
|
|
55
|
+
# With options
|
|
56
|
+
client = MCPClient.connect('http://api.example.com/mcp',
|
|
57
|
+
headers: { 'Authorization' => 'Bearer TOKEN' },
|
|
58
|
+
read_timeout: 60,
|
|
59
|
+
retries: 3,
|
|
60
|
+
logger: Logger.new($stdout)
|
|
107
61
|
)
|
|
108
62
|
|
|
109
|
-
#
|
|
110
|
-
client = MCPClient.
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
)
|
|
63
|
+
# Multiple servers
|
|
64
|
+
client = MCPClient.connect(['http://server1/mcp', 'http://server2/sse'])
|
|
65
|
+
|
|
66
|
+
# Force specific transport
|
|
67
|
+
client = MCPClient.connect('http://custom.com/api', transport: :streamable_http)
|
|
114
68
|
|
|
115
|
-
#
|
|
116
|
-
# 1. A single server object:
|
|
117
|
-
# { "type": "sse", "url": "http://example.com/sse" }
|
|
118
|
-
# { "type": "http", "url": "http://example.com", "endpoint": "/rpc" }
|
|
119
|
-
# { "type": "streamable_http", "url": "http://example.com/mcp", "endpoint": "/rpc" }
|
|
120
|
-
# 2. An array of server objects:
|
|
121
|
-
# [{ "type": "stdio", "command": "npx server" }, { "type": "sse", "url": "http://..." }, { "type": "streamable_http", "url": "http://..." }]
|
|
122
|
-
# 3. An object with "mcpServers" key containing named servers:
|
|
123
|
-
# { "mcpServers": { "server1": { "type": "sse", "url": "http://..." }, "server2": { "type": "streamable_http", "url": "http://..." } } }
|
|
124
|
-
# Note: When using this format, server1/server2 will be accessible by name
|
|
125
|
-
|
|
126
|
-
# List available tools
|
|
69
|
+
# Use the client
|
|
127
70
|
tools = client.list_tools
|
|
71
|
+
result = client.call_tool('example_tool', { param: 'value' })
|
|
72
|
+
client.cleanup
|
|
73
|
+
```
|
|
128
74
|
|
|
129
|
-
|
|
130
|
-
filesystem_server = client.find_server('filesystem')
|
|
75
|
+
**Transport Detection:**
|
|
131
76
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
77
|
+
| URL Pattern | Transport |
|
|
78
|
+
|-------------|-----------|
|
|
79
|
+
| Ends with `/sse` | SSE |
|
|
80
|
+
| Ends with `/mcp` | Streamable HTTP |
|
|
81
|
+
| `stdio://command` or Array | stdio |
|
|
82
|
+
| `npx`, `node`, `python`, etc. | stdio |
|
|
83
|
+
| Other HTTP URLs | Auto-detect (Streamable HTTP → SSE → HTTP) |
|
|
135
84
|
|
|
136
|
-
|
|
137
|
-
result = client.call_tool('example_tool', { param1: 'value1', param2: 42 })
|
|
85
|
+
## Working with Tools, Prompts & Resources
|
|
138
86
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
result =
|
|
87
|
+
```ruby
|
|
88
|
+
# Tools
|
|
89
|
+
tools = client.list_tools
|
|
90
|
+
result = client.call_tool('tool_name', { param: 'value' })
|
|
91
|
+
result = client.call_tool('tool_name', { param: 'value' }, server: 'server_name')
|
|
143
92
|
|
|
144
|
-
#
|
|
93
|
+
# Batch tool calls
|
|
145
94
|
results = client.call_tools([
|
|
146
|
-
{ name: 'tool1', parameters: {
|
|
147
|
-
{ name: 'tool2', parameters: {
|
|
95
|
+
{ name: 'tool1', parameters: { key: 'value' } },
|
|
96
|
+
{ name: 'tool2', parameters: { key: 'value' }, server: 'specific_server' }
|
|
148
97
|
])
|
|
149
98
|
|
|
150
|
-
#
|
|
151
|
-
|
|
152
|
-
client.call_tool_streaming('streaming_tool', { param: 'value' }, server: 'api').each do |chunk|
|
|
153
|
-
# Process each chunk as it arrives
|
|
99
|
+
# Streaming (SSE/Streamable HTTP)
|
|
100
|
+
client.call_tool_streaming('tool', { param: 'value' }).each do |chunk|
|
|
154
101
|
puts chunk
|
|
155
102
|
end
|
|
156
103
|
|
|
157
|
-
#
|
|
158
|
-
# List available prompts from all servers
|
|
104
|
+
# Prompts
|
|
159
105
|
prompts = client.list_prompts
|
|
106
|
+
result = client.get_prompt('greeting', { name: 'Alice' })
|
|
160
107
|
|
|
161
|
-
#
|
|
162
|
-
result = client.get_prompt('greeting', { name: 'Ruby Developer' })
|
|
163
|
-
|
|
164
|
-
# Get a prompt from a specific server
|
|
165
|
-
result = client.get_prompt('greeting', { name: 'Ruby Developer' }, server: 'filesystem')
|
|
166
|
-
|
|
167
|
-
# === Working with Resources ===
|
|
168
|
-
# List available resources from all servers (returns hash with 'resources' array and optional 'nextCursor')
|
|
108
|
+
# Resources
|
|
169
109
|
result = client.list_resources
|
|
170
|
-
resources = result['resources'] # Array of Resource objects from all servers
|
|
171
|
-
next_cursor = result['nextCursor'] # nil when aggregating multiple servers
|
|
172
|
-
|
|
173
|
-
# Get resources from a specific server with pagination support
|
|
174
|
-
result = client.servers.first.list_resources
|
|
175
|
-
resources = result['resources'] # Array of Resource objects
|
|
176
|
-
next_cursor = result['nextCursor'] # For pagination
|
|
177
|
-
|
|
178
|
-
# List resources with pagination (only works with single server or client.list_resources with cursor)
|
|
179
|
-
result = client.list_resources(cursor: next_cursor) # Uses first server when cursor provided
|
|
180
|
-
|
|
181
|
-
# Read a specific resource by URI (returns array of ResourceContent objects)
|
|
182
110
|
contents = client.read_resource('file:///example.txt')
|
|
183
111
|
contents.each do |content|
|
|
184
|
-
if content.text?
|
|
185
|
-
|
|
186
|
-
elsif content.binary?
|
|
187
|
-
data = Base64.decode64(content.blob) # Binary content
|
|
188
|
-
end
|
|
189
|
-
puts content.mime_type if content.mime_type
|
|
190
|
-
puts content.annotations if content.annotations # Optional metadata
|
|
191
|
-
end
|
|
192
|
-
|
|
193
|
-
# Read a resource from a specific server
|
|
194
|
-
contents = client.read_resource('file:///example.txt', server: 'filesystem')
|
|
195
|
-
|
|
196
|
-
# Format tools for specific AI services
|
|
197
|
-
openai_tools = client.to_openai_tools
|
|
198
|
-
anthropic_tools = client.to_anthropic_tools
|
|
199
|
-
google_tools = client.to_google_tools
|
|
200
|
-
|
|
201
|
-
# Register for server notifications
|
|
202
|
-
client.on_notification do |server, method, params|
|
|
203
|
-
puts "Server notification: #{server.class}[#{server.name}] - #{method} - #{params}"
|
|
204
|
-
# Handle specific notifications based on method name
|
|
205
|
-
# 'notifications/tools/list_changed' is handled automatically by the client
|
|
112
|
+
puts content.text if content.text?
|
|
113
|
+
data = Base64.decode64(content.blob) if content.binary?
|
|
206
114
|
end
|
|
207
|
-
|
|
208
|
-
# Send custom JSON-RPC requests or notifications
|
|
209
|
-
client.send_rpc('custom_method', params: { key: 'value' }, server: :sse) # Uses specific server by type
|
|
210
|
-
client.send_rpc('custom_method', params: { key: 'value' }, server: 'filesystem') # Uses specific server by name
|
|
211
|
-
result = client.send_rpc('another_method', params: { data: 123 }) # Uses first available server
|
|
212
|
-
client.send_notification('status_update', params: { status: 'ready' })
|
|
213
|
-
|
|
214
|
-
# Check server connectivity
|
|
215
|
-
client.ping # Basic connectivity check (zero-parameter heartbeat call)
|
|
216
|
-
client.ping(server_index: 1) # Ping a specific server by index
|
|
217
|
-
|
|
218
|
-
# Clear cached tools to force fresh fetch on next list
|
|
219
|
-
client.clear_cache
|
|
220
|
-
# Clean up connections
|
|
221
|
-
client.cleanup
|
|
222
115
|
```
|
|
223
116
|
|
|
224
|
-
|
|
117
|
+
## MCP 2025-06-18 Features
|
|
225
118
|
|
|
226
|
-
|
|
119
|
+
### Tool Annotations
|
|
227
120
|
|
|
228
121
|
```ruby
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
#
|
|
233
|
-
logger = Logger.new($stdout)
|
|
234
|
-
logger.level = Logger::INFO
|
|
235
|
-
|
|
236
|
-
# Create an MCP client that connects to an HTTP MCP server
|
|
237
|
-
http_client = MCPClient.create_client(
|
|
238
|
-
mcp_server_configs: [
|
|
239
|
-
MCPClient.http_config(
|
|
240
|
-
base_url: 'https://api.example.com',
|
|
241
|
-
endpoint: '/mcp', # JSON-RPC endpoint path
|
|
242
|
-
headers: {
|
|
243
|
-
'Authorization' => 'Bearer YOUR_API_TOKEN',
|
|
244
|
-
'X-Custom-Header' => 'custom-value'
|
|
245
|
-
},
|
|
246
|
-
read_timeout: 30, # Timeout in seconds for HTTP requests
|
|
247
|
-
retries: 3, # Number of retry attempts on transient errors
|
|
248
|
-
retry_backoff: 1, # Base delay in seconds for exponential backoff
|
|
249
|
-
logger: logger # Optional logger for debugging HTTP requests
|
|
250
|
-
)
|
|
251
|
-
]
|
|
252
|
-
)
|
|
253
|
-
|
|
254
|
-
# List available tools
|
|
255
|
-
tools = http_client.list_tools
|
|
256
|
-
|
|
257
|
-
# Call a tool
|
|
258
|
-
result = http_client.call_tool('analyze_data', {
|
|
259
|
-
dataset: 'sales_2024',
|
|
260
|
-
metrics: ['revenue', 'conversion_rate']
|
|
261
|
-
})
|
|
262
|
-
|
|
263
|
-
# HTTP transport also supports streaming (though implemented as single response)
|
|
264
|
-
# This provides API compatibility with SSE transport
|
|
265
|
-
http_client.call_tool_streaming('process_batch', { batch_id: 123 }).each do |result|
|
|
266
|
-
puts "Processing result: #{result}"
|
|
267
|
-
end
|
|
268
|
-
|
|
269
|
-
# Send custom JSON-RPC requests
|
|
270
|
-
custom_result = http_client.send_rpc('custom_method', params: { key: 'value' })
|
|
271
|
-
|
|
272
|
-
# Send notifications (fire-and-forget)
|
|
273
|
-
http_client.send_notification('status_update', params: { status: 'processing' })
|
|
274
|
-
|
|
275
|
-
# Test connectivity
|
|
276
|
-
ping_result = http_client.ping
|
|
277
|
-
puts "Server is responsive: #{ping_result.inspect}"
|
|
278
|
-
|
|
279
|
-
# Clean up
|
|
280
|
-
http_client.cleanup
|
|
122
|
+
tool = client.find_tool('delete_user')
|
|
123
|
+
tool.read_only? # Safe to execute?
|
|
124
|
+
tool.destructive? # Warning: destructive operation
|
|
125
|
+
tool.requires_confirmation? # Needs user confirmation
|
|
281
126
|
```
|
|
282
127
|
|
|
283
|
-
###
|
|
284
|
-
|
|
285
|
-
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:
|
|
128
|
+
### Structured Outputs
|
|
286
129
|
|
|
287
130
|
```ruby
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
# Optional logger for debugging
|
|
292
|
-
logger = Logger.new($stdout)
|
|
293
|
-
logger.level = Logger::INFO
|
|
294
|
-
|
|
295
|
-
# Create an MCP client that connects to a Streamable HTTP MCP server
|
|
296
|
-
streamable_client = MCPClient.create_client(
|
|
297
|
-
mcp_server_configs: [
|
|
298
|
-
MCPClient.streamable_http_config(
|
|
299
|
-
base_url: 'https://mcp.zapier.com/api/mcp/s/YOUR_SESSION_ID/mcp',
|
|
300
|
-
headers: {
|
|
301
|
-
'Authorization' => 'Bearer YOUR_ZAPIER_TOKEN'
|
|
302
|
-
},
|
|
303
|
-
read_timeout: 60, # Timeout in seconds for HTTP requests
|
|
304
|
-
retries: 3, # Number of retry attempts on transient errors
|
|
305
|
-
retry_backoff: 2, # Base delay in seconds for exponential backoff
|
|
306
|
-
logger: logger # Optional logger for debugging requests
|
|
307
|
-
)
|
|
308
|
-
]
|
|
309
|
-
)
|
|
310
|
-
|
|
311
|
-
# List available tools (server responds with SSE-formatted JSON)
|
|
312
|
-
tools = streamable_client.list_tools
|
|
313
|
-
puts "Found #{tools.size} tools:"
|
|
314
|
-
tools.each { |tool| puts "- #{tool.name}: #{tool.description}" }
|
|
315
|
-
|
|
316
|
-
# Call a tool (response will be in SSE format)
|
|
317
|
-
result = streamable_client.call_tool('google_calendar_find_event', {
|
|
318
|
-
instructions: 'Find today\'s meetings',
|
|
319
|
-
calendarid: 'primary'
|
|
320
|
-
})
|
|
321
|
-
|
|
322
|
-
# The client automatically parses SSE responses like:
|
|
323
|
-
# event: message
|
|
324
|
-
# data: {"jsonrpc":"2.0","id":1,"result":{"content":[...]}}
|
|
325
|
-
|
|
326
|
-
puts "Tool result: #{result.inspect}"
|
|
131
|
+
tool = client.find_tool('get_weather')
|
|
132
|
+
tool.structured_output? # Has output schema?
|
|
133
|
+
tool.output_schema # JSON Schema for output
|
|
327
134
|
|
|
328
|
-
|
|
329
|
-
|
|
135
|
+
result = client.call_tool('get_weather', { location: 'SF' })
|
|
136
|
+
data = result['structuredContent'] # Type-safe structured data
|
|
330
137
|
```
|
|
331
138
|
|
|
332
|
-
###
|
|
333
|
-
|
|
334
|
-
The SSE transport provides robust connection handling for remote MCP servers:
|
|
139
|
+
### Roots
|
|
335
140
|
|
|
336
141
|
```ruby
|
|
337
|
-
|
|
338
|
-
|
|
142
|
+
# Set filesystem scope boundaries
|
|
143
|
+
client.roots = [
|
|
144
|
+
{ uri: 'file:///home/user/project', name: 'Project' },
|
|
145
|
+
{ uri: 'file:///var/log', name: 'Logs' }
|
|
146
|
+
]
|
|
147
|
+
|
|
148
|
+
# Access current roots
|
|
149
|
+
client.roots
|
|
150
|
+
```
|
|
339
151
|
|
|
340
|
-
|
|
341
|
-
logger = Logger.new($stdout)
|
|
342
|
-
logger.level = Logger::INFO
|
|
152
|
+
### Sampling (Server-requested LLM completions)
|
|
343
153
|
|
|
344
|
-
|
|
345
|
-
#
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
]
|
|
154
|
+
```ruby
|
|
155
|
+
# Configure handler when creating client
|
|
156
|
+
client = MCPClient.connect('http://server/mcp',
|
|
157
|
+
sampling_handler: ->(messages, model_prefs, system_prompt, max_tokens) {
|
|
158
|
+
# Process server's LLM request
|
|
159
|
+
{
|
|
160
|
+
'model' => 'gpt-4',
|
|
161
|
+
'stopReason' => 'endTurn',
|
|
162
|
+
'role' => 'assistant',
|
|
163
|
+
'content' => { 'type' => 'text', 'text' => 'Response here' }
|
|
164
|
+
}
|
|
165
|
+
}
|
|
357
166
|
)
|
|
358
|
-
|
|
359
|
-
# List available tools
|
|
360
|
-
tools = mcp_client.list_tools
|
|
361
|
-
|
|
362
|
-
# Launch a browser
|
|
363
|
-
result = mcp_client.call_tool('browser_install', {})
|
|
364
|
-
result = mcp_client.call_tool('browser_navigate', { url: 'about:blank' })
|
|
365
|
-
# No browser ID needed with these tool names
|
|
366
|
-
|
|
367
|
-
# Create a new page
|
|
368
|
-
page_result = mcp_client.call_tool('browser_tab', {action: 'create'})
|
|
369
|
-
# No page ID needed with these tool names
|
|
370
|
-
|
|
371
|
-
# Navigate to a website
|
|
372
|
-
mcp_client.call_tool('browser_navigate', { url: 'https://example.com' })
|
|
373
|
-
|
|
374
|
-
# Get page title
|
|
375
|
-
title_result = mcp_client.call_tool('browser_snapshot', {})
|
|
376
|
-
puts "Page snapshot: #{title_result}"
|
|
377
|
-
|
|
378
|
-
# Take a screenshot
|
|
379
|
-
screenshot_result = mcp_client.call_tool('browser_take_screenshot', {})
|
|
380
|
-
|
|
381
|
-
# Ping the server to verify connectivity
|
|
382
|
-
ping_result = mcp_client.ping
|
|
383
|
-
puts "Ping successful: #{ping_result.inspect}"
|
|
384
|
-
|
|
385
|
-
# Clean up
|
|
386
|
-
mcp_client.cleanup
|
|
387
167
|
```
|
|
388
168
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
### FastMCP Example
|
|
392
|
-
|
|
393
|
-
The repository includes complete FastMCP server examples that demonstrate the Ruby MCP client working with Python FastMCP servers, including full MCP protocol support with tools, prompts, and resources:
|
|
394
|
-
|
|
395
|
-
#### Basic FastMCP Example
|
|
396
|
-
|
|
397
|
-
**For FastMCP server with SSE transport (includes tools, prompts, and resources):**
|
|
398
|
-
```bash
|
|
399
|
-
# From the ruby-mcp-client directory
|
|
400
|
-
python examples/echo_server.py
|
|
401
|
-
```
|
|
169
|
+
### Completion (Autocomplete)
|
|
402
170
|
|
|
403
171
|
```ruby
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
require 'mcp_client'
|
|
409
|
-
|
|
410
|
-
# Connect to FastMCP server
|
|
411
|
-
client = MCPClient.create_client(
|
|
412
|
-
mcp_server_configs: [
|
|
413
|
-
MCPClient.sse_config(
|
|
414
|
-
base_url: 'http://127.0.0.1:8000/sse',
|
|
415
|
-
read_timeout: 30
|
|
416
|
-
)
|
|
417
|
-
]
|
|
172
|
+
result = client.complete(
|
|
173
|
+
ref: { type: 'ref/prompt', name: 'greeting' },
|
|
174
|
+
argument: { name: 'name', value: 'A' }
|
|
418
175
|
)
|
|
176
|
+
# => { 'values' => ['Alice', 'Alex'], 'total' => 100, 'hasMore' => true }
|
|
177
|
+
```
|
|
419
178
|
|
|
420
|
-
|
|
421
|
-
tools = client.list_tools
|
|
422
|
-
puts "Found #{tools.length} tools:"
|
|
423
|
-
tools.each { |tool| puts "- #{tool.name}: #{tool.description}" }
|
|
424
|
-
|
|
425
|
-
result = client.call_tool('echo', { message: 'Hello FastMCP!' })
|
|
426
|
-
|
|
427
|
-
# List and use prompts
|
|
428
|
-
prompts = client.list_prompts
|
|
429
|
-
puts "Found #{prompts.length} prompts:"
|
|
430
|
-
prompts.each { |prompt| puts "- #{prompt.name}: #{prompt.description}" }
|
|
431
|
-
|
|
432
|
-
greeting = client.get_prompt('greeting', { name: 'Ruby Developer' })
|
|
179
|
+
### Logging
|
|
433
180
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
puts "Found #{resources.length} resources:"
|
|
438
|
-
resources.each { |resource| puts "- #{resource.name} (#{resource.uri})" }
|
|
181
|
+
```ruby
|
|
182
|
+
# Set log level
|
|
183
|
+
client.log_level = 'debug' # debug/info/notice/warning/error/critical
|
|
439
184
|
|
|
440
|
-
#
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
185
|
+
# Handle log notifications
|
|
186
|
+
client.on_notification do |server, method, params|
|
|
187
|
+
if method == 'notifications/message'
|
|
188
|
+
puts "[#{params['level']}] #{params['logger']}: #{params['data']}"
|
|
189
|
+
end
|
|
444
190
|
end
|
|
445
191
|
```
|
|
446
192
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
**For FastMCP server with Streamable HTTP transport (includes tools, prompts, and resources):**
|
|
450
|
-
```bash
|
|
451
|
-
# From the ruby-mcp-client directory
|
|
452
|
-
python examples/echo_server_streamable.py
|
|
453
|
-
```
|
|
193
|
+
### Elicitation (Server-initiated user interactions)
|
|
454
194
|
|
|
455
195
|
```ruby
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
client = MCPClient.create_client(
|
|
464
|
-
mcp_server_configs: [
|
|
465
|
-
MCPClient.streamable_http_config(
|
|
466
|
-
base_url: 'http://localhost:8931/mcp',
|
|
467
|
-
read_timeout: 60
|
|
468
|
-
)
|
|
469
|
-
]
|
|
196
|
+
client = MCPClient::Client.new(
|
|
197
|
+
mcp_server_configs: [MCPClient.stdio_config(command: 'python server.py')],
|
|
198
|
+
elicitation_handler: ->(message, schema) {
|
|
199
|
+
puts "Server asks: #{message}"
|
|
200
|
+
# Return: { 'action' => 'accept', 'content' => { 'field' => 'value' } }
|
|
201
|
+
# Or: { 'action' => 'decline' } or { 'action' => 'cancel' }
|
|
202
|
+
}
|
|
470
203
|
)
|
|
471
|
-
|
|
472
|
-
# Full protocol support including real-time notifications
|
|
473
|
-
client.on_notification do |method, params|
|
|
474
|
-
puts "Server notification: #{method} - #{params}"
|
|
475
|
-
end
|
|
476
|
-
|
|
477
|
-
# Use all MCP features: tools, prompts, resources
|
|
478
|
-
tools = client.list_tools
|
|
479
|
-
prompts = client.list_prompts
|
|
480
|
-
resources = client.list_resources
|
|
481
|
-
|
|
482
|
-
# Real-time progress notifications for long-running tasks
|
|
483
|
-
result = client.call_tool('long_task', { duration: 5, steps: 5 })
|
|
484
|
-
|
|
485
|
-
# Use the tools
|
|
486
|
-
result = client.call_tool('echo', { message: 'Hello from Ruby!' })
|
|
487
|
-
result = client.call_tool('reverse', { text: 'FastMCP rocks!' })
|
|
488
|
-
|
|
489
|
-
client.cleanup
|
|
490
204
|
```
|
|
491
205
|
|
|
492
|
-
|
|
493
|
-
- **`echo_server.py`** - A Python FastMCP server with tools, prompts, and resources
|
|
494
|
-
- **`echo_server_client.rb`** - Ruby client demonstrating all features including prompts and resources
|
|
495
|
-
- **`echo_server_streamable.py`** - Enhanced streamable HTTP server with tools, prompts, and resources
|
|
496
|
-
- **`echo_server_streamable_client.rb`** - Ruby client demonstrating streamable HTTP transport
|
|
497
|
-
- **`README_ECHO_SERVER.md`** - Complete setup and usage instructions
|
|
498
|
-
|
|
499
|
-
This example showcases redirect support, proper line ending handling, and seamless integration between Ruby and Python MCP implementations.
|
|
206
|
+
## Advanced Configuration
|
|
500
207
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
The repository includes examples for integrating with popular AI APIs:
|
|
504
|
-
|
|
505
|
-
#### OpenAI Integration
|
|
506
|
-
|
|
507
|
-
Ruby-MCP-Client works with both official and community OpenAI gems:
|
|
208
|
+
For more control, use `create_client` with explicit configs:
|
|
508
209
|
|
|
509
210
|
```ruby
|
|
510
|
-
|
|
511
|
-
require 'mcp_client'
|
|
512
|
-
require 'openai'
|
|
513
|
-
|
|
514
|
-
# Create MCP client
|
|
515
|
-
mcp_client = MCPClient.create_client(
|
|
211
|
+
client = MCPClient.create_client(
|
|
516
212
|
mcp_server_configs: [
|
|
517
|
-
MCPClient.stdio_config(
|
|
518
|
-
|
|
213
|
+
MCPClient.stdio_config(command: 'npx server', name: 'local'),
|
|
214
|
+
MCPClient.sse_config(
|
|
215
|
+
base_url: 'https://api.example.com/sse',
|
|
216
|
+
headers: { 'Authorization' => 'Bearer TOKEN' },
|
|
217
|
+
read_timeout: 30, ping: 10, retries: 3
|
|
218
|
+
),
|
|
219
|
+
MCPClient.http_config(
|
|
220
|
+
base_url: 'https://api.example.com',
|
|
221
|
+
endpoint: '/rpc',
|
|
222
|
+
headers: { 'Authorization' => 'Bearer TOKEN' }
|
|
223
|
+
),
|
|
224
|
+
MCPClient.streamable_http_config(
|
|
225
|
+
base_url: 'https://api.example.com/mcp',
|
|
226
|
+
read_timeout: 60, retries: 3
|
|
519
227
|
)
|
|
520
|
-
]
|
|
521
|
-
)
|
|
522
|
-
|
|
523
|
-
# Convert tools to OpenAI format
|
|
524
|
-
tools = mcp_client.to_openai_tools
|
|
525
|
-
|
|
526
|
-
# Use with OpenAI client
|
|
527
|
-
client = OpenAI::Client.new(api_key: ENV['OPENAI_API_KEY'])
|
|
528
|
-
response = client.chat.completions.create(
|
|
529
|
-
model: 'gpt-4',
|
|
530
|
-
messages: [
|
|
531
|
-
{ role: 'user', content: 'List files in current directory' }
|
|
532
228
|
],
|
|
533
|
-
|
|
229
|
+
logger: Logger.new($stdout)
|
|
534
230
|
)
|
|
535
231
|
|
|
536
|
-
#
|
|
537
|
-
|
|
232
|
+
# Or load from JSON file
|
|
233
|
+
client = MCPClient.create_client(server_definition_file: 'servers.json')
|
|
538
234
|
```
|
|
539
235
|
|
|
540
|
-
|
|
541
|
-
# Using the alexrudall/ruby-openai gem (community)
|
|
542
|
-
require 'mcp_client'
|
|
543
|
-
require 'openai'
|
|
544
|
-
|
|
545
|
-
# Create MCP client
|
|
546
|
-
mcp_client = MCPClient.create_client(
|
|
547
|
-
mcp_server_configs: [
|
|
548
|
-
MCPClient.stdio_config(command: 'npx @playwright/mcp@latest')
|
|
549
|
-
]
|
|
550
|
-
)
|
|
551
|
-
|
|
552
|
-
# Convert tools to OpenAI format
|
|
553
|
-
tools = mcp_client.to_openai_tools
|
|
554
|
-
|
|
555
|
-
# Use with Ruby-OpenAI client
|
|
556
|
-
client = OpenAI::Client.new(access_token: ENV['OPENAI_API_KEY'])
|
|
557
|
-
# See examples directory for complete implementation
|
|
558
|
-
```
|
|
559
|
-
|
|
560
|
-
#### Anthropic Integration
|
|
236
|
+
### Faraday Customization
|
|
561
237
|
|
|
562
238
|
```ruby
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
mcp_client = MCPClient.create_client(
|
|
568
|
-
mcp_server_configs: [
|
|
569
|
-
MCPClient.stdio_config(
|
|
570
|
-
command: %W[npx -y @modelcontextprotocol/server-filesystem #{Dir.pwd}]
|
|
571
|
-
)
|
|
572
|
-
]
|
|
573
|
-
)
|
|
574
|
-
|
|
575
|
-
# Convert tools to Anthropic format
|
|
576
|
-
claude_tools = mcp_client.to_anthropic_tools
|
|
577
|
-
|
|
578
|
-
# Use with Anthropic client
|
|
579
|
-
client = Anthropic::Client.new(access_token: ENV['ANTHROPIC_API_KEY'])
|
|
580
|
-
# See examples directory for complete implementation
|
|
239
|
+
MCPClient.http_config(base_url: 'https://internal.company.com') do |faraday|
|
|
240
|
+
faraday.ssl.cert_store = custom_cert_store
|
|
241
|
+
faraday.ssl.verify = true
|
|
242
|
+
end
|
|
581
243
|
```
|
|
582
244
|
|
|
583
|
-
|
|
584
|
-
- `ruby_openai_mcp.rb` - Integration with alexrudall/ruby-openai gem
|
|
585
|
-
- `openai_ruby_mcp.rb` - Integration with official openai/openai-ruby gem
|
|
586
|
-
- `ruby_anthropic_mcp.rb` - Integration with alexrudall/ruby-anthropic gem
|
|
587
|
-
- `gemini_ai_mcp.rb` - Integration with Google Vertex AI and Gemini models
|
|
588
|
-
- `streamable_http_example.rb` - Streamable HTTP transport with Playwright MCP
|
|
589
|
-
- `echo_server.py` & `echo_server_client.rb` - FastMCP server example with full setup
|
|
590
|
-
- `echo_server_streamable.py` & `echo_server_streamable_client.rb` - Enhanced streamable HTTP server example
|
|
591
|
-
|
|
592
|
-
## MCP Server Compatibility
|
|
593
|
-
|
|
594
|
-
This client works with any MCP-compatible server, including:
|
|
595
|
-
|
|
596
|
-
- [@modelcontextprotocol/server-filesystem](https://www.npmjs.com/package/@modelcontextprotocol/server-filesystem) - File system access
|
|
597
|
-
- [@playwright/mcp](https://www.npmjs.com/package/@playwright/mcp) - Browser automation
|
|
598
|
-
- [FastMCP](https://github.com/jlowin/fastmcp) - Python framework for building MCP servers
|
|
599
|
-
- Custom servers implementing the MCP protocol
|
|
600
|
-
|
|
601
|
-
### Server Definition Files
|
|
602
|
-
|
|
603
|
-
You can define MCP server configurations in JSON files for easier management:
|
|
245
|
+
### Server Definition JSON
|
|
604
246
|
|
|
605
247
|
```json
|
|
606
248
|
{
|
|
607
249
|
"mcpServers": {
|
|
608
|
-
"playwright": {
|
|
609
|
-
"type": "sse",
|
|
610
|
-
"url": "http://localhost:8931/mcp",
|
|
611
|
-
"headers": {
|
|
612
|
-
"Authorization": "Bearer TOKEN"
|
|
613
|
-
}
|
|
614
|
-
},
|
|
615
|
-
"api_server": {
|
|
616
|
-
"type": "http",
|
|
617
|
-
"url": "https://api.example.com",
|
|
618
|
-
"endpoint": "/mcp",
|
|
619
|
-
"headers": {
|
|
620
|
-
"Authorization": "Bearer API_TOKEN",
|
|
621
|
-
"X-Custom-Header": "value"
|
|
622
|
-
}
|
|
623
|
-
},
|
|
624
250
|
"filesystem": {
|
|
625
251
|
"type": "stdio",
|
|
626
252
|
"command": "npx",
|
|
627
|
-
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
253
|
+
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/home"]
|
|
254
|
+
},
|
|
255
|
+
"api": {
|
|
256
|
+
"type": "streamable_http",
|
|
257
|
+
"url": "https://api.example.com/mcp",
|
|
258
|
+
"headers": { "Authorization": "Bearer TOKEN" }
|
|
631
259
|
}
|
|
632
260
|
}
|
|
633
261
|
}
|
|
634
262
|
```
|
|
635
263
|
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
```json
|
|
639
|
-
{
|
|
640
|
-
"mcpServers": {
|
|
641
|
-
"playwright": {
|
|
642
|
-
"url": "http://localhost:8931/mcp",
|
|
643
|
-
"headers": {},
|
|
644
|
-
"comment": "Local Playwright MCP Server running on port 8931"
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
```
|
|
264
|
+
## AI Integration Examples
|
|
649
265
|
|
|
650
|
-
|
|
266
|
+
### OpenAI
|
|
651
267
|
|
|
652
268
|
```ruby
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
The JSON format supports:
|
|
657
|
-
1. A single server object: `{ "type": "sse", "url": "..." }` or `{ "type": "http", "url": "..." }`
|
|
658
|
-
2. An array of server objects: `[{ "type": "stdio", ... }, { "type": "sse", ... }, { "type": "http", ... }]`
|
|
659
|
-
3. An object with named servers under `mcpServers` key (as shown above)
|
|
660
|
-
|
|
661
|
-
Special configuration options:
|
|
662
|
-
- `comment` and `description` are reserved keys that are ignored during parsing and can be used for documentation
|
|
663
|
-
- Server type can be inferred from the presence of either `command` (for stdio) or `url` (for SSE/HTTP)
|
|
664
|
-
- For HTTP servers, `endpoint` specifies the JSON-RPC endpoint path (defaults to '/rpc' if not specified)
|
|
665
|
-
- All string values in arrays (like `args`) are automatically converted to strings
|
|
666
|
-
|
|
667
|
-
## Session-Based MCP Protocol Support
|
|
668
|
-
|
|
669
|
-
Both HTTP and Streamable HTTP transports now support session-based MCP servers that require session continuity:
|
|
670
|
-
|
|
671
|
-
### Session Management Features
|
|
672
|
-
|
|
673
|
-
- **Automatic Session Management**: Captures session IDs from `initialize` response headers
|
|
674
|
-
- **Session Header Injection**: Automatically includes `Mcp-Session-Id` header in subsequent requests
|
|
675
|
-
- **Session Termination**: Sends HTTP DELETE requests to properly terminate sessions during cleanup
|
|
676
|
-
- **Session Validation**: Validates session ID format for security (8-128 alphanumeric characters with hyphens/underscores)
|
|
677
|
-
- **Backward Compatibility**: Works with both session-based and stateless MCP servers
|
|
678
|
-
- **Session Cleanup**: Properly cleans up session state during connection teardown
|
|
679
|
-
|
|
680
|
-
### Resumability and Redelivery (Streamable HTTP)
|
|
681
|
-
|
|
682
|
-
The Streamable HTTP transport provides additional resumability features for reliable message delivery:
|
|
683
|
-
|
|
684
|
-
- **Event ID Tracking**: Automatically tracks event IDs from SSE responses
|
|
685
|
-
- **Last-Event-ID Header**: Includes `Last-Event-ID` header in requests for resuming from disconnection points
|
|
686
|
-
- **Message Replay**: Enables servers to replay missed messages from the last received event
|
|
687
|
-
- **Connection Recovery**: Maintains message continuity even with unstable network connections
|
|
688
|
-
|
|
689
|
-
### Security Features
|
|
690
|
-
|
|
691
|
-
Both transports implement security best practices:
|
|
692
|
-
|
|
693
|
-
- **URL Validation**: Validates server URLs to ensure only HTTP/HTTPS protocols are used
|
|
694
|
-
- **Session ID Validation**: Enforces secure session ID formats to prevent malicious injection
|
|
695
|
-
- **Security Warnings**: Logs warnings for potentially insecure configurations (e.g., 0.0.0.0 binding)
|
|
696
|
-
- **Header Sanitization**: Properly handles and validates all session-related headers
|
|
697
|
-
|
|
698
|
-
### Usage
|
|
699
|
-
|
|
700
|
-
The session support is transparent to the user - no additional configuration is required. The client will automatically detect and handle session-based servers by:
|
|
701
|
-
|
|
702
|
-
1. **Session Initialization**: Capturing the `Mcp-Session-Id` header from the `initialize` response
|
|
703
|
-
2. **Session Persistence**: Including this header in all subsequent requests (except `initialize`)
|
|
704
|
-
3. **Session Termination**: Sending HTTP DELETE request with session ID during cleanup
|
|
705
|
-
4. **Resumability** (Streamable HTTP): Tracking event IDs and including `Last-Event-ID` for message replay
|
|
706
|
-
5. **Security Validation**: Validating session IDs and server URLs for security
|
|
707
|
-
6. **Logging**: Comprehensive logging of session activity for debugging purposes
|
|
269
|
+
require 'mcp_client'
|
|
270
|
+
require 'openai'
|
|
708
271
|
|
|
709
|
-
|
|
272
|
+
mcp = MCPClient.connect('npx -y @modelcontextprotocol/server-filesystem .')
|
|
273
|
+
tools = mcp.to_openai_tools
|
|
710
274
|
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
]
|
|
275
|
+
client = OpenAI::Client.new(api_key: ENV['OPENAI_API_KEY'])
|
|
276
|
+
response = client.chat.completions.create(
|
|
277
|
+
model: 'gpt-4',
|
|
278
|
+
messages: [{ role: 'user', content: 'List files' }],
|
|
279
|
+
tools: tools
|
|
717
280
|
)
|
|
718
|
-
|
|
719
|
-
# Use the client...
|
|
720
|
-
tools = client.list_tools
|
|
721
|
-
|
|
722
|
-
# Session automatically terminated with HTTP DELETE request
|
|
723
|
-
client.cleanup
|
|
724
281
|
```
|
|
725
282
|
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
## OAuth 2.1 Authentication
|
|
729
|
-
|
|
730
|
-
The Ruby MCP Client includes comprehensive OAuth 2.1 support for secure authentication with MCP servers:
|
|
283
|
+
### Anthropic
|
|
731
284
|
|
|
732
285
|
```ruby
|
|
733
286
|
require 'mcp_client'
|
|
287
|
+
require 'anthropic'
|
|
734
288
|
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
server_url: 'https://api.example.com/mcp',
|
|
738
|
-
redirect_uri: 'http://localhost:8080/callback',
|
|
739
|
-
scope: 'mcp:read mcp:write'
|
|
740
|
-
)
|
|
741
|
-
|
|
742
|
-
# Check if authorization is needed
|
|
743
|
-
unless MCPClient::OAuthClient.valid_token?(server)
|
|
744
|
-
# Start OAuth flow
|
|
745
|
-
auth_url = MCPClient::OAuthClient.start_oauth_flow(server)
|
|
746
|
-
puts "Please visit: #{auth_url}"
|
|
747
|
-
|
|
748
|
-
# After user authorization, complete the flow
|
|
749
|
-
# token = MCPClient::OAuthClient.complete_oauth_flow(server, code, state)
|
|
750
|
-
end
|
|
289
|
+
mcp = MCPClient.connect('npx -y @modelcontextprotocol/server-filesystem .')
|
|
290
|
+
tools = mcp.to_anthropic_tools
|
|
751
291
|
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
tools = server.list_tools
|
|
292
|
+
client = Anthropic::Client.new(access_token: ENV['ANTHROPIC_API_KEY'])
|
|
293
|
+
# Use tools with Claude API
|
|
755
294
|
```
|
|
756
295
|
|
|
757
|
-
|
|
296
|
+
See `examples/` for complete implementations:
|
|
297
|
+
- `ruby_openai_mcp.rb`, `openai_ruby_mcp.rb` - OpenAI integration
|
|
298
|
+
- `ruby_anthropic_mcp.rb` - Anthropic integration
|
|
299
|
+
- `gemini_ai_mcp.rb` - Google Vertex AI integration
|
|
758
300
|
|
|
759
|
-
|
|
301
|
+
## OAuth 2.1 Authentication
|
|
760
302
|
|
|
761
303
|
```ruby
|
|
762
|
-
|
|
763
|
-
|
|
304
|
+
require 'mcp_client/auth/browser_oauth'
|
|
305
|
+
|
|
306
|
+
oauth = MCPClient::Auth::OAuthProvider.new(
|
|
764
307
|
server_url: 'https://api.example.com/mcp',
|
|
765
308
|
redirect_uri: 'http://localhost:8080/callback',
|
|
766
309
|
scope: 'mcp:read mcp:write'
|
|
767
310
|
)
|
|
768
311
|
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
oauth_provider.redirect_uri = 'http://localhost:9000/callback'
|
|
772
|
-
|
|
773
|
-
# Start authorization flow
|
|
774
|
-
auth_url = oauth_provider.start_authorization_flow
|
|
312
|
+
browser_oauth = MCPClient::Auth::BrowserOAuth.new(oauth)
|
|
313
|
+
token = browser_oauth.authenticate # Opens browser, handles callback
|
|
775
314
|
|
|
776
|
-
|
|
777
|
-
|
|
315
|
+
client = MCPClient::Client.new(
|
|
316
|
+
mcp_server_configs: [{
|
|
317
|
+
type: 'streamable_http',
|
|
318
|
+
base_url: 'https://api.example.com/mcp',
|
|
319
|
+
oauth_provider: oauth
|
|
320
|
+
}]
|
|
321
|
+
)
|
|
778
322
|
```
|
|
779
323
|
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
- **OAuth 2.1 compliance** with PKCE for security
|
|
783
|
-
- **Automatic server discovery** via `.well-known` endpoints
|
|
784
|
-
- **Dynamic client registration** when supported by servers
|
|
785
|
-
- **Token refresh** and automatic token management
|
|
786
|
-
- **Pluggable storage** for tokens and client credentials
|
|
787
|
-
- **Runtime configuration** via getter/setter methods
|
|
788
|
-
|
|
789
|
-
For complete OAuth documentation, see [OAUTH.md](OAUTH.md).
|
|
790
|
-
|
|
791
|
-
## Resources
|
|
792
|
-
|
|
793
|
-
The Ruby MCP Client provides full support for the MCP resources specification, enabling access to files, data, and other content with URI-based identification.
|
|
324
|
+
Features: PKCE, server discovery (`.well-known`), dynamic registration, token refresh.
|
|
794
325
|
|
|
795
|
-
|
|
326
|
+
See [OAUTH.md](OAUTH.md) for full documentation.
|
|
796
327
|
|
|
797
|
-
|
|
798
|
-
- **Resource templates** with URI patterns (RFC 6570)
|
|
799
|
-
- **Resource subscriptions** for real-time updates
|
|
800
|
-
- **Binary content support** with base64 encoding
|
|
801
|
-
- **Resource annotations** for metadata (audience, priority, lastModified)
|
|
802
|
-
- **ResourceContent objects** for structured content access
|
|
803
|
-
|
|
804
|
-
### Resource API
|
|
328
|
+
## Server Notifications
|
|
805
329
|
|
|
806
330
|
```ruby
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
#
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
if next_cursor
|
|
817
|
-
next_result = server.list_resources(cursor: next_cursor)
|
|
331
|
+
client.on_notification do |server, method, params|
|
|
332
|
+
case method
|
|
333
|
+
when 'notifications/tools/list_changed'
|
|
334
|
+
client.clear_cache # Auto-handled
|
|
335
|
+
when 'notifications/message'
|
|
336
|
+
puts "Log: #{params['data']}"
|
|
337
|
+
when 'notifications/roots/list_changed'
|
|
338
|
+
puts "Roots changed"
|
|
339
|
+
end
|
|
818
340
|
end
|
|
341
|
+
```
|
|
819
342
|
|
|
820
|
-
|
|
821
|
-
resource = resources.first
|
|
822
|
-
resource.uri # "file:///example.txt"
|
|
823
|
-
resource.name # "example.txt"
|
|
824
|
-
resource.title # Optional human-readable title
|
|
825
|
-
resource.description # Optional description
|
|
826
|
-
resource.mime_type # "text/plain"
|
|
827
|
-
resource.size # Optional file size in bytes
|
|
828
|
-
resource.annotations # Optional metadata hash
|
|
343
|
+
## Session Management
|
|
829
344
|
|
|
830
|
-
|
|
831
|
-
contents = server.read_resource(resource.uri)
|
|
345
|
+
Both HTTP and Streamable HTTP transports automatically handle session-based servers:
|
|
832
346
|
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
text = content.text
|
|
838
|
-
mime = content.mime_type # e.g., "text/plain"
|
|
839
|
-
elsif content.binary?
|
|
840
|
-
# Binary content (base64 encoded)
|
|
841
|
-
blob = content.blob # Base64 string
|
|
842
|
-
data = Base64.decode64(blob) # Decoded binary data
|
|
843
|
-
end
|
|
347
|
+
- **Session capture**: Extracts `Mcp-Session-Id` from initialize response
|
|
348
|
+
- **Session persistence**: Includes session header in subsequent requests
|
|
349
|
+
- **Session termination**: Sends DELETE request during cleanup
|
|
350
|
+
- **Resumability** (Streamable HTTP): Tracks event IDs for message replay
|
|
844
351
|
|
|
845
|
-
|
|
846
|
-
if content.annotations
|
|
847
|
-
audience = content.annotations['audience'] # e.g., ["user", "assistant"]
|
|
848
|
-
priority = content.annotations['priority'] # e.g., 0.5
|
|
849
|
-
modified = content.annotations['lastModified'] # ISO 8601 timestamp
|
|
850
|
-
end
|
|
851
|
-
end
|
|
352
|
+
No configuration required - works automatically.
|
|
852
353
|
|
|
853
|
-
|
|
854
|
-
templates_result = server.list_resource_templates
|
|
855
|
-
templates = templates_result['resourceTemplates'] # Array of ResourceTemplate objects
|
|
354
|
+
## Server Compatibility
|
|
856
355
|
|
|
857
|
-
|
|
858
|
-
template.uri_template # "file:///{path}" (RFC 6570 URI template)
|
|
859
|
-
template.name # Template name
|
|
860
|
-
template.title # Optional title
|
|
861
|
-
template.description # Optional description
|
|
862
|
-
template.mime_type # Optional MIME type hint
|
|
356
|
+
Works with any MCP-compatible server:
|
|
863
357
|
|
|
864
|
-
|
|
865
|
-
|
|
358
|
+
- [@modelcontextprotocol/server-filesystem](https://www.npmjs.com/package/@modelcontextprotocol/server-filesystem)
|
|
359
|
+
- [@playwright/mcp](https://www.npmjs.com/package/@playwright/mcp)
|
|
360
|
+
- [FastMCP](https://github.com/jlowin/fastmcp)
|
|
361
|
+
- Custom servers implementing MCP protocol
|
|
866
362
|
|
|
867
|
-
|
|
868
|
-
server.unsubscribe_resource('file:///watched.txt') # Returns true on success
|
|
363
|
+
### FastMCP Example
|
|
869
364
|
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
can_subscribe = capabilities['resources']['subscribe'] # true/false
|
|
874
|
-
list_changed = capabilities['resources']['listChanged'] # true/false
|
|
875
|
-
end
|
|
365
|
+
```bash
|
|
366
|
+
# Start server
|
|
367
|
+
python examples/echo_server_streamable.py
|
|
876
368
|
```
|
|
877
369
|
|
|
878
|
-
### Working with ResourceContent
|
|
879
|
-
|
|
880
|
-
The `ResourceContent` class provides a structured way to handle both text and binary content:
|
|
881
|
-
|
|
882
370
|
```ruby
|
|
883
|
-
#
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
mime_type: 'text/plain',
|
|
888
|
-
text: 'File contents here',
|
|
889
|
-
annotations: {
|
|
890
|
-
'audience' => ['user'],
|
|
891
|
-
'priority' => 1.0
|
|
892
|
-
}
|
|
893
|
-
)
|
|
894
|
-
|
|
895
|
-
# ResourceContent for binary data
|
|
896
|
-
binary_content = MCPClient::ResourceContent.new(
|
|
897
|
-
uri: 'file:///image.png',
|
|
898
|
-
name: 'image.png',
|
|
899
|
-
mime_type: 'image/png',
|
|
900
|
-
blob: Base64.strict_encode64(binary_data)
|
|
901
|
-
)
|
|
902
|
-
|
|
903
|
-
# Access content
|
|
904
|
-
if content.text?
|
|
905
|
-
puts content.text
|
|
906
|
-
elsif content.binary?
|
|
907
|
-
data = Base64.decode64(content.blob)
|
|
908
|
-
end
|
|
371
|
+
# Connect and use
|
|
372
|
+
client = MCPClient.connect('http://localhost:8931/mcp')
|
|
373
|
+
tools = client.list_tools
|
|
374
|
+
result = client.call_tool('echo', { message: 'Hello!' })
|
|
909
375
|
```
|
|
910
376
|
|
|
911
|
-
## Key Features
|
|
912
|
-
|
|
913
|
-
### Client Features
|
|
914
|
-
|
|
915
|
-
- **Multiple transports** - Support for stdio, SSE, HTTP, and Streamable HTTP transports
|
|
916
|
-
- **Multiple servers** - Connect to multiple MCP servers simultaneously
|
|
917
|
-
- **Named servers** - Associate names with servers and find/reference them by name
|
|
918
|
-
- **Server lookup** - Find servers by name using `find_server`
|
|
919
|
-
- **Tool association** - Each tool knows which server it belongs to
|
|
920
|
-
- **Tool discovery** - Find tools by name or pattern
|
|
921
|
-
- **Server disambiguation** - Specify which server to use when tools with same name exist in multiple servers
|
|
922
|
-
- **Atomic tool calls** - Simple API for invoking tools with parameters
|
|
923
|
-
- **Batch support** - Call multiple tools in a single operation
|
|
924
|
-
- **Prompts support** - List and get prompts with parameters from MCP servers
|
|
925
|
-
- **Resources support** - List and read resources by URI from MCP servers
|
|
926
|
-
- **API conversions** - Built-in format conversion for OpenAI, Anthropic, and Google Vertex AI APIs
|
|
927
|
-
- **Thread safety** - Synchronized access for thread-safe operation
|
|
928
|
-
- **Server notifications** - Support for JSON-RPC notifications
|
|
929
|
-
- **Custom RPC methods** - Send any custom JSON-RPC method
|
|
930
|
-
- **Consistent error handling** - Rich error types for better exception handling
|
|
931
|
-
- **JSON configuration** - Support for server definition files in JSON format with name retention
|
|
932
|
-
|
|
933
|
-
### Server-Sent Events (SSE) Implementation
|
|
934
|
-
|
|
935
|
-
The SSE client implementation provides these key features:
|
|
936
|
-
|
|
937
|
-
- **Robust connection handling**: Properly manages HTTP/HTTPS connections with configurable timeouts and retries
|
|
938
|
-
- **Automatic redirect support**: Follows HTTP redirects up to 3 hops for seamless server integration
|
|
939
|
-
- **Advanced connection management**:
|
|
940
|
-
- **Inactivity tracking**: Monitors connection activity to detect idle connections
|
|
941
|
-
- **Automatic ping**: Sends ping requests after a configurable period of inactivity (default: 10 seconds)
|
|
942
|
-
- **Automatic disconnection**: Closes idle connections after inactivity (2.5× ping interval)
|
|
943
|
-
- **MCP compliant**: Any server communication resets the inactivity timer per specification
|
|
944
|
-
- **Intelligent reconnection**:
|
|
945
|
-
- **Ping failure detection**: Tracks consecutive ping failures (when server isn't responding)
|
|
946
|
-
- **Automatic reconnection**: Attempts to reconnect after 3 consecutive ping failures
|
|
947
|
-
- **Exponential backoff**: Uses increasing delays between reconnection attempts
|
|
948
|
-
- **Smart retry limits**: Caps reconnection attempts (default: 5) to avoid infinite loops
|
|
949
|
-
- **Connection state monitoring**: Properly detects and handles closed connections to prevent errors
|
|
950
|
-
- **Failure transparency**: Handles reconnection in the background without disrupting client code
|
|
951
|
-
- **Thread safety**: All operations are thread-safe using monitors and synchronized access
|
|
952
|
-
- **Reliable error handling**: Comprehensive error handling for network issues, timeouts, and malformed responses
|
|
953
|
-
- **JSON-RPC over SSE**: Full implementation of JSON-RPC 2.0 over SSE transport with initialize handshake
|
|
954
|
-
- **Streaming support**: Native streaming for real-time updates via the `call_tool_streaming` method, which returns an Enumerator for processing results as they arrive
|
|
955
|
-
- **Notification support**: Built-in handling for JSON-RPC notifications with automatic tool cache invalidation and custom notification callback support
|
|
956
|
-
- **Custom RPC methods**: Send any custom JSON-RPC method or notification through `send_rpc` and `send_notification`
|
|
957
|
-
- **Configurable retries**: All RPC requests support configurable retries with exponential backoff
|
|
958
|
-
- **Consistent logging**: Tagged, leveled logging across all components for better debugging
|
|
959
|
-
- **Graceful fallbacks**: Automatic fallback to synchronous HTTP when SSE connection fails
|
|
960
|
-
- **URL normalization**: Consistent URL handling that respects user-provided formats
|
|
961
|
-
- **Server connectivity check**: Built-in `ping` method to test server connectivity and health
|
|
962
|
-
|
|
963
|
-
### HTTP Transport Implementation
|
|
964
|
-
|
|
965
|
-
The HTTP transport provides a simpler, stateless communication mechanism for MCP servers:
|
|
966
|
-
|
|
967
|
-
- **Request/Response Model**: Standard HTTP request/response cycle for each JSON-RPC call
|
|
968
|
-
- **Automatic redirect support**: Follows HTTP redirects up to 3 hops for seamless server integration
|
|
969
|
-
- **JSON-Only Responses**: Accepts only `application/json` responses (no SSE support)
|
|
970
|
-
- **Session Support**: Automatic session header (`Mcp-Session-Id`) capture and injection for session-based MCP servers
|
|
971
|
-
- **Session Termination**: Proper session cleanup with HTTP DELETE requests during connection teardown
|
|
972
|
-
- **Session Validation**: Security validation of session IDs to prevent malicious injection
|
|
973
|
-
- **Stateless & Stateful**: Supports both stateless servers and session-based servers that require state continuity
|
|
974
|
-
- **HTTP Headers Support**: Full support for custom headers including authorization, API keys, and other metadata
|
|
975
|
-
- **Reliable Error Handling**: Comprehensive HTTP status code handling with appropriate error mapping
|
|
976
|
-
- **Configurable Retries**: Exponential backoff retry logic for transient network failures
|
|
977
|
-
- **Connection Pooling**: Uses Faraday's connection pooling for efficient HTTP connections
|
|
978
|
-
- **Timeout Management**: Configurable timeouts for both connection establishment and request completion
|
|
979
|
-
- **JSON-RPC over HTTP**: Full JSON-RPC 2.0 implementation over HTTP POST requests
|
|
980
|
-
- **MCP Protocol Compliance**: Supports all standard MCP methods (initialize, tools/list, tools/call)
|
|
981
|
-
- **Custom RPC Methods**: Send any custom JSON-RPC method or notification
|
|
982
|
-
- **Thread Safety**: All operations are thread-safe for concurrent usage
|
|
983
|
-
- **Streaming API Compatibility**: Provides `call_tool_streaming` method for API compatibility (returns single response)
|
|
984
|
-
- **Graceful Degradation**: Simple fallback behavior when complex features aren't needed
|
|
985
|
-
|
|
986
|
-
### Streamable HTTP Transport Implementation
|
|
987
|
-
|
|
988
|
-
The Streamable HTTP transport bridges HTTP and Server-Sent Events, designed for servers that use HTTP POST but return SSE-formatted responses:
|
|
989
|
-
|
|
990
|
-
- **Hybrid Communication**: HTTP POST requests with Server-Sent Event formatted responses
|
|
991
|
-
- **SSE Response Parsing**: Automatically parses `event:` and `data:` lines from SSE responses
|
|
992
|
-
- **Session Support**: Automatic session header (`Mcp-Session-Id`) capture and injection for session-based MCP servers
|
|
993
|
-
- **Session Termination**: Proper session cleanup with HTTP DELETE requests during connection teardown
|
|
994
|
-
- **Resumability**: Event ID tracking and `Last-Event-ID` header support for message replay after disconnections
|
|
995
|
-
- **Session Validation**: Security validation of session IDs to prevent malicious injection
|
|
996
|
-
- **HTTP Semantics**: Maintains standard HTTP request/response model for client compatibility
|
|
997
|
-
- **Streaming Format Support**: Handles complex SSE responses with multiple fields (event, id, retry, etc.)
|
|
998
|
-
- **Error Handling**: Comprehensive error handling for both HTTP and SSE parsing failures
|
|
999
|
-
- **Headers Optimization**: Includes SSE-compatible headers (`Accept: text/event-stream, application/json`, `Cache-Control: no-cache`)
|
|
1000
|
-
- **JSON-RPC Compliance**: Full JSON-RPC 2.0 support over the hybrid HTTP/SSE transport
|
|
1001
|
-
- **Retry Logic**: Exponential backoff for both connection and parsing failures
|
|
1002
|
-
- **Thread Safety**: All operations are thread-safe for concurrent usage
|
|
1003
|
-
- **Malformed Response Handling**: Graceful handling of invalid SSE format or missing data lines
|
|
1004
|
-
|
|
1005
377
|
## Requirements
|
|
1006
378
|
|
|
1007
379
|
- Ruby >= 3.2.0
|
|
1008
380
|
- No runtime dependencies
|
|
1009
381
|
|
|
1010
|
-
## Implementing an MCP Server
|
|
1011
|
-
|
|
1012
|
-
To implement a compatible MCP server you must:
|
|
1013
|
-
|
|
1014
|
-
- Listen on your chosen transport (JSON-RPC stdio, HTTP SSE, HTTP, or Streamable HTTP)
|
|
1015
|
-
- Respond to `list_tools` requests with a JSON list of tools
|
|
1016
|
-
- Respond to `call_tool` requests by executing the specified tool
|
|
1017
|
-
- Return results (or errors) in JSON format
|
|
1018
|
-
- Optionally send JSON-RPC notifications for events like tool updates
|
|
1019
|
-
|
|
1020
|
-
### JSON-RPC Notifications
|
|
1021
|
-
|
|
1022
|
-
The client supports JSON-RPC notifications from the server:
|
|
1023
|
-
|
|
1024
|
-
- Default notification handler for `notifications/tools/list_changed` to automatically clear the tool cache
|
|
1025
|
-
- Custom notification handling via the `on_notification` method
|
|
1026
|
-
- Callbacks receive the server instance, method name, and parameters
|
|
1027
|
-
- Multiple notification listeners can be registered
|
|
1028
|
-
|
|
1029
|
-
## Tool Schema
|
|
1030
|
-
|
|
1031
|
-
Each tool is defined by a name, description, and a JSON Schema for its parameters:
|
|
1032
|
-
|
|
1033
|
-
```json
|
|
1034
|
-
{
|
|
1035
|
-
"name": "example_tool",
|
|
1036
|
-
"description": "Does something useful",
|
|
1037
|
-
"schema": {
|
|
1038
|
-
"type": "object",
|
|
1039
|
-
"properties": {
|
|
1040
|
-
"param1": { "type": "string" },
|
|
1041
|
-
"param2": { "type": "number" }
|
|
1042
|
-
},
|
|
1043
|
-
"required": ["param1"]
|
|
1044
|
-
}
|
|
1045
|
-
}
|
|
1046
|
-
```
|
|
1047
|
-
|
|
1048
382
|
## License
|
|
1049
383
|
|
|
1050
|
-
|
|
384
|
+
Available as open source under the [MIT License](LICENSE).
|
|
1051
385
|
|
|
1052
386
|
## Contributing
|
|
1053
387
|
|
|
1054
|
-
Bug reports and pull requests
|
|
1055
|
-
https://github.com/simonx1/ruby-mcp-client.
|
|
388
|
+
Bug reports and pull requests welcome at https://github.com/simonx1/ruby-mcp-client.
|