ruby-mcp-client 0.9.0 → 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 +220 -1229
- data/lib/mcp_client/client.rb +189 -5
- 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 -3
- data/lib/mcp_client/root.rb +63 -0
- data/lib/mcp_client/server_factory.rb +4 -2
- data/lib/mcp_client/server_http.rb +31 -1
- data/lib/mcp_client/server_sse.rb +130 -5
- data/lib/mcp_client/server_stdio.rb +140 -0
- data/lib/mcp_client/server_streamable_http.rb +131 -1
- data/lib/mcp_client/version.rb +1 -1
- data/lib/mcp_client.rb +317 -4
- metadata +3 -2
data/README.md
CHANGED
|
@@ -1,1397 +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
29
|
## MCP Protocol Support
|
|
44
30
|
|
|
45
|
-
|
|
31
|
+
Implements **MCP 2025-06-18** specification:
|
|
46
32
|
|
|
47
|
-
|
|
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
|
|
48
42
|
|
|
49
|
-
|
|
50
|
-
- **Structured Tool Outputs** - Tools can declare output schemas and return type-safe, validated structured data
|
|
51
|
-
- **Elicitation (Server-initiated User Interactions)** - Servers can request user input during tool execution via bidirectional JSON-RPC (stdio, SSE, and Streamable HTTP transports)
|
|
52
|
-
- **Tool Annotations** - Support for tool behavior annotations (readOnly, destructive, requiresConfirmation) for safer tool execution
|
|
53
|
-
- **OAuth 2.1 Authorization Framework** - Complete authentication with PKCE, dynamic client registration, server discovery, and runtime configuration
|
|
54
|
-
- **Streamable HTTP Transport** - Enhanced transport with Server-Sent Event formatted responses and session management
|
|
55
|
-
- **HTTP Redirect Support** - Automatic redirect handling for both SSE and HTTP transports with configurable limits
|
|
56
|
-
- **FastMCP Compatibility** - Full compatibility with FastMCP servers including proper line ending handling
|
|
57
|
-
- **Prompts Support** - Full implementation of MCP prompts for dynamic content generation
|
|
58
|
-
- **Resources Support** - Full MCP resources specification compliance including templates, subscriptions, pagination, and annotations
|
|
43
|
+
## Quick Connect API (Recommended)
|
|
59
44
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
### Basic Client Usage
|
|
45
|
+
The simplest way to connect to an MCP server:
|
|
63
46
|
|
|
64
47
|
```ruby
|
|
65
48
|
require 'mcp_client'
|
|
66
49
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
name: 'sse_api', # Optional name for this server
|
|
79
|
-
read_timeout: 30, # Optional timeout in seconds (default: 30)
|
|
80
|
-
ping: 10, # Optional ping interval in seconds of inactivity (default: 10)
|
|
81
|
-
# Connection closes automatically after inactivity (2.5x ping interval)
|
|
82
|
-
retries: 3, # Optional number of retry attempts (default: 0)
|
|
83
|
-
retry_backoff: 1, # Optional backoff delay in seconds (default: 1)
|
|
84
|
-
# Native support for tool streaming via call_tool_streaming method
|
|
85
|
-
logger: Logger.new($stdout, level: Logger::INFO) # Optional logger for this server
|
|
86
|
-
),
|
|
87
|
-
# Remote HTTP server (request/response without streaming)
|
|
88
|
-
MCPClient.http_config(
|
|
89
|
-
base_url: 'https://api.example.com',
|
|
90
|
-
endpoint: '/rpc', # Optional JSON-RPC endpoint path (default: '/rpc')
|
|
91
|
-
headers: { 'Authorization' => 'Bearer YOUR_TOKEN' },
|
|
92
|
-
name: 'http_api', # Optional name for this server
|
|
93
|
-
read_timeout: 30, # Optional timeout in seconds (default: 30)
|
|
94
|
-
retries: 3, # Optional number of retry attempts (default: 3)
|
|
95
|
-
retry_backoff: 1, # Optional backoff delay in seconds (default: 1)
|
|
96
|
-
logger: Logger.new($stdout, level: Logger::INFO) # Optional logger for this server
|
|
97
|
-
),
|
|
98
|
-
# Streamable HTTP server (HTTP POST with SSE responses and session management)
|
|
99
|
-
MCPClient.streamable_http_config(
|
|
100
|
-
base_url: 'https://api.example.com/mcp',
|
|
101
|
-
endpoint: '/rpc', # Optional JSON-RPC endpoint path (default: '/rpc')
|
|
102
|
-
headers: { 'Authorization' => 'Bearer YOUR_TOKEN' },
|
|
103
|
-
name: 'streamable_api', # Optional name for this server
|
|
104
|
-
read_timeout: 60, # Optional timeout in seconds (default: 30)
|
|
105
|
-
retries: 3, # Optional number of retry attempts (default: 3)
|
|
106
|
-
retry_backoff: 1, # Optional backoff delay in seconds (default: 1)
|
|
107
|
-
logger: Logger.new($stdout, level: Logger::INFO) # Optional logger for this server
|
|
108
|
-
)
|
|
109
|
-
],
|
|
110
|
-
# Optional logger for the client and all servers without explicit loggers
|
|
111
|
-
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)
|
|
112
61
|
)
|
|
113
62
|
|
|
114
|
-
#
|
|
115
|
-
client = MCPClient.
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
)
|
|
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)
|
|
119
68
|
|
|
120
|
-
#
|
|
121
|
-
# 1. A single server object:
|
|
122
|
-
# { "type": "sse", "url": "http://example.com/sse" }
|
|
123
|
-
# { "type": "http", "url": "http://example.com", "endpoint": "/rpc" }
|
|
124
|
-
# { "type": "streamable_http", "url": "http://example.com/mcp", "endpoint": "/rpc" }
|
|
125
|
-
# 2. An array of server objects:
|
|
126
|
-
# [{ "type": "stdio", "command": "npx server" }, { "type": "sse", "url": "http://..." }, { "type": "streamable_http", "url": "http://..." }]
|
|
127
|
-
# 3. An object with "mcpServers" key containing named servers:
|
|
128
|
-
# { "mcpServers": { "server1": { "type": "sse", "url": "http://..." }, "server2": { "type": "streamable_http", "url": "http://..." } } }
|
|
129
|
-
# Note: When using this format, server1/server2 will be accessible by name
|
|
130
|
-
|
|
131
|
-
# List available tools
|
|
69
|
+
# Use the client
|
|
132
70
|
tools = client.list_tools
|
|
71
|
+
result = client.call_tool('example_tool', { param: 'value' })
|
|
72
|
+
client.cleanup
|
|
73
|
+
```
|
|
133
74
|
|
|
134
|
-
|
|
135
|
-
filesystem_server = client.find_server('filesystem')
|
|
75
|
+
**Transport Detection:**
|
|
136
76
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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) |
|
|
140
84
|
|
|
141
|
-
|
|
142
|
-
result = client.call_tool('example_tool', { param1: 'value1', param2: 42 })
|
|
85
|
+
## Working with Tools, Prompts & Resources
|
|
143
86
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
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')
|
|
148
92
|
|
|
149
|
-
#
|
|
93
|
+
# Batch tool calls
|
|
150
94
|
results = client.call_tools([
|
|
151
|
-
{ name: 'tool1', parameters: {
|
|
152
|
-
{ name: 'tool2', parameters: {
|
|
95
|
+
{ name: 'tool1', parameters: { key: 'value' } },
|
|
96
|
+
{ name: 'tool2', parameters: { key: 'value' }, server: 'specific_server' }
|
|
153
97
|
])
|
|
154
98
|
|
|
155
|
-
#
|
|
156
|
-
|
|
157
|
-
client.call_tool_streaming('streaming_tool', { param: 'value' }, server: 'api').each do |chunk|
|
|
158
|
-
# Process each chunk as it arrives
|
|
99
|
+
# Streaming (SSE/Streamable HTTP)
|
|
100
|
+
client.call_tool_streaming('tool', { param: 'value' }).each do |chunk|
|
|
159
101
|
puts chunk
|
|
160
102
|
end
|
|
161
103
|
|
|
162
|
-
#
|
|
163
|
-
# List available prompts from all servers
|
|
104
|
+
# Prompts
|
|
164
105
|
prompts = client.list_prompts
|
|
106
|
+
result = client.get_prompt('greeting', { name: 'Alice' })
|
|
165
107
|
|
|
166
|
-
#
|
|
167
|
-
result = client.get_prompt('greeting', { name: 'Ruby Developer' })
|
|
168
|
-
|
|
169
|
-
# Get a prompt from a specific server
|
|
170
|
-
result = client.get_prompt('greeting', { name: 'Ruby Developer' }, server: 'filesystem')
|
|
171
|
-
|
|
172
|
-
# === Working with Resources ===
|
|
173
|
-
# List available resources from all servers (returns hash with 'resources' array and optional 'nextCursor')
|
|
108
|
+
# Resources
|
|
174
109
|
result = client.list_resources
|
|
175
|
-
resources = result['resources'] # Array of Resource objects from all servers
|
|
176
|
-
next_cursor = result['nextCursor'] # nil when aggregating multiple servers
|
|
177
|
-
|
|
178
|
-
# Get resources from a specific server with pagination support
|
|
179
|
-
result = client.servers.first.list_resources
|
|
180
|
-
resources = result['resources'] # Array of Resource objects
|
|
181
|
-
next_cursor = result['nextCursor'] # For pagination
|
|
182
|
-
|
|
183
|
-
# List resources with pagination (only works with single server or client.list_resources with cursor)
|
|
184
|
-
result = client.list_resources(cursor: next_cursor) # Uses first server when cursor provided
|
|
185
|
-
|
|
186
|
-
# Read a specific resource by URI (returns array of ResourceContent objects)
|
|
187
110
|
contents = client.read_resource('file:///example.txt')
|
|
188
111
|
contents.each do |content|
|
|
189
|
-
if content.text?
|
|
190
|
-
|
|
191
|
-
elsif content.binary?
|
|
192
|
-
data = Base64.decode64(content.blob) # Binary content
|
|
193
|
-
end
|
|
194
|
-
puts content.mime_type if content.mime_type
|
|
195
|
-
puts content.annotations if content.annotations # Optional metadata
|
|
196
|
-
end
|
|
197
|
-
|
|
198
|
-
# Read a resource from a specific server
|
|
199
|
-
contents = client.read_resource('file:///example.txt', server: 'filesystem')
|
|
200
|
-
|
|
201
|
-
# Format tools for specific AI services
|
|
202
|
-
openai_tools = client.to_openai_tools
|
|
203
|
-
anthropic_tools = client.to_anthropic_tools
|
|
204
|
-
google_tools = client.to_google_tools
|
|
205
|
-
|
|
206
|
-
# Register for server notifications
|
|
207
|
-
client.on_notification do |server, method, params|
|
|
208
|
-
puts "Server notification: #{server.class}[#{server.name}] - #{method} - #{params}"
|
|
209
|
-
# Handle specific notifications based on method name
|
|
210
|
-
# '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?
|
|
211
114
|
end
|
|
212
|
-
|
|
213
|
-
# Send custom JSON-RPC requests or notifications
|
|
214
|
-
client.send_rpc('custom_method', params: { key: 'value' }, server: :sse) # Uses specific server by type
|
|
215
|
-
client.send_rpc('custom_method', params: { key: 'value' }, server: 'filesystem') # Uses specific server by name
|
|
216
|
-
result = client.send_rpc('another_method', params: { data: 123 }) # Uses first available server
|
|
217
|
-
client.send_notification('status_update', params: { status: 'ready' })
|
|
218
|
-
|
|
219
|
-
# Check server connectivity
|
|
220
|
-
client.ping # Basic connectivity check (zero-parameter heartbeat call)
|
|
221
|
-
client.ping(server_index: 1) # Ping a specific server by index
|
|
222
|
-
|
|
223
|
-
# Clear cached tools to force fresh fetch on next list
|
|
224
|
-
client.clear_cache
|
|
225
|
-
# Clean up connections
|
|
226
|
-
client.cleanup
|
|
227
115
|
```
|
|
228
116
|
|
|
229
|
-
|
|
117
|
+
## MCP 2025-06-18 Features
|
|
230
118
|
|
|
231
|
-
|
|
119
|
+
### Tool Annotations
|
|
232
120
|
|
|
233
121
|
```ruby
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
#
|
|
238
|
-
logger = Logger.new($stdout)
|
|
239
|
-
logger.level = Logger::INFO
|
|
240
|
-
|
|
241
|
-
# Create an MCP client that connects to an HTTP MCP server
|
|
242
|
-
http_client = MCPClient.create_client(
|
|
243
|
-
mcp_server_configs: [
|
|
244
|
-
MCPClient.http_config(
|
|
245
|
-
base_url: 'https://api.example.com',
|
|
246
|
-
endpoint: '/mcp', # JSON-RPC endpoint path
|
|
247
|
-
headers: {
|
|
248
|
-
'Authorization' => 'Bearer YOUR_API_TOKEN',
|
|
249
|
-
'X-Custom-Header' => 'custom-value'
|
|
250
|
-
},
|
|
251
|
-
read_timeout: 30, # Timeout in seconds for HTTP requests
|
|
252
|
-
retries: 3, # Number of retry attempts on transient errors
|
|
253
|
-
retry_backoff: 1, # Base delay in seconds for exponential backoff
|
|
254
|
-
logger: logger # Optional logger for debugging HTTP requests
|
|
255
|
-
)
|
|
256
|
-
]
|
|
257
|
-
)
|
|
258
|
-
|
|
259
|
-
# List available tools
|
|
260
|
-
tools = http_client.list_tools
|
|
261
|
-
|
|
262
|
-
# Call a tool
|
|
263
|
-
result = http_client.call_tool('analyze_data', {
|
|
264
|
-
dataset: 'sales_2024',
|
|
265
|
-
metrics: ['revenue', 'conversion_rate']
|
|
266
|
-
})
|
|
267
|
-
|
|
268
|
-
# HTTP transport also supports streaming (though implemented as single response)
|
|
269
|
-
# This provides API compatibility with SSE transport
|
|
270
|
-
http_client.call_tool_streaming('process_batch', { batch_id: 123 }).each do |result|
|
|
271
|
-
puts "Processing result: #{result}"
|
|
272
|
-
end
|
|
273
|
-
|
|
274
|
-
# Send custom JSON-RPC requests
|
|
275
|
-
custom_result = http_client.send_rpc('custom_method', params: { key: 'value' })
|
|
276
|
-
|
|
277
|
-
# Send notifications (fire-and-forget)
|
|
278
|
-
http_client.send_notification('status_update', params: { status: 'processing' })
|
|
279
|
-
|
|
280
|
-
# Test connectivity
|
|
281
|
-
ping_result = http_client.ping
|
|
282
|
-
puts "Server is responsive: #{ping_result.inspect}"
|
|
283
|
-
|
|
284
|
-
# Clean up
|
|
285
|
-
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
|
|
286
126
|
```
|
|
287
127
|
|
|
288
|
-
###
|
|
289
|
-
|
|
290
|
-
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
|
|
291
129
|
|
|
292
130
|
```ruby
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
# Optional logger for debugging
|
|
297
|
-
logger = Logger.new($stdout)
|
|
298
|
-
logger.level = Logger::INFO
|
|
299
|
-
|
|
300
|
-
# Create an MCP client that connects to a Streamable HTTP MCP server
|
|
301
|
-
streamable_client = MCPClient.create_client(
|
|
302
|
-
mcp_server_configs: [
|
|
303
|
-
MCPClient.streamable_http_config(
|
|
304
|
-
base_url: 'https://mcp.zapier.com/api/mcp/s/YOUR_SESSION_ID/mcp',
|
|
305
|
-
headers: {
|
|
306
|
-
'Authorization' => 'Bearer YOUR_ZAPIER_TOKEN'
|
|
307
|
-
},
|
|
308
|
-
read_timeout: 60, # Timeout in seconds for HTTP requests
|
|
309
|
-
retries: 3, # Number of retry attempts on transient errors
|
|
310
|
-
retry_backoff: 2, # Base delay in seconds for exponential backoff
|
|
311
|
-
logger: logger # Optional logger for debugging requests
|
|
312
|
-
)
|
|
313
|
-
]
|
|
314
|
-
)
|
|
315
|
-
|
|
316
|
-
# List available tools (server responds with SSE-formatted JSON)
|
|
317
|
-
tools = streamable_client.list_tools
|
|
318
|
-
puts "Found #{tools.size} tools:"
|
|
319
|
-
tools.each { |tool| puts "- #{tool.name}: #{tool.description}" }
|
|
320
|
-
|
|
321
|
-
# Call a tool (response will be in SSE format)
|
|
322
|
-
result = streamable_client.call_tool('google_calendar_find_event', {
|
|
323
|
-
instructions: 'Find today\'s meetings',
|
|
324
|
-
calendarid: 'primary'
|
|
325
|
-
})
|
|
326
|
-
|
|
327
|
-
# The client automatically parses SSE responses like:
|
|
328
|
-
# event: message
|
|
329
|
-
# data: {"jsonrpc":"2.0","id":1,"result":{"content":[...]}}
|
|
330
|
-
|
|
331
|
-
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
|
|
332
134
|
|
|
333
|
-
|
|
334
|
-
|
|
135
|
+
result = client.call_tool('get_weather', { location: 'SF' })
|
|
136
|
+
data = result['structuredContent'] # Type-safe structured data
|
|
335
137
|
```
|
|
336
138
|
|
|
337
|
-
###
|
|
338
|
-
|
|
339
|
-
The SSE transport provides robust connection handling for remote MCP servers:
|
|
139
|
+
### Roots
|
|
340
140
|
|
|
341
141
|
```ruby
|
|
342
|
-
|
|
343
|
-
|
|
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
|
+
```
|
|
344
151
|
|
|
345
|
-
|
|
346
|
-
logger = Logger.new($stdout)
|
|
347
|
-
logger.level = Logger::INFO
|
|
152
|
+
### Sampling (Server-requested LLM completions)
|
|
348
153
|
|
|
349
|
-
|
|
350
|
-
#
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
]
|
|
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
|
+
}
|
|
362
166
|
)
|
|
363
|
-
|
|
364
|
-
# List available tools
|
|
365
|
-
tools = mcp_client.list_tools
|
|
366
|
-
|
|
367
|
-
# Launch a browser
|
|
368
|
-
result = mcp_client.call_tool('browser_install', {})
|
|
369
|
-
result = mcp_client.call_tool('browser_navigate', { url: 'about:blank' })
|
|
370
|
-
# No browser ID needed with these tool names
|
|
371
|
-
|
|
372
|
-
# Create a new page
|
|
373
|
-
page_result = mcp_client.call_tool('browser_tab', {action: 'create'})
|
|
374
|
-
# No page ID needed with these tool names
|
|
375
|
-
|
|
376
|
-
# Navigate to a website
|
|
377
|
-
mcp_client.call_tool('browser_navigate', { url: 'https://example.com' })
|
|
378
|
-
|
|
379
|
-
# Get page title
|
|
380
|
-
title_result = mcp_client.call_tool('browser_snapshot', {})
|
|
381
|
-
puts "Page snapshot: #{title_result}"
|
|
382
|
-
|
|
383
|
-
# Take a screenshot
|
|
384
|
-
screenshot_result = mcp_client.call_tool('browser_take_screenshot', {})
|
|
385
|
-
|
|
386
|
-
# Ping the server to verify connectivity
|
|
387
|
-
ping_result = mcp_client.ping
|
|
388
|
-
puts "Ping successful: #{ping_result.inspect}"
|
|
389
|
-
|
|
390
|
-
# Clean up
|
|
391
|
-
mcp_client.cleanup
|
|
392
167
|
```
|
|
393
168
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
### FastMCP Example
|
|
397
|
-
|
|
398
|
-
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:
|
|
399
|
-
|
|
400
|
-
#### Basic FastMCP Example
|
|
401
|
-
|
|
402
|
-
**For FastMCP server with SSE transport (includes tools, prompts, and resources):**
|
|
403
|
-
```bash
|
|
404
|
-
# From the ruby-mcp-client directory
|
|
405
|
-
python examples/echo_server.py
|
|
406
|
-
```
|
|
169
|
+
### Completion (Autocomplete)
|
|
407
170
|
|
|
408
171
|
```ruby
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
require 'mcp_client'
|
|
414
|
-
|
|
415
|
-
# Connect to FastMCP server
|
|
416
|
-
client = MCPClient.create_client(
|
|
417
|
-
mcp_server_configs: [
|
|
418
|
-
MCPClient.sse_config(
|
|
419
|
-
base_url: 'http://127.0.0.1:8000/sse',
|
|
420
|
-
read_timeout: 30
|
|
421
|
-
)
|
|
422
|
-
]
|
|
172
|
+
result = client.complete(
|
|
173
|
+
ref: { type: 'ref/prompt', name: 'greeting' },
|
|
174
|
+
argument: { name: 'name', value: 'A' }
|
|
423
175
|
)
|
|
176
|
+
# => { 'values' => ['Alice', 'Alex'], 'total' => 100, 'hasMore' => true }
|
|
177
|
+
```
|
|
424
178
|
|
|
425
|
-
|
|
426
|
-
tools = client.list_tools
|
|
427
|
-
puts "Found #{tools.length} tools:"
|
|
428
|
-
tools.each { |tool| puts "- #{tool.name}: #{tool.description}" }
|
|
429
|
-
|
|
430
|
-
result = client.call_tool('echo', { message: 'Hello FastMCP!' })
|
|
431
|
-
|
|
432
|
-
# List and use prompts
|
|
433
|
-
prompts = client.list_prompts
|
|
434
|
-
puts "Found #{prompts.length} prompts:"
|
|
435
|
-
prompts.each { |prompt| puts "- #{prompt.name}: #{prompt.description}" }
|
|
436
|
-
|
|
437
|
-
greeting = client.get_prompt('greeting', { name: 'Ruby Developer' })
|
|
179
|
+
### Logging
|
|
438
180
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
puts "Found #{resources.length} resources:"
|
|
443
|
-
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
|
|
444
184
|
|
|
445
|
-
#
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
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
|
|
449
190
|
end
|
|
450
191
|
```
|
|
451
192
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
**For FastMCP server with Streamable HTTP transport (includes tools, prompts, and resources):**
|
|
455
|
-
```bash
|
|
456
|
-
# From the ruby-mcp-client directory
|
|
457
|
-
python examples/echo_server_streamable.py
|
|
458
|
-
```
|
|
193
|
+
### Elicitation (Server-initiated user interactions)
|
|
459
194
|
|
|
460
195
|
```ruby
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
client = MCPClient.create_client(
|
|
469
|
-
mcp_server_configs: [
|
|
470
|
-
MCPClient.streamable_http_config(
|
|
471
|
-
base_url: 'http://localhost:8931/mcp',
|
|
472
|
-
read_timeout: 60
|
|
473
|
-
)
|
|
474
|
-
]
|
|
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
|
+
}
|
|
475
203
|
)
|
|
476
|
-
|
|
477
|
-
# Full protocol support including real-time notifications
|
|
478
|
-
client.on_notification do |method, params|
|
|
479
|
-
puts "Server notification: #{method} - #{params}"
|
|
480
|
-
end
|
|
481
|
-
|
|
482
|
-
# Use all MCP features: tools, prompts, resources
|
|
483
|
-
tools = client.list_tools
|
|
484
|
-
prompts = client.list_prompts
|
|
485
|
-
resources = client.list_resources
|
|
486
|
-
|
|
487
|
-
# Real-time progress notifications for long-running tasks
|
|
488
|
-
result = client.call_tool('long_task', { duration: 5, steps: 5 })
|
|
489
|
-
|
|
490
|
-
# Use the tools
|
|
491
|
-
result = client.call_tool('echo', { message: 'Hello from Ruby!' })
|
|
492
|
-
result = client.call_tool('reverse', { text: 'FastMCP rocks!' })
|
|
493
|
-
|
|
494
|
-
client.cleanup
|
|
495
204
|
```
|
|
496
205
|
|
|
497
|
-
|
|
498
|
-
- **`echo_server.py`** - A Python FastMCP server with tools, prompts, and resources
|
|
499
|
-
- **`echo_server_client.rb`** - Ruby client demonstrating all features including prompts and resources
|
|
500
|
-
- **`echo_server_streamable.py`** - Enhanced streamable HTTP server with tools, prompts, and resources
|
|
501
|
-
- **`echo_server_streamable_client.rb`** - Ruby client demonstrating streamable HTTP transport
|
|
502
|
-
- **`README_ECHO_SERVER.md`** - Complete setup and usage instructions
|
|
206
|
+
## Advanced Configuration
|
|
503
207
|
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
### Integration Examples
|
|
507
|
-
|
|
508
|
-
The repository includes examples for integrating with popular AI APIs:
|
|
509
|
-
|
|
510
|
-
#### OpenAI Integration
|
|
511
|
-
|
|
512
|
-
Ruby-MCP-Client works with both official and community OpenAI gems:
|
|
208
|
+
For more control, use `create_client` with explicit configs:
|
|
513
209
|
|
|
514
210
|
```ruby
|
|
515
|
-
|
|
516
|
-
require 'mcp_client'
|
|
517
|
-
require 'openai'
|
|
518
|
-
|
|
519
|
-
# Create MCP client
|
|
520
|
-
mcp_client = MCPClient.create_client(
|
|
211
|
+
client = MCPClient.create_client(
|
|
521
212
|
mcp_server_configs: [
|
|
522
|
-
MCPClient.stdio_config(
|
|
523
|
-
|
|
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
|
|
524
227
|
)
|
|
525
|
-
]
|
|
526
|
-
)
|
|
527
|
-
|
|
528
|
-
# Convert tools to OpenAI format
|
|
529
|
-
tools = mcp_client.to_openai_tools
|
|
530
|
-
|
|
531
|
-
# Use with OpenAI client
|
|
532
|
-
client = OpenAI::Client.new(api_key: ENV['OPENAI_API_KEY'])
|
|
533
|
-
response = client.chat.completions.create(
|
|
534
|
-
model: 'gpt-4',
|
|
535
|
-
messages: [
|
|
536
|
-
{ role: 'user', content: 'List files in current directory' }
|
|
537
228
|
],
|
|
538
|
-
|
|
539
|
-
)
|
|
540
|
-
|
|
541
|
-
# Process tool calls and results
|
|
542
|
-
# See examples directory for complete implementation
|
|
543
|
-
```
|
|
544
|
-
|
|
545
|
-
```ruby
|
|
546
|
-
# Using the alexrudall/ruby-openai gem (community)
|
|
547
|
-
require 'mcp_client'
|
|
548
|
-
require 'openai'
|
|
549
|
-
|
|
550
|
-
# Create MCP client
|
|
551
|
-
mcp_client = MCPClient.create_client(
|
|
552
|
-
mcp_server_configs: [
|
|
553
|
-
MCPClient.stdio_config(command: 'npx @playwright/mcp@latest')
|
|
554
|
-
]
|
|
229
|
+
logger: Logger.new($stdout)
|
|
555
230
|
)
|
|
556
231
|
|
|
557
|
-
#
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
# Use with Ruby-OpenAI client
|
|
561
|
-
client = OpenAI::Client.new(access_token: ENV['OPENAI_API_KEY'])
|
|
562
|
-
# See examples directory for complete implementation
|
|
232
|
+
# Or load from JSON file
|
|
233
|
+
client = MCPClient.create_client(server_definition_file: 'servers.json')
|
|
563
234
|
```
|
|
564
235
|
|
|
565
|
-
|
|
236
|
+
### Faraday Customization
|
|
566
237
|
|
|
567
238
|
```ruby
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
mcp_client = MCPClient.create_client(
|
|
573
|
-
mcp_server_configs: [
|
|
574
|
-
MCPClient.stdio_config(
|
|
575
|
-
command: %W[npx -y @modelcontextprotocol/server-filesystem #{Dir.pwd}]
|
|
576
|
-
)
|
|
577
|
-
]
|
|
578
|
-
)
|
|
579
|
-
|
|
580
|
-
# Convert tools to Anthropic format
|
|
581
|
-
claude_tools = mcp_client.to_anthropic_tools
|
|
582
|
-
|
|
583
|
-
# Use with Anthropic client
|
|
584
|
-
client = Anthropic::Client.new(access_token: ENV['ANTHROPIC_API_KEY'])
|
|
585
|
-
# 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
|
|
586
243
|
```
|
|
587
244
|
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
**AI Integration Examples:**
|
|
591
|
-
- `ruby_openai_mcp.rb` - Integration with alexrudall/ruby-openai gem
|
|
592
|
-
- `openai_ruby_mcp.rb` - Integration with official openai/openai-ruby gem
|
|
593
|
-
- `ruby_anthropic_mcp.rb` - Integration with alexrudall/ruby-anthropic gem
|
|
594
|
-
- `gemini_ai_mcp.rb` - Integration with Google Vertex AI and Gemini models
|
|
595
|
-
|
|
596
|
-
**Transport Examples:**
|
|
597
|
-
- `streamable_http_example.rb` - Streamable HTTP transport with Playwright MCP
|
|
598
|
-
- `echo_server.py` & `echo_server_client.rb` - FastMCP server example with full setup
|
|
599
|
-
- `echo_server_streamable.py` & `echo_server_streamable_client.rb` - Enhanced streamable HTTP server example
|
|
600
|
-
|
|
601
|
-
**MCP 2025 Protocol Features:**
|
|
602
|
-
- `structured_output_server.py` & `test_structured_outputs.rb` - Structured tool outputs (MCP 2025-06-18)
|
|
603
|
-
- `echo_server_with_annotations.py` & `test_tool_annotations.rb` - Tool annotations (MCP 2025-03-26)
|
|
604
|
-
|
|
605
|
-
## MCP Server Compatibility
|
|
606
|
-
|
|
607
|
-
This client works with any MCP-compatible server, including:
|
|
608
|
-
|
|
609
|
-
- [@modelcontextprotocol/server-filesystem](https://www.npmjs.com/package/@modelcontextprotocol/server-filesystem) - File system access
|
|
610
|
-
- [@playwright/mcp](https://www.npmjs.com/package/@playwright/mcp) - Browser automation
|
|
611
|
-
- [FastMCP](https://github.com/jlowin/fastmcp) - Python framework for building MCP servers
|
|
612
|
-
- Custom servers implementing the MCP protocol
|
|
613
|
-
|
|
614
|
-
### Server Definition Files
|
|
615
|
-
|
|
616
|
-
You can define MCP server configurations in JSON files for easier management:
|
|
245
|
+
### Server Definition JSON
|
|
617
246
|
|
|
618
247
|
```json
|
|
619
248
|
{
|
|
620
249
|
"mcpServers": {
|
|
621
|
-
"playwright": {
|
|
622
|
-
"type": "sse",
|
|
623
|
-
"url": "http://localhost:8931/mcp",
|
|
624
|
-
"headers": {
|
|
625
|
-
"Authorization": "Bearer TOKEN"
|
|
626
|
-
}
|
|
627
|
-
},
|
|
628
|
-
"api_server": {
|
|
629
|
-
"type": "http",
|
|
630
|
-
"url": "https://api.example.com",
|
|
631
|
-
"endpoint": "/mcp",
|
|
632
|
-
"headers": {
|
|
633
|
-
"Authorization": "Bearer API_TOKEN",
|
|
634
|
-
"X-Custom-Header": "value"
|
|
635
|
-
}
|
|
636
|
-
},
|
|
637
250
|
"filesystem": {
|
|
638
251
|
"type": "stdio",
|
|
639
252
|
"command": "npx",
|
|
640
|
-
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
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" }
|
|
644
259
|
}
|
|
645
260
|
}
|
|
646
261
|
}
|
|
647
262
|
```
|
|
648
263
|
|
|
649
|
-
|
|
264
|
+
## AI Integration Examples
|
|
650
265
|
|
|
651
|
-
|
|
652
|
-
{
|
|
653
|
-
"mcpServers": {
|
|
654
|
-
"playwright": {
|
|
655
|
-
"url": "http://localhost:8931/mcp",
|
|
656
|
-
"headers": {},
|
|
657
|
-
"comment": "Local Playwright MCP Server running on port 8931"
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
```
|
|
662
|
-
|
|
663
|
-
Load this configuration with:
|
|
266
|
+
### OpenAI
|
|
664
267
|
|
|
665
268
|
```ruby
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
The JSON format supports:
|
|
670
|
-
1. A single server object: `{ "type": "sse", "url": "..." }` or `{ "type": "http", "url": "..." }`
|
|
671
|
-
2. An array of server objects: `[{ "type": "stdio", ... }, { "type": "sse", ... }, { "type": "http", ... }]`
|
|
672
|
-
3. An object with named servers under `mcpServers` key (as shown above)
|
|
673
|
-
|
|
674
|
-
Special configuration options:
|
|
675
|
-
- `comment` and `description` are reserved keys that are ignored during parsing and can be used for documentation
|
|
676
|
-
- Server type can be inferred from the presence of either `command` (for stdio) or `url` (for SSE/HTTP)
|
|
677
|
-
- For HTTP servers, `endpoint` specifies the JSON-RPC endpoint path (defaults to '/rpc' if not specified)
|
|
678
|
-
- All string values in arrays (like `args`) are automatically converted to strings
|
|
679
|
-
|
|
680
|
-
## Session-Based MCP Protocol Support
|
|
681
|
-
|
|
682
|
-
Both HTTP and Streamable HTTP transports now support session-based MCP servers that require session continuity:
|
|
683
|
-
|
|
684
|
-
### Session Management Features
|
|
685
|
-
|
|
686
|
-
- **Automatic Session Management**: Captures session IDs from `initialize` response headers
|
|
687
|
-
- **Session Header Injection**: Automatically includes `Mcp-Session-Id` header in subsequent requests
|
|
688
|
-
- **Session Termination**: Sends HTTP DELETE requests to properly terminate sessions during cleanup
|
|
689
|
-
- **Session Validation**: Validates session ID format for security (8-128 alphanumeric characters with hyphens/underscores)
|
|
690
|
-
- **Backward Compatibility**: Works with both session-based and stateless MCP servers
|
|
691
|
-
- **Session Cleanup**: Properly cleans up session state during connection teardown
|
|
692
|
-
|
|
693
|
-
### Resumability and Redelivery (Streamable HTTP)
|
|
694
|
-
|
|
695
|
-
The Streamable HTTP transport provides additional resumability features for reliable message delivery:
|
|
696
|
-
|
|
697
|
-
- **Event ID Tracking**: Automatically tracks event IDs from SSE responses
|
|
698
|
-
- **Last-Event-ID Header**: Includes `Last-Event-ID` header in requests for resuming from disconnection points
|
|
699
|
-
- **Message Replay**: Enables servers to replay missed messages from the last received event
|
|
700
|
-
- **Connection Recovery**: Maintains message continuity even with unstable network connections
|
|
701
|
-
|
|
702
|
-
### Security Features
|
|
703
|
-
|
|
704
|
-
Both transports implement security best practices:
|
|
705
|
-
|
|
706
|
-
- **URL Validation**: Validates server URLs to ensure only HTTP/HTTPS protocols are used
|
|
707
|
-
- **Session ID Validation**: Enforces secure session ID formats to prevent malicious injection
|
|
708
|
-
- **Security Warnings**: Logs warnings for potentially insecure configurations (e.g., 0.0.0.0 binding)
|
|
709
|
-
- **Header Sanitization**: Properly handles and validates all session-related headers
|
|
710
|
-
|
|
711
|
-
### Usage
|
|
712
|
-
|
|
713
|
-
The session support is transparent to the user - no additional configuration is required. The client will automatically detect and handle session-based servers by:
|
|
714
|
-
|
|
715
|
-
1. **Session Initialization**: Capturing the `Mcp-Session-Id` header from the `initialize` response
|
|
716
|
-
2. **Session Persistence**: Including this header in all subsequent requests (except `initialize`)
|
|
717
|
-
3. **Session Termination**: Sending HTTP DELETE request with session ID during cleanup
|
|
718
|
-
4. **Resumability** (Streamable HTTP): Tracking event IDs and including `Last-Event-ID` for message replay
|
|
719
|
-
5. **Security Validation**: Validating session IDs and server URLs for security
|
|
720
|
-
6. **Logging**: Comprehensive logging of session activity for debugging purposes
|
|
269
|
+
require 'mcp_client'
|
|
270
|
+
require 'openai'
|
|
721
271
|
|
|
722
|
-
|
|
272
|
+
mcp = MCPClient.connect('npx -y @modelcontextprotocol/server-filesystem .')
|
|
273
|
+
tools = mcp.to_openai_tools
|
|
723
274
|
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
]
|
|
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
|
|
730
280
|
)
|
|
731
|
-
|
|
732
|
-
# Use the client...
|
|
733
|
-
tools = client.list_tools
|
|
734
|
-
|
|
735
|
-
# Session automatically terminated with HTTP DELETE request
|
|
736
|
-
client.cleanup
|
|
737
281
|
```
|
|
738
282
|
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
## OAuth 2.1 Authentication
|
|
742
|
-
|
|
743
|
-
The Ruby MCP Client includes comprehensive OAuth 2.1 support for secure authentication with MCP servers:
|
|
283
|
+
### Anthropic
|
|
744
284
|
|
|
745
285
|
```ruby
|
|
746
286
|
require 'mcp_client'
|
|
287
|
+
require 'anthropic'
|
|
747
288
|
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
server_url: 'https://api.example.com/mcp',
|
|
751
|
-
redirect_uri: 'http://localhost:8080/callback',
|
|
752
|
-
scope: 'mcp:read mcp:write'
|
|
753
|
-
)
|
|
754
|
-
|
|
755
|
-
# Check if authorization is needed
|
|
756
|
-
unless MCPClient::OAuthClient.valid_token?(server)
|
|
757
|
-
# Start OAuth flow
|
|
758
|
-
auth_url = MCPClient::OAuthClient.start_oauth_flow(server)
|
|
759
|
-
puts "Please visit: #{auth_url}"
|
|
760
|
-
|
|
761
|
-
# After user authorization, complete the flow
|
|
762
|
-
# token = MCPClient::OAuthClient.complete_oauth_flow(server, code, state)
|
|
763
|
-
end
|
|
764
|
-
|
|
765
|
-
# Use the server normally
|
|
766
|
-
server.connect
|
|
767
|
-
tools = server.list_tools
|
|
768
|
-
```
|
|
769
|
-
|
|
770
|
-
### Manual OAuth Provider
|
|
771
|
-
|
|
772
|
-
For more control over the OAuth flow:
|
|
773
|
-
|
|
774
|
-
```ruby
|
|
775
|
-
# Create OAuth provider directly
|
|
776
|
-
oauth_provider = MCPClient::Auth::OAuthProvider.new(
|
|
777
|
-
server_url: 'https://api.example.com/mcp',
|
|
778
|
-
redirect_uri: 'http://localhost:8080/callback',
|
|
779
|
-
scope: 'mcp:read mcp:write'
|
|
780
|
-
)
|
|
781
|
-
|
|
782
|
-
# Update configuration at runtime
|
|
783
|
-
oauth_provider.scope = 'mcp:read mcp:write admin'
|
|
784
|
-
oauth_provider.redirect_uri = 'http://localhost:9000/callback'
|
|
785
|
-
|
|
786
|
-
# Start authorization flow
|
|
787
|
-
auth_url = oauth_provider.start_authorization_flow
|
|
289
|
+
mcp = MCPClient.connect('npx -y @modelcontextprotocol/server-filesystem .')
|
|
290
|
+
tools = mcp.to_anthropic_tools
|
|
788
291
|
|
|
789
|
-
|
|
790
|
-
|
|
292
|
+
client = Anthropic::Client.new(access_token: ENV['ANTHROPIC_API_KEY'])
|
|
293
|
+
# Use tools with Claude API
|
|
791
294
|
```
|
|
792
295
|
|
|
793
|
-
|
|
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
|
|
794
300
|
|
|
795
|
-
|
|
301
|
+
## OAuth 2.1 Authentication
|
|
796
302
|
|
|
797
303
|
```ruby
|
|
798
304
|
require 'mcp_client/auth/browser_oauth'
|
|
799
305
|
|
|
800
|
-
|
|
801
|
-
oauth_provider = MCPClient::Auth::OAuthProvider.new(
|
|
306
|
+
oauth = MCPClient::Auth::OAuthProvider.new(
|
|
802
307
|
server_url: 'https://api.example.com/mcp',
|
|
803
308
|
redirect_uri: 'http://localhost:8080/callback',
|
|
804
|
-
scope: 'read:
|
|
309
|
+
scope: 'mcp:read mcp:write'
|
|
805
310
|
)
|
|
806
311
|
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
oauth_provider,
|
|
810
|
-
callback_port: 8080, # Optional: Port for local server (default: 8080)
|
|
811
|
-
callback_path: '/callback' # Optional: Callback path (default: '/callback')
|
|
812
|
-
)
|
|
312
|
+
browser_oauth = MCPClient::Auth::BrowserOAuth.new(oauth)
|
|
313
|
+
token = browser_oauth.authenticate # Opens browser, handles callback
|
|
813
314
|
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
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
|
+
}]
|
|
818
321
|
)
|
|
819
|
-
|
|
820
|
-
# Create authenticated client (use streamable_http for modern MCP servers)
|
|
821
|
-
server_config = {
|
|
822
|
-
type: 'streamable_http', # or 'http' for simple HTTP-only servers
|
|
823
|
-
base_url: 'https://api.example.com/mcp',
|
|
824
|
-
oauth_provider: oauth_provider
|
|
825
|
-
}
|
|
826
|
-
|
|
827
|
-
client = MCPClient::Client.new(mcp_server_configs: [server_config])
|
|
828
|
-
```
|
|
829
|
-
|
|
830
|
-
**How it works:**
|
|
831
|
-
1. **Automatically starts a local HTTP server** (using pure Ruby `TCPServer`) to handle the OAuth callback
|
|
832
|
-
2. **Opens your default browser** to the authorization page (macOS: `open`, Linux: `xdg-open`, Windows: `start`)
|
|
833
|
-
3. **Captures the authorization code** automatically from the callback
|
|
834
|
-
4. **Completes the OAuth flow** and stores the access token
|
|
835
|
-
5. **Handles token refresh** automatically when tokens expire
|
|
836
|
-
|
|
837
|
-
**Features:**
|
|
838
|
-
- **Zero external dependencies** - Uses pure Ruby stdlib (`TCPServer`)
|
|
839
|
-
- **User-friendly HTML pages** - Shows success/error pages in the browser
|
|
840
|
-
- **Automatic token management** - Handles token refresh transparently
|
|
841
|
-
- **Configurable timeout** - Raises `Timeout::Error` if user doesn't authorize in time
|
|
842
|
-
- **Token storage** - Supports custom storage backends for persistent tokens
|
|
843
|
-
- **Port conflict detection** - Clear error messages if port is already in use
|
|
844
|
-
- **Security hardened** - PKCE, CSRF protection, request validation, header limits
|
|
845
|
-
|
|
846
|
-
**Error Handling:**
|
|
847
|
-
```ruby
|
|
848
|
-
begin
|
|
849
|
-
token = browser_oauth.authenticate
|
|
850
|
-
rescue Timeout::Error
|
|
851
|
-
puts "User took too long to authorize"
|
|
852
|
-
rescue MCPClient::Errors::ConnectionError => e
|
|
853
|
-
puts "OAuth flow failed: #{e.message}"
|
|
854
|
-
# Port already in use, server doesn't support OAuth, etc.
|
|
855
|
-
rescue ArgumentError => e
|
|
856
|
-
puts "Invalid state parameter (CSRF protection)"
|
|
857
|
-
end
|
|
858
322
|
```
|
|
859
323
|
|
|
860
|
-
|
|
861
|
-
- OAuth 2.1 Protocol with PKCE
|
|
862
|
-
- Authorization Server Discovery via:
|
|
863
|
-
- `/.well-known/oauth-authorization-server` (primary, for self-contained servers)
|
|
864
|
-
- `/.well-known/oauth-protected-resource` (fallback, for delegated auth)
|
|
865
|
-
- Dynamic Client Registration (recommended)
|
|
866
|
-
- Authorization Code Grant Flow
|
|
867
|
-
|
|
868
|
-
For a complete working example, see [`examples/oauth_browser_auth.rb`](examples/oauth_browser_auth.rb).
|
|
324
|
+
Features: PKCE, server discovery (`.well-known`), dynamic registration, token refresh.
|
|
869
325
|
|
|
870
|
-
|
|
326
|
+
See [OAUTH.md](OAUTH.md) for full documentation.
|
|
871
327
|
|
|
872
|
-
|
|
873
|
-
- **Automatic server discovery** via `.well-known` endpoints
|
|
874
|
-
- **Dynamic client registration** when supported by servers
|
|
875
|
-
- **Token refresh** and automatic token management
|
|
876
|
-
- **Pluggable storage** for tokens and client credentials
|
|
877
|
-
- **Runtime configuration** via getter/setter methods
|
|
878
|
-
- **Browser-based authentication** with automatic callback handling
|
|
879
|
-
|
|
880
|
-
For complete OAuth documentation, see [OAUTH.md](OAUTH.md).
|
|
881
|
-
|
|
882
|
-
## Resources
|
|
883
|
-
|
|
884
|
-
The Ruby MCP Client provides full support for the MCP resources specification, enabling access to files, data, and other content with URI-based identification.
|
|
885
|
-
|
|
886
|
-
### Resource Features
|
|
887
|
-
|
|
888
|
-
- **Resource listing** with cursor-based pagination
|
|
889
|
-
- **Resource templates** with URI patterns (RFC 6570)
|
|
890
|
-
- **Resource subscriptions** for real-time updates
|
|
891
|
-
- **Binary content support** with base64 encoding
|
|
892
|
-
- **Resource annotations** for metadata (audience, priority, lastModified)
|
|
893
|
-
- **ResourceContent objects** for structured content access
|
|
894
|
-
|
|
895
|
-
### Resource API
|
|
328
|
+
## Server Notifications
|
|
896
329
|
|
|
897
330
|
```ruby
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
#
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
# Get next page of resources
|
|
907
|
-
if next_cursor
|
|
908
|
-
next_result = server.list_resources(cursor: next_cursor)
|
|
909
|
-
end
|
|
910
|
-
|
|
911
|
-
# Access Resource properties
|
|
912
|
-
resource = resources.first
|
|
913
|
-
resource.uri # "file:///example.txt"
|
|
914
|
-
resource.name # "example.txt"
|
|
915
|
-
resource.title # Optional human-readable title
|
|
916
|
-
resource.description # Optional description
|
|
917
|
-
resource.mime_type # "text/plain"
|
|
918
|
-
resource.size # Optional file size in bytes
|
|
919
|
-
resource.annotations # Optional metadata hash
|
|
920
|
-
|
|
921
|
-
# Read resource contents (returns array of ResourceContent objects)
|
|
922
|
-
contents = server.read_resource(resource.uri)
|
|
923
|
-
|
|
924
|
-
contents.each do |content|
|
|
925
|
-
# Check content type
|
|
926
|
-
if content.text?
|
|
927
|
-
# Text content
|
|
928
|
-
text = content.text
|
|
929
|
-
mime = content.mime_type # e.g., "text/plain"
|
|
930
|
-
elsif content.binary?
|
|
931
|
-
# Binary content (base64 encoded)
|
|
932
|
-
blob = content.blob # Base64 string
|
|
933
|
-
data = Base64.decode64(blob) # Decoded binary data
|
|
934
|
-
end
|
|
935
|
-
|
|
936
|
-
# Access optional annotations
|
|
937
|
-
if content.annotations
|
|
938
|
-
audience = content.annotations['audience'] # e.g., ["user", "assistant"]
|
|
939
|
-
priority = content.annotations['priority'] # e.g., 0.5
|
|
940
|
-
modified = content.annotations['lastModified'] # ISO 8601 timestamp
|
|
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"
|
|
941
339
|
end
|
|
942
340
|
end
|
|
943
|
-
|
|
944
|
-
# List resource templates
|
|
945
|
-
templates_result = server.list_resource_templates
|
|
946
|
-
templates = templates_result['resourceTemplates'] # Array of ResourceTemplate objects
|
|
947
|
-
|
|
948
|
-
template = templates.first
|
|
949
|
-
template.uri_template # "file:///{path}" (RFC 6570 URI template)
|
|
950
|
-
template.name # Template name
|
|
951
|
-
template.title # Optional title
|
|
952
|
-
template.description # Optional description
|
|
953
|
-
template.mime_type # Optional MIME type hint
|
|
954
|
-
|
|
955
|
-
# Subscribe to resource updates
|
|
956
|
-
server.subscribe_resource('file:///watched.txt') # Returns true on success
|
|
957
|
-
|
|
958
|
-
# Unsubscribe from resource updates
|
|
959
|
-
server.unsubscribe_resource('file:///watched.txt') # Returns true on success
|
|
960
|
-
|
|
961
|
-
# Check server capabilities for resources
|
|
962
|
-
capabilities = server.capabilities
|
|
963
|
-
if capabilities['resources']
|
|
964
|
-
can_subscribe = capabilities['resources']['subscribe'] # true/false
|
|
965
|
-
list_changed = capabilities['resources']['listChanged'] # true/false
|
|
966
|
-
end
|
|
967
|
-
```
|
|
968
|
-
|
|
969
|
-
### Working with ResourceContent
|
|
970
|
-
|
|
971
|
-
The `ResourceContent` class provides a structured way to handle both text and binary content:
|
|
972
|
-
|
|
973
|
-
```ruby
|
|
974
|
-
# ResourceContent for text
|
|
975
|
-
content = MCPClient::ResourceContent.new(
|
|
976
|
-
uri: 'file:///example.txt',
|
|
977
|
-
name: 'example.txt',
|
|
978
|
-
mime_type: 'text/plain',
|
|
979
|
-
text: 'File contents here',
|
|
980
|
-
annotations: {
|
|
981
|
-
'audience' => ['user'],
|
|
982
|
-
'priority' => 1.0
|
|
983
|
-
}
|
|
984
|
-
)
|
|
985
|
-
|
|
986
|
-
# ResourceContent for binary data
|
|
987
|
-
binary_content = MCPClient::ResourceContent.new(
|
|
988
|
-
uri: 'file:///image.png',
|
|
989
|
-
name: 'image.png',
|
|
990
|
-
mime_type: 'image/png',
|
|
991
|
-
blob: Base64.strict_encode64(binary_data)
|
|
992
|
-
)
|
|
993
|
-
|
|
994
|
-
# Access content
|
|
995
|
-
if content.text?
|
|
996
|
-
puts content.text
|
|
997
|
-
elsif content.binary?
|
|
998
|
-
data = Base64.decode64(content.blob)
|
|
999
|
-
end
|
|
1000
341
|
```
|
|
1001
342
|
|
|
1002
|
-
##
|
|
1003
|
-
|
|
1004
|
-
MCP 2025-06-18 supports tool annotations that describe tool behavior, enabling safer and more informed tool execution. The Ruby MCP Client provides full support for reading and interpreting these annotations.
|
|
343
|
+
## Session Management
|
|
1005
344
|
|
|
1006
|
-
|
|
345
|
+
Both HTTP and Streamable HTTP transports automatically handle session-based servers:
|
|
1007
346
|
|
|
1008
|
-
|
|
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
|
|
1009
351
|
|
|
1010
|
-
|
|
1011
|
-
- **`destructive`** - Indicates the tool performs potentially dangerous operations (e.g., deleting data)
|
|
1012
|
-
- **`requiresConfirmation`** - Indicates the tool should require explicit user confirmation before execution
|
|
352
|
+
No configuration required - works automatically.
|
|
1013
353
|
|
|
1014
|
-
|
|
354
|
+
## Server Compatibility
|
|
1015
355
|
|
|
1016
|
-
|
|
1017
|
-
# List tools and check their annotations
|
|
1018
|
-
tools = client.list_tools
|
|
356
|
+
Works with any MCP-compatible server:
|
|
1019
357
|
|
|
1020
|
-
|
|
1021
|
-
|
|
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
|
|
1022
362
|
|
|
1023
|
-
|
|
1024
|
-
if tool.read_only?
|
|
1025
|
-
puts " → Safe to execute (read-only)"
|
|
1026
|
-
end
|
|
1027
|
-
|
|
1028
|
-
if tool.destructive?
|
|
1029
|
-
puts " ⚠️ Warning: This tool is destructive"
|
|
1030
|
-
end
|
|
1031
|
-
|
|
1032
|
-
if tool.requires_confirmation?
|
|
1033
|
-
puts " 🛡️ Requires user confirmation before execution"
|
|
1034
|
-
end
|
|
1035
|
-
|
|
1036
|
-
# Access raw annotations hash
|
|
1037
|
-
if tool.annotations
|
|
1038
|
-
puts " Annotations: #{tool.annotations.inspect}"
|
|
1039
|
-
end
|
|
1040
|
-
end
|
|
363
|
+
### FastMCP Example
|
|
1041
364
|
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
# Prompt user for confirmation before executing
|
|
1046
|
-
puts "This will delete user data. Are you sure? (y/n)"
|
|
1047
|
-
response = gets.chomp
|
|
1048
|
-
if response.downcase == 'y'
|
|
1049
|
-
result = client.call_tool('delete_user', { user_id: 123 })
|
|
1050
|
-
else
|
|
1051
|
-
puts "Operation cancelled"
|
|
1052
|
-
end
|
|
1053
|
-
else
|
|
1054
|
-
# Safe to execute without confirmation
|
|
1055
|
-
result = client.call_tool('get_user', { user_id: 123 })
|
|
1056
|
-
end
|
|
365
|
+
```bash
|
|
366
|
+
# Start server
|
|
367
|
+
python examples/echo_server_streamable.py
|
|
1057
368
|
```
|
|
1058
369
|
|
|
1059
|
-
### Tool Annotation Helper Methods
|
|
1060
|
-
|
|
1061
|
-
The `Tool` class provides convenient helper methods:
|
|
1062
|
-
|
|
1063
|
-
- `tool.read_only?` - Returns true if the tool is marked as read-only
|
|
1064
|
-
- `tool.destructive?` - Returns true if the tool is marked as destructive
|
|
1065
|
-
- `tool.requires_confirmation?` - Returns true if the tool requires confirmation
|
|
1066
|
-
- `tool.annotations` - Returns the raw annotations hash
|
|
1067
|
-
|
|
1068
|
-
### Example
|
|
1069
|
-
|
|
1070
|
-
See `examples/test_tool_annotations.rb` for a complete working example demonstrating:
|
|
1071
|
-
- Listing tools with their annotations
|
|
1072
|
-
- Using annotation helper methods
|
|
1073
|
-
- Making annotation-aware decisions about tool execution
|
|
1074
|
-
- Handling destructive operations safely
|
|
1075
|
-
|
|
1076
|
-
## Structured Tool Outputs
|
|
1077
|
-
|
|
1078
|
-
MCP 2025-06-18 introduces structured tool outputs, allowing tools to declare output schemas and return type-safe, validated data structures instead of just text.
|
|
1079
|
-
|
|
1080
|
-
### Overview
|
|
1081
|
-
|
|
1082
|
-
Tools can now specify an `outputSchema` (JSON Schema) that defines the expected structure of their results. When called, they return data in a `structuredContent` field that validates against this schema, providing:
|
|
1083
|
-
|
|
1084
|
-
- **Type safety** - Predictable, validated data structures
|
|
1085
|
-
- **Better IDE support** - Code completion and type checking
|
|
1086
|
-
- **Easier parsing** - No need to parse text or guess formats
|
|
1087
|
-
- **Backward compatibility** - Text content is still provided alongside structured data
|
|
1088
|
-
|
|
1089
|
-
### Using Structured Outputs
|
|
1090
|
-
|
|
1091
370
|
```ruby
|
|
1092
|
-
#
|
|
371
|
+
# Connect and use
|
|
372
|
+
client = MCPClient.connect('http://localhost:8931/mcp')
|
|
1093
373
|
tools = client.list_tools
|
|
1094
|
-
|
|
1095
|
-
tools.each do |tool|
|
|
1096
|
-
if tool.structured_output?
|
|
1097
|
-
puts "#{tool.name} supports structured output"
|
|
1098
|
-
puts "Output schema: #{tool.output_schema}"
|
|
1099
|
-
end
|
|
1100
|
-
end
|
|
1101
|
-
|
|
1102
|
-
# Call a tool that returns structured output
|
|
1103
|
-
result = client.call_tool('get_weather', { location: 'San Francisco' })
|
|
1104
|
-
|
|
1105
|
-
# Access structured data (type-safe, validated)
|
|
1106
|
-
if result['structuredContent']
|
|
1107
|
-
data = result['structuredContent']
|
|
1108
|
-
puts "Temperature: #{data['temperature']}°C"
|
|
1109
|
-
puts "Conditions: #{data['conditions']}"
|
|
1110
|
-
puts "Humidity: #{data['humidity']}%"
|
|
1111
|
-
end
|
|
1112
|
-
|
|
1113
|
-
# Backward compatibility: text content is still available
|
|
1114
|
-
text_content = result['content']&.first&.dig('text')
|
|
1115
|
-
parsed = JSON.parse(text_content) if text_content
|
|
374
|
+
result = client.call_tool('echo', { message: 'Hello!' })
|
|
1116
375
|
```
|
|
1117
376
|
|
|
1118
|
-
### Helper Methods
|
|
1119
|
-
|
|
1120
|
-
The `Tool` class provides a convenient helper method:
|
|
1121
|
-
|
|
1122
|
-
- `tool.structured_output?` - Returns true if the tool has an output schema defined
|
|
1123
|
-
- `tool.output_schema` - Returns the JSON Schema for the tool's output
|
|
1124
|
-
|
|
1125
|
-
### Example
|
|
1126
|
-
|
|
1127
|
-
See `examples/test_structured_outputs.rb` for a complete working example demonstrating:
|
|
1128
|
-
- Detecting tools with structured output support
|
|
1129
|
-
- Accessing output schemas
|
|
1130
|
-
- Calling tools and receiving structured data
|
|
1131
|
-
- Backward compatibility with text content
|
|
1132
|
-
|
|
1133
|
-
The example includes a Python MCP server (`examples/structured_output_server.py`) that provides three tools with structured outputs:
|
|
1134
|
-
- `get_weather` - Weather data with temperature, conditions, humidity
|
|
1135
|
-
- `analyze_text` - Text analysis with word count, character count, statistics
|
|
1136
|
-
- `calculate_stats` - Statistical calculations (mean, median, min, max, etc.)
|
|
1137
|
-
|
|
1138
|
-
## Elicitation (Server-initiated User Interactions) - MCP 2025-06-18
|
|
1139
|
-
|
|
1140
|
-
Elicitation enables MCP servers to request user input during tool execution via bidirectional JSON-RPC.
|
|
1141
|
-
This allows servers to gather additional information, confirm sensitive operations, or interact with users
|
|
1142
|
-
dynamically during the execution of tools.
|
|
1143
|
-
|
|
1144
|
-
### Transport Support
|
|
1145
|
-
|
|
1146
|
-
**Fully Supported:**
|
|
1147
|
-
- ✅ **stdio** - Full bidirectional JSON-RPC over stdin/stdout
|
|
1148
|
-
- ✅ **SSE** - Server sends requests via SSE stream, client responds via HTTP POST
|
|
1149
|
-
- ✅ **Streamable HTTP** - Server sends requests via SSE-formatted responses, client responds via HTTP POST
|
|
1150
|
-
|
|
1151
|
-
**Not Supported:**
|
|
1152
|
-
- ❌ **HTTP** - Pure request-response architecture prevents server-initiated requests
|
|
1153
|
-
|
|
1154
|
-
### Using Elicitation
|
|
1155
|
-
|
|
1156
|
-
To use elicitation, register a handler when creating the client:
|
|
1157
|
-
|
|
1158
|
-
```ruby
|
|
1159
|
-
# Define an elicitation handler
|
|
1160
|
-
elicitation_handler = lambda do |message, requested_schema|
|
|
1161
|
-
puts "Server requests: #{message}"
|
|
1162
|
-
|
|
1163
|
-
# Show expected input format from schema
|
|
1164
|
-
if requested_schema && requested_schema['properties']
|
|
1165
|
-
requested_schema['properties'].each do |field, schema|
|
|
1166
|
-
puts " #{field}: #{schema['type']}"
|
|
1167
|
-
end
|
|
1168
|
-
end
|
|
1169
|
-
|
|
1170
|
-
# Prompt user and return one of three response types:
|
|
1171
|
-
|
|
1172
|
-
# 1. Accept and provide data to the server
|
|
1173
|
-
{
|
|
1174
|
-
'action' => 'accept',
|
|
1175
|
-
'content' => {
|
|
1176
|
-
'field_name' => 'user_input_value'
|
|
1177
|
-
}
|
|
1178
|
-
}
|
|
1179
|
-
|
|
1180
|
-
# 2. Decline to provide input
|
|
1181
|
-
# { 'action' => 'decline' }
|
|
1182
|
-
|
|
1183
|
-
# 3. Cancel the operation
|
|
1184
|
-
# { 'action' => 'cancel' }
|
|
1185
|
-
end
|
|
1186
|
-
|
|
1187
|
-
# Create client with elicitation handler
|
|
1188
|
-
# Works with stdio, SSE, and Streamable HTTP transports
|
|
1189
|
-
client = MCPClient::Client.new(
|
|
1190
|
-
mcp_server_configs: [
|
|
1191
|
-
# Stdio transport
|
|
1192
|
-
MCPClient.stdio_config(
|
|
1193
|
-
command: 'python my_server.py',
|
|
1194
|
-
name: 'my-server'
|
|
1195
|
-
),
|
|
1196
|
-
# Or SSE transport
|
|
1197
|
-
MCPClient.sse_config(
|
|
1198
|
-
base_url: 'https://api.example.com/sse',
|
|
1199
|
-
name: 'remote-server'
|
|
1200
|
-
),
|
|
1201
|
-
# Or Streamable HTTP transport
|
|
1202
|
-
MCPClient.streamable_http_config(
|
|
1203
|
-
base_url: 'https://api.example.com',
|
|
1204
|
-
endpoint: '/mcp',
|
|
1205
|
-
name: 'streamable-server'
|
|
1206
|
-
)
|
|
1207
|
-
],
|
|
1208
|
-
elicitation_handler: elicitation_handler
|
|
1209
|
-
)
|
|
1210
|
-
|
|
1211
|
-
# Call tools - connection happens automatically
|
|
1212
|
-
# Server may send elicitation requests during tool execution
|
|
1213
|
-
result = client.call_tool('create_document', { format: 'markdown' }, server: 'my-server')
|
|
1214
|
-
```
|
|
1215
|
-
|
|
1216
|
-
### Response Actions
|
|
1217
|
-
|
|
1218
|
-
The elicitation handler should return a hash with an `action` field:
|
|
1219
|
-
|
|
1220
|
-
1. **Accept** - Provide the requested input:
|
|
1221
|
-
```ruby
|
|
1222
|
-
{
|
|
1223
|
-
'action' => 'accept',
|
|
1224
|
-
'content' => { 'field' => 'value' } # Must match requested schema
|
|
1225
|
-
}
|
|
1226
|
-
```
|
|
1227
|
-
|
|
1228
|
-
2. **Decline** - Refuse to provide input (server should handle gracefully):
|
|
1229
|
-
```ruby
|
|
1230
|
-
{ 'action' => 'decline' }
|
|
1231
|
-
```
|
|
1232
|
-
|
|
1233
|
-
3. **Cancel** - Cancel the entire operation:
|
|
1234
|
-
```ruby
|
|
1235
|
-
{ 'action' => 'cancel' }
|
|
1236
|
-
```
|
|
1237
|
-
|
|
1238
|
-
### Example
|
|
1239
|
-
|
|
1240
|
-
See `examples/elicitation/test_elicitation.rb` for a complete working example demonstrating:
|
|
1241
|
-
- Registering an elicitation handler
|
|
1242
|
-
- Handling different message types and schemas
|
|
1243
|
-
- Responding with accept/decline/cancel actions
|
|
1244
|
-
- Interactive user prompts during tool execution
|
|
1245
|
-
|
|
1246
|
-
The example includes a Python MCP server (`examples/elicitation/elicitation_server.py`) that provides tools using elicitation:
|
|
1247
|
-
- `create_document` - Requests title and content via elicitation
|
|
1248
|
-
- `send_notification` - Confirms before sending a notification
|
|
1249
|
-
|
|
1250
|
-
## Key Features
|
|
1251
|
-
|
|
1252
|
-
### Client Features
|
|
1253
|
-
|
|
1254
|
-
- **Multiple transports** - Support for stdio, SSE, HTTP, and Streamable HTTP transports
|
|
1255
|
-
- **Multiple servers** - Connect to multiple MCP servers simultaneously
|
|
1256
|
-
- **Named servers** - Associate names with servers and find/reference them by name
|
|
1257
|
-
- **Server lookup** - Find servers by name using `find_server`
|
|
1258
|
-
- **Tool association** - Each tool knows which server it belongs to
|
|
1259
|
-
- **Tool discovery** - Find tools by name or pattern
|
|
1260
|
-
- **Structured outputs** - Support for MCP 2025-06-18 structured tool outputs with output schemas and type-safe responses
|
|
1261
|
-
- **Tool annotations** - Support for readOnly, destructive, and requiresConfirmation annotations with helper methods
|
|
1262
|
-
- **Elicitation support** - Server-initiated user interactions during tool execution (stdio, SSE, and Streamable HTTP transports)
|
|
1263
|
-
- **Server disambiguation** - Specify which server to use when tools with same name exist in multiple servers
|
|
1264
|
-
- **Atomic tool calls** - Simple API for invoking tools with parameters
|
|
1265
|
-
- **Batch support** - Call multiple tools in a single operation
|
|
1266
|
-
- **Prompts support** - List and get prompts with parameters from MCP servers
|
|
1267
|
-
- **Resources support** - List and read resources by URI from MCP servers
|
|
1268
|
-
- **API conversions** - Built-in format conversion for OpenAI, Anthropic, and Google Vertex AI APIs
|
|
1269
|
-
- **Thread safety** - Synchronized access for thread-safe operation
|
|
1270
|
-
- **Server notifications** - Support for JSON-RPC notifications
|
|
1271
|
-
- **Custom RPC methods** - Send any custom JSON-RPC method
|
|
1272
|
-
- **Consistent error handling** - Rich error types for better exception handling
|
|
1273
|
-
- **JSON configuration** - Support for server definition files in JSON format with name retention
|
|
1274
|
-
|
|
1275
|
-
### Server-Sent Events (SSE) Implementation
|
|
1276
|
-
|
|
1277
|
-
The SSE client implementation provides these key features:
|
|
1278
|
-
|
|
1279
|
-
- **Robust connection handling**: Properly manages HTTP/HTTPS connections with configurable timeouts and retries
|
|
1280
|
-
- **Automatic redirect support**: Follows HTTP redirects up to 3 hops for seamless server integration
|
|
1281
|
-
- **Advanced connection management**:
|
|
1282
|
-
- **Inactivity tracking**: Monitors connection activity to detect idle connections
|
|
1283
|
-
- **Automatic ping**: Sends ping requests after a configurable period of inactivity (default: 10 seconds)
|
|
1284
|
-
- **Automatic disconnection**: Closes idle connections after inactivity (2.5× ping interval)
|
|
1285
|
-
- **MCP compliant**: Any server communication resets the inactivity timer per specification
|
|
1286
|
-
- **Intelligent reconnection**:
|
|
1287
|
-
- **Ping failure detection**: Tracks consecutive ping failures (when server isn't responding)
|
|
1288
|
-
- **Automatic reconnection**: Attempts to reconnect after 3 consecutive ping failures
|
|
1289
|
-
- **Exponential backoff**: Uses increasing delays between reconnection attempts
|
|
1290
|
-
- **Smart retry limits**: Caps reconnection attempts (default: 5) to avoid infinite loops
|
|
1291
|
-
- **Connection state monitoring**: Properly detects and handles closed connections to prevent errors
|
|
1292
|
-
- **Failure transparency**: Handles reconnection in the background without disrupting client code
|
|
1293
|
-
- **Thread safety**: All operations are thread-safe using monitors and synchronized access
|
|
1294
|
-
- **Reliable error handling**: Comprehensive error handling for network issues, timeouts, and malformed responses
|
|
1295
|
-
- **JSON-RPC over SSE**: Full implementation of JSON-RPC 2.0 over SSE transport with initialize handshake
|
|
1296
|
-
- **Streaming support**: Native streaming for real-time updates via the `call_tool_streaming` method, which returns an Enumerator for processing results as they arrive
|
|
1297
|
-
- **Notification support**: Built-in handling for JSON-RPC notifications with automatic tool cache invalidation and custom notification callback support
|
|
1298
|
-
- **Custom RPC methods**: Send any custom JSON-RPC method or notification through `send_rpc` and `send_notification`
|
|
1299
|
-
- **Configurable retries**: All RPC requests support configurable retries with exponential backoff
|
|
1300
|
-
- **Consistent logging**: Tagged, leveled logging across all components for better debugging
|
|
1301
|
-
- **Graceful fallbacks**: Automatic fallback to synchronous HTTP when SSE connection fails
|
|
1302
|
-
- **URL normalization**: Consistent URL handling that respects user-provided formats
|
|
1303
|
-
- **Server connectivity check**: Built-in `ping` method to test server connectivity and health
|
|
1304
|
-
|
|
1305
|
-
### HTTP Transport Implementation
|
|
1306
|
-
|
|
1307
|
-
The HTTP transport provides a simpler, stateless communication mechanism for MCP servers:
|
|
1308
|
-
|
|
1309
|
-
- **Request/Response Model**: Standard HTTP request/response cycle for each JSON-RPC call
|
|
1310
|
-
- **Automatic redirect support**: Follows HTTP redirects up to 3 hops for seamless server integration
|
|
1311
|
-
- **JSON-Only Responses**: Accepts only `application/json` responses (no SSE support)
|
|
1312
|
-
- **Session Support**: Automatic session header (`Mcp-Session-Id`) capture and injection for session-based MCP servers
|
|
1313
|
-
- **Session Termination**: Proper session cleanup with HTTP DELETE requests during connection teardown
|
|
1314
|
-
- **Session Validation**: Security validation of session IDs to prevent malicious injection
|
|
1315
|
-
- **Stateless & Stateful**: Supports both stateless servers and session-based servers that require state continuity
|
|
1316
|
-
- **HTTP Headers Support**: Full support for custom headers including authorization, API keys, and other metadata
|
|
1317
|
-
- **Reliable Error Handling**: Comprehensive HTTP status code handling with appropriate error mapping
|
|
1318
|
-
- **Configurable Retries**: Exponential backoff retry logic for transient network failures
|
|
1319
|
-
- **Connection Pooling**: Uses Faraday's connection pooling for efficient HTTP connections
|
|
1320
|
-
- **Timeout Management**: Configurable timeouts for both connection establishment and request completion
|
|
1321
|
-
- **JSON-RPC over HTTP**: Full JSON-RPC 2.0 implementation over HTTP POST requests
|
|
1322
|
-
- **MCP Protocol Compliance**: Supports all standard MCP methods (initialize, tools/list, tools/call)
|
|
1323
|
-
- **Custom RPC Methods**: Send any custom JSON-RPC method or notification
|
|
1324
|
-
- **Thread Safety**: All operations are thread-safe for concurrent usage
|
|
1325
|
-
- **Streaming API Compatibility**: Provides `call_tool_streaming` method for API compatibility (returns single response)
|
|
1326
|
-
- **Graceful Degradation**: Simple fallback behavior when complex features aren't needed
|
|
1327
|
-
|
|
1328
|
-
### Streamable HTTP Transport Implementation
|
|
1329
|
-
|
|
1330
|
-
The Streamable HTTP transport bridges HTTP and Server-Sent Events, designed for servers that use HTTP POST but return SSE-formatted responses:
|
|
1331
|
-
|
|
1332
|
-
- **Hybrid Communication**: HTTP POST requests with Server-Sent Event formatted responses
|
|
1333
|
-
- **SSE Response Parsing**: Automatically parses `event:` and `data:` lines from SSE responses
|
|
1334
|
-
- **Session Support**: Automatic session header (`Mcp-Session-Id`) capture and injection for session-based MCP servers
|
|
1335
|
-
- **Session Termination**: Proper session cleanup with HTTP DELETE requests during connection teardown
|
|
1336
|
-
- **Resumability**: Event ID tracking and `Last-Event-ID` header support for message replay after disconnections
|
|
1337
|
-
- **Session Validation**: Security validation of session IDs to prevent malicious injection
|
|
1338
|
-
- **HTTP Semantics**: Maintains standard HTTP request/response model for client compatibility
|
|
1339
|
-
- **Streaming Format Support**: Handles complex SSE responses with multiple fields (event, id, retry, etc.)
|
|
1340
|
-
- **Error Handling**: Comprehensive error handling for both HTTP and SSE parsing failures
|
|
1341
|
-
- **Headers Optimization**: Includes SSE-compatible headers (`Accept: text/event-stream, application/json`, `Cache-Control: no-cache`)
|
|
1342
|
-
- **JSON-RPC Compliance**: Full JSON-RPC 2.0 support over the hybrid HTTP/SSE transport
|
|
1343
|
-
- **Retry Logic**: Exponential backoff for both connection and parsing failures
|
|
1344
|
-
- **Thread Safety**: All operations are thread-safe for concurrent usage
|
|
1345
|
-
- **Malformed Response Handling**: Graceful handling of invalid SSE format or missing data lines
|
|
1346
|
-
|
|
1347
377
|
## Requirements
|
|
1348
378
|
|
|
1349
379
|
- Ruby >= 3.2.0
|
|
1350
380
|
- No runtime dependencies
|
|
1351
381
|
|
|
1352
|
-
## Implementing an MCP Server
|
|
1353
|
-
|
|
1354
|
-
To implement a compatible MCP server you must:
|
|
1355
|
-
|
|
1356
|
-
- Listen on your chosen transport (JSON-RPC stdio, HTTP SSE, HTTP, or Streamable HTTP)
|
|
1357
|
-
- Respond to `list_tools` requests with a JSON list of tools
|
|
1358
|
-
- Respond to `call_tool` requests by executing the specified tool
|
|
1359
|
-
- Return results (or errors) in JSON format
|
|
1360
|
-
- Optionally send JSON-RPC notifications for events like tool updates
|
|
1361
|
-
|
|
1362
|
-
### JSON-RPC Notifications
|
|
1363
|
-
|
|
1364
|
-
The client supports JSON-RPC notifications from the server:
|
|
1365
|
-
|
|
1366
|
-
- Default notification handler for `notifications/tools/list_changed` to automatically clear the tool cache
|
|
1367
|
-
- Custom notification handling via the `on_notification` method
|
|
1368
|
-
- Callbacks receive the server instance, method name, and parameters
|
|
1369
|
-
- Multiple notification listeners can be registered
|
|
1370
|
-
|
|
1371
|
-
## Tool Schema
|
|
1372
|
-
|
|
1373
|
-
Each tool is defined by a name, description, and a JSON Schema for its parameters:
|
|
1374
|
-
|
|
1375
|
-
```json
|
|
1376
|
-
{
|
|
1377
|
-
"name": "example_tool",
|
|
1378
|
-
"description": "Does something useful",
|
|
1379
|
-
"schema": {
|
|
1380
|
-
"type": "object",
|
|
1381
|
-
"properties": {
|
|
1382
|
-
"param1": { "type": "string" },
|
|
1383
|
-
"param2": { "type": "number" }
|
|
1384
|
-
},
|
|
1385
|
-
"required": ["param1"]
|
|
1386
|
-
}
|
|
1387
|
-
}
|
|
1388
|
-
```
|
|
1389
|
-
|
|
1390
382
|
## License
|
|
1391
383
|
|
|
1392
|
-
|
|
384
|
+
Available as open source under the [MIT License](LICENSE).
|
|
1393
385
|
|
|
1394
386
|
## Contributing
|
|
1395
387
|
|
|
1396
|
-
Bug reports and pull requests
|
|
1397
|
-
https://github.com/simonx1/ruby-mcp-client.
|
|
388
|
+
Bug reports and pull requests welcome at https://github.com/simonx1/ruby-mcp-client.
|