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.
data/README.md CHANGED
@@ -1,1055 +1,388 @@
1
1
  # ruby-mcp-client
2
2
 
3
- This gem provides a Ruby client for the Model Context Protocol (MCP),
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 and other services to discover and invoke external tools
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
- The core client resides in `MCPClient::Client` and provides helper methods for integrating
37
- with popular AI services with built-in conversions:
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()` - Formats tools for OpenAI API
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 2025-03-26 Protocol Features
29
+ ## MCP Protocol Support
44
30
 
45
- This Ruby MCP Client implements key features from the latest MCP specification (Protocol Revision: 2025-03-26):
31
+ Implements **MCP 2025-06-18** specification:
46
32
 
47
- ### Implemented Features
48
- - **OAuth 2.1 Authorization Framework** - Complete authentication with PKCE, dynamic client registration, server discovery, and runtime configuration
49
- - **Streamable HTTP Transport** - Enhanced transport with Server-Sent Event formatted responses and session management
50
- - **HTTP Redirect Support** - Automatic redirect handling for both SSE and HTTP transports with configurable limits
51
- - **FastMCP Compatibility** - Full compatibility with FastMCP servers including proper line ending handling
52
- - **Prompts Support** - Full implementation of MCP prompts for dynamic content generation
53
- - **Resources Support** - Full MCP resources specification compliance including templates, subscriptions, pagination, and annotations
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
- ## Usage
43
+ ## Quick Connect API (Recommended)
56
44
 
57
- ### Basic Client Usage
45
+ The simplest way to connect to an MCP server:
58
46
 
59
47
  ```ruby
60
48
  require 'mcp_client'
61
49
 
62
- client = MCPClient.create_client(
63
- mcp_server_configs: [
64
- # Local stdio server
65
- MCPClient.stdio_config(
66
- command: 'npx -y @modelcontextprotocol/server-filesystem /home/user',
67
- name: 'filesystem' # Optional name for this server
68
- ),
69
- # Remote HTTP SSE server (with streaming support)
70
- MCPClient.sse_config(
71
- base_url: 'https://api.example.com/sse',
72
- headers: { 'Authorization' => 'Bearer YOUR_TOKEN' },
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
- # Or load server definitions from a JSON file
110
- client = MCPClient.create_client(
111
- server_definition_file: 'path/to/server_definition.json',
112
- logger: Logger.new($stdout, level: Logger::WARN) # Optional logger for client and servers
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
- # MCP server configuration JSON format can be:
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
- # Find a server by name
130
- filesystem_server = client.find_server('filesystem')
75
+ **Transport Detection:**
131
76
 
132
- # Find tools by name pattern (string or regex)
133
- file_tools = client.find_tools('file')
134
- first_tool = client.find_tool(/^file_/)
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
- # Call a specific tool by name
137
- result = client.call_tool('example_tool', { param1: 'value1', param2: 42 })
85
+ ## Working with Tools, Prompts & Resources
138
86
 
139
- # Call a tool on a specific server by name
140
- result = client.call_tool('example_tool', { param1: 'value1' }, server: 'filesystem')
141
- # You can also call a tool on a server directly
142
- result = filesystem_server.call_tool('example_tool', { param1: 'value1' })
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
- # Call multiple tools in batch
93
+ # Batch tool calls
145
94
  results = client.call_tools([
146
- { name: 'tool1', parameters: { key1: 'value1' } },
147
- { name: 'tool2', parameters: { key2: 'value2' }, server: 'filesystem' } # Specify server for a specific tool
95
+ { name: 'tool1', parameters: { key: 'value' } },
96
+ { name: 'tool2', parameters: { key: 'value' }, server: 'specific_server' }
148
97
  ])
149
98
 
150
- # Stream results (supported by the SSE transport)
151
- # Returns an Enumerator that yields results as they become available
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
- # === Working with Prompts ===
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
- # Get a specific prompt with parameters
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
- puts content.text # Text content
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
- ### HTTP Transport Example
117
+ ## MCP 2025-06-18 Features
225
118
 
226
- The HTTP transport provides simple request/response communication with MCP servers:
119
+ ### Tool Annotations
227
120
 
228
121
  ```ruby
229
- require 'mcp_client'
230
- require 'logger'
231
-
232
- # Optional logger for debugging
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
- ### Streamable HTTP Transport Example
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
- require 'mcp_client'
289
- require 'logger'
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
- # Clean up
329
- streamable_client.cleanup
135
+ result = client.call_tool('get_weather', { location: 'SF' })
136
+ data = result['structuredContent'] # Type-safe structured data
330
137
  ```
331
138
 
332
- ### Server-Sent Events (SSE) Example
333
-
334
- The SSE transport provides robust connection handling for remote MCP servers:
139
+ ### Roots
335
140
 
336
141
  ```ruby
337
- require 'mcp_client'
338
- require 'logger'
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
- # Optional logger for debugging
341
- logger = Logger.new($stdout)
342
- logger.level = Logger::INFO
152
+ ### Sampling (Server-requested LLM completions)
343
153
 
344
- # Create an MCP client that connects to a Playwright MCP server via Streamable HTTP
345
- # First run: npx @playwright/mcp@latest --port 8931
346
- mcp_client = MCPClient.create_client(
347
- mcp_server_configs: [
348
- MCPClient.streamable_http_config(
349
- base_url: 'http://localhost:8931/mcp',
350
- read_timeout: 30, # Timeout in seconds for request fulfillment
351
- ping: 10, # Send ping after 10 seconds of inactivity
352
- # Connection closes automatically after inactivity (2.5x ping interval)
353
- retries: 2, # Number of retry attempts on transient errors
354
- logger: logger # Optional logger for debugging connection issues
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
- See `examples/streamable_http_example.rb` for the full Playwright SSE example.
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
- # Run the Ruby client
406
- # bundle exec ruby examples/echo_server_client.rb
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
- # List and use tools
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
- # List and read resources
435
- result = client.list_resources
436
- resources = result['resources']
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
- # Read resource (returns array of ResourceContent objects)
441
- contents = client.read_resource('file:///sample/README.md')
442
- contents.each do |content|
443
- puts content.text if content.text?
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
- #### Streamable HTTP Example
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
- # Run the streamable HTTP client
458
- # bundle exec ruby examples/echo_server_streamable_client.rb
459
-
460
- require 'mcp_client'
461
-
462
- # Connect to streamable HTTP server with full MCP protocol support
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
- The FastMCP example includes:
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
- ### Integration Examples
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
- # Using the openai/openai-ruby gem (official)
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
- command: %W[npx -y @modelcontextprotocol/server-filesystem #{Dir.pwd}]
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
- tools: tools
229
+ logger: Logger.new($stdout)
534
230
  )
535
231
 
536
- # Process tool calls and results
537
- # See examples directory for complete implementation
232
+ # Or load from JSON file
233
+ client = MCPClient.create_client(server_definition_file: 'servers.json')
538
234
  ```
539
235
 
540
- ```ruby
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
- require 'mcp_client'
564
- require 'anthropic'
565
-
566
- # Create MCP client
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
- Complete examples can be found in the `examples/` directory:
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", "/path/to/dir"],
628
- "env": {
629
- "DEBUG": "true"
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
- A simpler example used in the Playwright demo (found in `examples/sample_server_definition.json`):
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
- Load this configuration with:
266
+ ### OpenAI
651
267
 
652
268
  ```ruby
653
- client = MCPClient.create_client(server_definition_file: 'path/to/definition.json')
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
- Example of automatic session termination:
272
+ mcp = MCPClient.connect('npx -y @modelcontextprotocol/server-filesystem .')
273
+ tools = mcp.to_openai_tools
710
274
 
711
- ```ruby
712
- # Session is automatically terminated when client is cleaned up
713
- client = MCPClient.create_client(
714
- mcp_server_configs: [
715
- MCPClient.http_config(base_url: 'https://api.example.com/mcp')
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
- This enables compatibility with MCP servers that maintain state between requests and require session identification.
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
- # Create an OAuth-enabled HTTP server
736
- server = MCPClient::OAuthClient.create_http_server(
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
- # Use the server normally
753
- server.connect
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
- ### Manual OAuth Provider
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
- For more control over the OAuth flow:
301
+ ## OAuth 2.1 Authentication
760
302
 
761
303
  ```ruby
762
- # Create OAuth provider directly
763
- oauth_provider = MCPClient::Auth::OAuthProvider.new(
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
- # Update configuration at runtime
770
- oauth_provider.scope = 'mcp:read mcp:write admin'
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
- # Complete flow after user authorization
777
- token = oauth_provider.complete_authorization_flow(code, state)
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
- ### OAuth Features
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
- ### Resource Features
326
+ See [OAUTH.md](OAUTH.md) for full documentation.
796
327
 
797
- - **Resource listing** with cursor-based pagination
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
- # Get a server instance
808
- server = client.servers.first # or client.find_server('name')
809
-
810
- # List resources with pagination
811
- result = server.list_resources
812
- resources = result['resources'] # Array of Resource objects
813
- next_cursor = result['nextCursor'] # String cursor for next page (if any)
814
-
815
- # Get next page of resources
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
- # Access Resource properties
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
- # Read resource contents (returns array of ResourceContent objects)
831
- contents = server.read_resource(resource.uri)
345
+ Both HTTP and Streamable HTTP transports automatically handle session-based servers:
832
346
 
833
- contents.each do |content|
834
- # Check content type
835
- if content.text?
836
- # Text content
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
- # Access optional annotations
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
- # List resource templates
854
- templates_result = server.list_resource_templates
855
- templates = templates_result['resourceTemplates'] # Array of ResourceTemplate objects
354
+ ## Server Compatibility
856
355
 
857
- template = templates.first
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
- # Subscribe to resource updates
865
- server.subscribe_resource('file:///watched.txt') # Returns true on success
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
- # Unsubscribe from resource updates
868
- server.unsubscribe_resource('file:///watched.txt') # Returns true on success
363
+ ### FastMCP Example
869
364
 
870
- # Check server capabilities for resources
871
- capabilities = server.capabilities
872
- if capabilities['resources']
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
- # ResourceContent for text
884
- content = MCPClient::ResourceContent.new(
885
- uri: 'file:///example.txt',
886
- name: 'example.txt',
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
- This gem is available as open source under the [MIT License](LICENSE).
384
+ Available as open source under the [MIT License](LICENSE).
1051
385
 
1052
386
  ## Contributing
1053
387
 
1054
- Bug reports and pull requests are welcome on GitHub at
1055
- https://github.com/simonx1/ruby-mcp-client.
388
+ Bug reports and pull requests welcome at https://github.com/simonx1/ruby-mcp-client.