mcp 0.4.0 → 0.11.0

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.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +216 -0
  3. data/README.md +550 -63
  4. data/lib/json_rpc_handler.rb +171 -0
  5. data/lib/mcp/annotations.rb +21 -0
  6. data/lib/mcp/client/http.rb +23 -7
  7. data/lib/mcp/client/stdio.rb +222 -0
  8. data/lib/mcp/client.rb +109 -34
  9. data/lib/mcp/configuration.rb +11 -9
  10. data/lib/mcp/content.rb +29 -2
  11. data/lib/mcp/icon.rb +22 -0
  12. data/lib/mcp/instrumentation.rb +1 -1
  13. data/lib/mcp/logging_message_notification.rb +30 -0
  14. data/lib/mcp/methods.rb +3 -0
  15. data/lib/mcp/progress.rb +24 -0
  16. data/lib/mcp/prompt/message.rb +1 -1
  17. data/lib/mcp/prompt/result.rb +1 -1
  18. data/lib/mcp/prompt.rb +22 -5
  19. data/lib/mcp/resource/contents.rb +2 -2
  20. data/lib/mcp/resource/embedded.rb +2 -1
  21. data/lib/mcp/resource.rb +7 -2
  22. data/lib/mcp/resource_template.rb +4 -2
  23. data/lib/mcp/server/transports/stdio_transport.rb +41 -4
  24. data/lib/mcp/server/transports/streamable_http_transport.rb +456 -85
  25. data/lib/mcp/server/transports.rb +10 -0
  26. data/lib/mcp/server.rb +403 -67
  27. data/lib/mcp/server_context.rb +58 -0
  28. data/lib/mcp/server_session.rb +107 -0
  29. data/lib/mcp/string_utils.rb +3 -3
  30. data/lib/mcp/tool/annotations.rb +1 -1
  31. data/lib/mcp/tool/input_schema.rb +6 -55
  32. data/lib/mcp/tool/output_schema.rb +3 -54
  33. data/lib/mcp/tool/response.rb +1 -1
  34. data/lib/mcp/tool/schema.rb +48 -0
  35. data/lib/mcp/tool.rb +39 -5
  36. data/lib/mcp/transport.rb +15 -2
  37. data/lib/mcp/version.rb +1 -1
  38. data/lib/mcp.rb +12 -31
  39. metadata +21 -42
  40. data/.gitattributes +0 -4
  41. data/.github/dependabot.yml +0 -6
  42. data/.github/workflows/ci.yml +0 -33
  43. data/.github/workflows/release.yml +0 -25
  44. data/.gitignore +0 -10
  45. data/.rubocop.yml +0 -12
  46. data/AGENTS.md +0 -119
  47. data/CHANGELOG.md +0 -87
  48. data/CODE_OF_CONDUCT.md +0 -74
  49. data/Gemfile +0 -27
  50. data/LICENSE.txt +0 -21
  51. data/Rakefile +0 -17
  52. data/bin/console +0 -15
  53. data/bin/rake +0 -31
  54. data/bin/setup +0 -8
  55. data/dev.yml +0 -31
  56. data/examples/README.md +0 -197
  57. data/examples/http_client.rb +0 -184
  58. data/examples/http_server.rb +0 -170
  59. data/examples/stdio_server.rb +0 -94
  60. data/examples/streamable_http_client.rb +0 -203
  61. data/examples/streamable_http_server.rb +0 -172
  62. data/mcp.gemspec +0 -32
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # MCP Ruby SDK [![Gem Version](https://img.shields.io/gem/v/mcp)](https://rubygems.org/gems/mcp) [![MIT licensed](https://img.shields.io/badge/license-MIT-green)](https://github.com/modelcontextprotocol/ruby-sdk/blob/main/LICENSE.txt) [![CI](https://github.com/modelcontextprotocol/ruby-sdk/actions/workflows/ci.yml/badge.svg)](https://github.com/modelcontextprotocol/ruby-sdk/actions/workflows/ci.yml)
1
+ # MCP Ruby SDK [![Gem Version](https://img.shields.io/gem/v/mcp)](https://rubygems.org/gems/mcp) [![Apache 2.0 licensed](https://img.shields.io/badge/license-Apache%202.0-blue)](https://github.com/modelcontextprotocol/ruby-sdk/blob/main/LICENSE) [![CI](https://github.com/modelcontextprotocol/ruby-sdk/actions/workflows/ci.yml/badge.svg)](https://github.com/modelcontextprotocol/ruby-sdk/actions/workflows/ci.yml)
2
2
 
3
3
  The official Ruby SDK for Model Context Protocol servers and clients.
4
4
 
@@ -38,6 +38,7 @@ It implements the Model Context Protocol specification, handling model context r
38
38
  - Supports resource registration and retrieval
39
39
  - Supports stdio & Streamable HTTP (including SSE) transports
40
40
  - Supports notifications for list changes (tools, prompts, resources)
41
+ - Supports sampling (server-to-client LLM completion requests)
41
42
 
42
43
  ### Supported Methods
43
44
 
@@ -50,6 +51,8 @@ It implements the Model Context Protocol specification, handling model context r
50
51
  - `resources/list` - Lists all registered resources and their schemas
51
52
  - `resources/read` - Retrieves a specific resource by name
52
53
  - `resources/templates/list` - Lists all registered resource templates and their schemas
54
+ - `completion/complete` - Returns autocompletion suggestions for prompt arguments and resource URIs
55
+ - `sampling/createMessage` - Requests LLM completion from the client (server-to-client)
53
56
 
54
57
  ### Custom Methods
55
58
 
@@ -102,17 +105,184 @@ end
102
105
  - Raises `MCP::Server::MethodAlreadyDefinedError` if trying to override an existing method
103
106
  - Supports the same exception reporting and instrumentation as standard methods
104
107
 
108
+ ### Sampling
109
+
110
+ The Model Context Protocol allows servers to request LLM completions from clients through the `sampling/createMessage` method.
111
+ This enables servers to leverage the client's LLM capabilities without needing direct access to AI models.
112
+
113
+ **Key Concepts:**
114
+
115
+ - **Server-to-Client Request**: Unlike typical MCP methods (client→server), sampling is initiated by the server
116
+ - **Client Capability**: Clients must declare `sampling` capability during initialization
117
+ - **Tool Support**: When using tools in sampling requests, clients must declare `sampling.tools` capability
118
+ - **Human-in-the-Loop**: Clients can implement user approval before forwarding requests to LLMs
119
+
120
+ **Usage Example (Stdio transport):**
121
+
122
+ `Server#create_sampling_message` is for single-client transports (e.g., `StdioTransport`).
123
+ For multi-client transports (e.g., `StreamableHTTPTransport`), use `server_context.create_sampling_message` inside tools instead,
124
+ which routes the request to the correct client session.
125
+
126
+ ```ruby
127
+ server = MCP::Server.new(name: "my_server")
128
+ transport = MCP::Server::Transports::StdioTransport.new(server)
129
+ server.transport = transport
130
+ ```
131
+
132
+ Client must declare sampling capability during initialization.
133
+ This happens automatically when the client connects.
134
+
135
+ ```ruby
136
+ result = server.create_sampling_message(
137
+ messages: [
138
+ { role: "user", content: { type: "text", text: "What is the capital of France?" } }
139
+ ],
140
+ max_tokens: 100,
141
+ system_prompt: "You are a helpful assistant.",
142
+ temperature: 0.7
143
+ )
144
+ ```
145
+
146
+ Result contains the LLM response:
147
+
148
+ ```ruby
149
+ {
150
+ role: "assistant",
151
+ content: { type: "text", text: "The capital of France is Paris." },
152
+ model: "claude-3-sonnet-20240307",
153
+ stopReason: "endTurn"
154
+ }
155
+ ```
156
+
157
+ **Parameters:**
158
+
159
+ Required:
160
+
161
+ - `messages:` (Array) - Array of message objects with `role` and `content`
162
+ - `max_tokens:` (Integer) - Maximum tokens in the response
163
+
164
+ Optional:
165
+
166
+ - `system_prompt:` (String) - System prompt for the LLM
167
+ - `model_preferences:` (Hash) - Model selection preferences (e.g., `{ intelligencePriority: 0.8 }`)
168
+ - `include_context:` (String) - Context inclusion: `"none"`, `"thisServer"`, or `"allServers"` (soft-deprecated)
169
+ - `temperature:` (Float) - Sampling temperature
170
+ - `stop_sequences:` (Array) - Sequences that stop generation
171
+ - `metadata:` (Hash) - Additional metadata
172
+ - `tools:` (Array) - Tools available to the LLM (requires `sampling.tools` capability)
173
+ - `tool_choice:` (Hash) - Tool selection mode (e.g., `{ mode: "auto" }`)
174
+
175
+ **Using Sampling in Tools (works with both Stdio and HTTP transports):**
176
+
177
+ Tools that accept a `server_context:` parameter can call `create_sampling_message` on it.
178
+ The request is automatically routed to the correct client session.
179
+ Set `server.server_context = server` so that `server_context.create_sampling_message` delegates to the server:
180
+
181
+ ```ruby
182
+ class SummarizeTool < MCP::Tool
183
+ description "Summarize text using LLM"
184
+ input_schema(
185
+ properties: {
186
+ text: { type: "string" }
187
+ },
188
+ required: ["text"]
189
+ )
190
+
191
+ def self.call(text:, server_context:)
192
+ result = server_context.create_sampling_message(
193
+ messages: [
194
+ { role: "user", content: { type: "text", text: "Please summarize: #{text}" } }
195
+ ],
196
+ max_tokens: 500
197
+ )
198
+
199
+ MCP::Tool::Response.new([{
200
+ type: "text",
201
+ text: result[:content][:text]
202
+ }])
203
+ end
204
+ end
205
+
206
+ server = MCP::Server.new(name: "my_server", tools: [SummarizeTool])
207
+ server.server_context = server
208
+ ```
209
+
210
+ **Tool Use in Sampling:**
211
+
212
+ When tools are provided in a sampling request, the LLM can call them during generation.
213
+ The server must handle tool calls and continue the conversation with tool results:
214
+
215
+ ```ruby
216
+ result = server.create_sampling_message(
217
+ messages: [
218
+ { role: "user", content: { type: "text", text: "What's the weather in Paris?" } }
219
+ ],
220
+ max_tokens: 1000,
221
+ tools: [
222
+ {
223
+ name: "get_weather",
224
+ description: "Get weather for a city",
225
+ inputSchema: {
226
+ type: "object",
227
+ properties: { city: { type: "string" } },
228
+ required: ["city"]
229
+ }
230
+ }
231
+ ],
232
+ tool_choice: { mode: "auto" }
233
+ )
234
+
235
+ if result[:stopReason] == "toolUse"
236
+ tool_results = result[:content].map do |tool_use|
237
+ weather_data = get_weather(tool_use[:input][:city])
238
+
239
+ {
240
+ type: "tool_result",
241
+ toolUseId: tool_use[:id],
242
+ content: [{ type: "text", text: weather_data.to_json }]
243
+ }
244
+ end
245
+
246
+ final_result = server.create_sampling_message(
247
+ messages: [
248
+ { role: "user", content: { type: "text", text: "What's the weather in Paris?" } },
249
+ { role: "assistant", content: result[:content] },
250
+ { role: "user", content: tool_results }
251
+ ],
252
+ max_tokens: 1000,
253
+ tools: [...]
254
+ )
255
+ end
256
+ ```
257
+
258
+ **Error Handling:**
259
+
260
+ - Raises `RuntimeError` if transport is not set
261
+ - Raises `RuntimeError` if client does not support `sampling` capability
262
+ - Raises `RuntimeError` if `tools` are used but client lacks `sampling.tools` capability
263
+ - Raises `StandardError` if client returns an error response
264
+
105
265
  ### Notifications
106
266
 
107
267
  The server supports sending notifications to clients when lists of tools, prompts, or resources change. This enables real-time updates without polling.
108
268
 
109
269
  #### Notification Methods
110
270
 
111
- The server provides three notification methods:
271
+ The server provides the following notification methods:
112
272
 
113
273
  - `notify_tools_list_changed` - Send a notification when the tools list changes
114
274
  - `notify_prompts_list_changed` - Send a notification when the prompts list changes
115
275
  - `notify_resources_list_changed` - Send a notification when the resources list changes
276
+ - `notify_log_message` - Send a structured logging notification message
277
+
278
+ #### Session Scoping
279
+
280
+ When using Streamable HTTP transport with multiple clients, each client connection gets its own session. Notifications are scoped as follows:
281
+
282
+ - **`report_progress`** and **`notify_log_message`** called via `server_context` inside a tool handler are automatically sent only to the requesting client.
283
+ No extra configuration is needed.
284
+ - **`notify_tools_list_changed`**, **`notify_prompts_list_changed`**, and **`notify_resources_list_changed`** are always broadcast to all connected clients,
285
+ as they represent server-wide state changes. These should be called on the `server` instance directly.
116
286
 
117
287
  #### Notification Format
118
288
 
@@ -121,6 +291,179 @@ Notifications follow the JSON-RPC 2.0 specification and use these method names:
121
291
  - `notifications/tools/list_changed`
122
292
  - `notifications/prompts/list_changed`
123
293
  - `notifications/resources/list_changed`
294
+ - `notifications/progress`
295
+ - `notifications/message`
296
+
297
+ ### Progress
298
+
299
+ The MCP Ruby SDK supports progress tracking for long-running tool operations,
300
+ following the [MCP Progress specification](https://modelcontextprotocol.io/specification/latest/server/utilities/progress).
301
+
302
+ #### How Progress Works
303
+
304
+ 1. **Client Request**: The client sends a `progressToken` in the `_meta` field when calling a tool
305
+ 2. **Server Notification**: The server sends `notifications/progress` messages back to the client during tool execution
306
+ 3. **Tool Integration**: Tools call `server_context.report_progress` to report incremental progress
307
+
308
+ #### Server-Side: Tool with Progress
309
+
310
+ Tools that accept a `server_context:` parameter can call `report_progress` on it.
311
+ The server automatically wraps the context in an `MCP::ServerContext` instance that provides this method:
312
+
313
+ ```ruby
314
+ class LongRunningTool < MCP::Tool
315
+ description "A tool that reports progress during execution"
316
+ input_schema(
317
+ properties: {
318
+ count: { type: "integer" },
319
+ },
320
+ required: ["count"]
321
+ )
322
+
323
+ def self.call(count:, server_context:)
324
+ count.times do |i|
325
+ # Do work here.
326
+ server_context.report_progress(i + 1, total: count, message: "Processing item #{i + 1}")
327
+ end
328
+
329
+ MCP::Tool::Response.new([{ type: "text", text: "Done" }])
330
+ end
331
+ end
332
+ ```
333
+
334
+ The `server_context.report_progress` method accepts:
335
+
336
+ - `progress` (required) — current progress value (numeric)
337
+ - `total:` (optional) — total expected value, so clients can display a percentage
338
+ - `message:` (optional) — human-readable status message
339
+
340
+ **Key Features:**
341
+
342
+ - Tools report progress via `server_context.report_progress`
343
+ - `report_progress` is a no-op when no `progressToken` was provided by the client
344
+ - Supports both numeric and string progress tokens
345
+
346
+ ### Completions
347
+
348
+ MCP spec includes [Completions](https://modelcontextprotocol.io/specification/latest/server/utilities/completion),
349
+ which enable servers to provide autocompletion suggestions for prompt arguments and resource URIs.
350
+
351
+ To enable completions, declare the `completions` capability and register a handler:
352
+
353
+ ```ruby
354
+ server = MCP::Server.new(
355
+ name: "my_server",
356
+ prompts: [CodeReviewPrompt],
357
+ resource_templates: [FileTemplate],
358
+ capabilities: { completions: {} },
359
+ )
360
+
361
+ server.completion_handler do |params|
362
+ ref = params[:ref]
363
+ argument = params[:argument]
364
+ value = argument[:value]
365
+
366
+ case ref[:type]
367
+ when "ref/prompt"
368
+ values = case argument[:name]
369
+ when "language"
370
+ ["python", "pytorch", "pyside"].select { |v| v.start_with?(value) }
371
+ else
372
+ []
373
+ end
374
+ { completion: { values: values, hasMore: false } }
375
+ when "ref/resource"
376
+ { completion: { values: [], hasMore: false } }
377
+ end
378
+ end
379
+ ```
380
+
381
+ The handler receives a `params` hash with:
382
+
383
+ - `ref` - The reference (`{ type: "ref/prompt", name: "..." }` or `{ type: "ref/resource", uri: "..." }`)
384
+ - `argument` - The argument being completed (`{ name: "...", value: "..." }`)
385
+ - `context` (optional) - Previously resolved arguments (`{ arguments: { ... } }`)
386
+
387
+ The handler must return a hash with a `completion` key containing `values` (array of strings), and optionally `total` and `hasMore`.
388
+ The SDK automatically enforces the 100-item limit per the MCP specification.
389
+
390
+ The server validates that the referenced prompt, resource, or resource template is registered before calling the handler.
391
+ Requests for unknown references return an error.
392
+
393
+ ### Logging
394
+
395
+ The MCP Ruby SDK supports structured logging through the `notify_log_message` method, following the [MCP Logging specification](https://modelcontextprotocol.io/specification/latest/server/utilities/logging).
396
+
397
+ The `notifications/message` notification is used for structured logging between client and server.
398
+
399
+ #### Log Levels
400
+
401
+ The SDK supports 8 log levels with increasing severity:
402
+
403
+ - `debug` - Detailed debugging information
404
+ - `info` - General informational messages
405
+ - `notice` - Normal but significant events
406
+ - `warning` - Warning conditions
407
+ - `error` - Error conditions
408
+ - `critical` - Critical conditions
409
+ - `alert` - Action must be taken immediately
410
+ - `emergency` - System is unusable
411
+
412
+ #### How Logging Works
413
+
414
+ 1. **Client Configuration**: The client sends a `logging/setLevel` request to configure the minimum log level
415
+ 2. **Server Filtering**: The server only sends log messages at the configured level or higher severity
416
+ 3. **Notification Delivery**: Log messages are sent as `notifications/message` to the client
417
+
418
+ For example, if the client sets the level to `"error"` (severity 4), the server will send messages with levels: `error`, `critical`, `alert`, and `emergency`.
419
+
420
+ For more details, see the [MCP Logging specification](https://modelcontextprotocol.io/specification/latest/server/utilities/logging).
421
+
422
+ **Usage Example:**
423
+
424
+ ```ruby
425
+ server = MCP::Server.new(name: "my_server")
426
+ transport = MCP::Server::Transports::StdioTransport.new(server)
427
+ server.transport = transport
428
+
429
+ # The client first configures the logging level (on the client side):
430
+ transport.send_request(
431
+ request: {
432
+ jsonrpc: "2.0",
433
+ method: "logging/setLevel",
434
+ params: { level: "info" },
435
+ id: session_id # Unique request ID within the session
436
+ }
437
+ )
438
+
439
+ # Send log messages at different severity levels
440
+ server.notify_log_message(
441
+ data: { message: "Application started successfully" },
442
+ level: "info"
443
+ )
444
+
445
+ server.notify_log_message(
446
+ data: { message: "Configuration file not found, using defaults" },
447
+ level: "warning"
448
+ )
449
+
450
+ server.notify_log_message(
451
+ data: {
452
+ error: "Database connection failed",
453
+ details: { host: "localhost", port: 5432 }
454
+ },
455
+ level: "error",
456
+ logger: "DatabaseLogger" # Optional logger name
457
+ )
458
+ ```
459
+
460
+ **Key Features:**
461
+
462
+ - Supports 8 log levels (debug, info, notice, warning, error, critical, alert, emergency) based on https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/logging#log-levels
463
+ - Server has capability `logging` to send log messages
464
+ - Messages are only sent if a transport is configured
465
+ - Messages are filtered based on the client's configured log level
466
+ - If the log level hasn't been set by the client, no messages will be sent
124
467
 
125
468
  #### Transport Support
126
469
 
@@ -131,7 +474,10 @@ Notifications follow the JSON-RPC 2.0 specification and use these method names:
131
474
 
132
475
  ```ruby
133
476
  server = MCP::Server.new(name: "my_server")
477
+
478
+ # Default Streamable HTTP - session oriented
134
479
  transport = MCP::Server::Transports::StreamableHTTPTransport.new(server)
480
+
135
481
  server.transport = transport
136
482
 
137
483
  # When tools change, notify clients
@@ -139,35 +485,63 @@ server.define_tool(name: "new_tool") { |**args| { result: "ok" } }
139
485
  server.notify_tools_list_changed
140
486
  ```
141
487
 
142
- ### Unsupported Features ( to be implemented in future versions )
488
+ You can use Stateless Streamable HTTP, where notifications are not supported and all calls are request/response interactions.
489
+ This mode allows for easy multi-node deployment.
490
+ Set `stateless: true` in `MCP::Server::Transports::StreamableHTTPTransport.new` (`stateless` defaults to `false`):
491
+
492
+ ```ruby
493
+ # Stateless Streamable HTTP - session-less
494
+ transport = MCP::Server::Transports::StreamableHTTPTransport.new(server, stateless: true)
495
+ ```
496
+
497
+ By default, sessions do not expire. To mitigate session hijacking risks, you can set a `session_idle_timeout` (in seconds).
498
+ When configured, sessions that receive no HTTP requests for this duration are automatically expired and cleaned up:
499
+
500
+ ```ruby
501
+ # Session timeout of 30 minutes
502
+ transport = MCP::Server::Transports::StreamableHTTPTransport.new(server, session_idle_timeout: 1800)
503
+ ```
504
+
505
+ ### Unsupported Features (to be implemented in future versions)
143
506
 
144
- - Log Level
145
507
  - Resource subscriptions
146
- - Completions
508
+ - Elicitation
147
509
 
148
510
  ### Usage
149
511
 
512
+ > [!IMPORTANT]
513
+ > `MCP::Server::Transports::StreamableHTTPTransport` stores session and SSE stream state in memory,
514
+ > so it must run in a single process. Use a single-process server (e.g., Puma with `workers 0`).
515
+ > Multi-process configurations (Unicorn, or Puma with `workers > 0`) fork separate processes that
516
+ > do not share memory, which breaks session management and SSE connections.
517
+ > Stateless mode (`stateless: true`) does not use sessions and works with any server configuration.
518
+
150
519
  #### Rails Controller
151
520
 
152
521
  When added to a Rails controller on a route that handles POST requests, your server will be compliant with non-streaming
153
- [Streamable HTTP](https://modelcontextprotocol.io/specification/2025-06-18/basic/transports#streamable-http) transport
522
+ [Streamable HTTP](https://modelcontextprotocol.io/specification/latest/basic/transports#streamable-http) transport
154
523
  requests.
155
524
 
156
- You can use the `Server#handle_json` method to handle requests.
525
+ You can use `StreamableHTTPTransport#handle_request` to handle requests with proper HTTP
526
+ status codes (e.g., 202 Accepted for notifications).
157
527
 
158
528
  ```ruby
159
- class ApplicationController < ActionController::Base
160
- def index
529
+ class McpController < ActionController::Base
530
+ def create
161
531
  server = MCP::Server.new(
162
532
  name: "my_server",
163
- title: "Example Server Display Name", # WARNING: This is a `Draft` and is not supported in the `Version 2025-06-18 (latest)` specification.
533
+ title: "Example Server Display Name",
164
534
  version: "1.0.0",
165
535
  instructions: "Use the tools of this server as a last resort",
166
536
  tools: [SomeTool, AnotherTool],
167
537
  prompts: [MyPrompt],
168
538
  server_context: { user_id: current_user.id },
169
539
  )
170
- render(json: server.handle_json(request.body.read))
540
+ transport = MCP::Server::Transports::StreamableHTTPTransport.new(server)
541
+ server.transport = transport
542
+ status, headers, body = transport.handle_request(request)
543
+
544
+ render(json: body.first, status: status, headers: headers)
171
545
  end
172
546
  end
173
547
  ```
@@ -286,6 +660,50 @@ server = MCP::Server.new(
286
660
 
287
661
  This hash is then passed as the `server_context` argument to tool and prompt calls, and is included in exception and instrumentation callbacks.
288
662
 
663
+ #### Request-specific `_meta` Parameter
664
+
665
+ The MCP protocol supports a special [`_meta` parameter](https://modelcontextprotocol.io/specification/2025-06-18/basic#general-fields) in requests that allows clients to pass request-specific metadata. The server automatically extracts this parameter and makes it available to tools and prompts as a nested field within the `server_context`.
666
+
667
+ **Access Pattern:**
668
+
669
+ When a client includes `_meta` in the request params, it becomes available as `server_context[:_meta]`:
670
+
671
+ ```ruby
672
+ class MyTool < MCP::Tool
673
+ def self.call(message:, server_context:)
674
+ # Access provider-specific metadata
675
+ session_id = server_context.dig(:_meta, :session_id)
676
+ request_id = server_context.dig(:_meta, :request_id)
677
+
678
+ # Access server's original context
679
+ user_id = server_context.dig(:user_id)
680
+
681
+ MCP::Tool::Response.new([{
682
+ type: "text",
683
+ text: "Processing for user #{user_id} in session #{session_id}"
684
+ }])
685
+ end
686
+ end
687
+ ```
688
+
689
+ **Client Request Example:**
690
+
691
+ ```json
692
+ {
693
+ "jsonrpc": "2.0",
694
+ "id": 1,
695
+ "method": "tools/call",
696
+ "params": {
697
+ "name": "my_tool",
698
+ "arguments": { "message": "Hello" },
699
+ "_meta": {
700
+ "session_id": "abc123",
701
+ "request_id": "req_456"
702
+ }
703
+ }
704
+ }
705
+ ```
706
+
289
707
  #### Configuration Block Data
290
708
 
291
709
  ##### Exception Reporter
@@ -307,10 +725,16 @@ The instrumentation callback receives a hash with the following possible keys:
307
725
 
308
726
  - `method`: (String) The protocol method called (e.g., "ping", "tools/list")
309
727
  - `tool_name`: (String, optional) The name of the tool called
728
+ - `tool_arguments`: (Hash, optional) The arguments passed to the tool
310
729
  - `prompt_name`: (String, optional) The name of the prompt called
311
730
  - `resource_uri`: (String, optional) The URI of the resource called
312
731
  - `error`: (String, optional) Error code if a lookup failed
313
732
  - `duration`: (Float) Duration of the call in seconds
733
+ - `client`: (Hash, optional) Client information with `name` and `version` keys, from the initialize request
734
+
735
+ > [!NOTE]
736
+ > `tool_name`, `prompt_name` and `resource_uri` are only populated if a matching handler is registered.
737
+ > This is to avoid potential issues with metric cardinality.
314
738
 
315
739
  **Type:**
316
740
 
@@ -322,9 +746,11 @@ instrumentation_callback = ->(data) { ... }
322
746
  **Example:**
323
747
 
324
748
  ```ruby
325
- config.instrumentation_callback = ->(data) {
326
- puts "Instrumentation: #{data.inspect}"
327
- }
749
+ MCP.configure do |config|
750
+ config.instrumentation_callback = ->(data) {
751
+ puts "Instrumentation: #{data.inspect}"
752
+ }
753
+ end
328
754
  ```
329
755
 
330
756
  ### Server Protocol Version
@@ -336,6 +762,9 @@ configuration = MCP::Configuration.new(protocol_version: "2024-11-05")
336
762
  MCP::Server.new(name: "test_server", configuration: configuration)
337
763
  ```
338
764
 
765
+ If no protocol version is specified, the latest stable version will be applied by default.
766
+ The latest stable version includes new features from the [draft version](https://modelcontextprotocol.io/specification/draft).
767
+
339
768
  This will make all new server instances use the specified protocol version instead of the default version. The protocol version can be reset to the default by setting it to `nil`:
340
769
 
341
770
  ```ruby
@@ -368,7 +797,7 @@ If no exception reporter is configured, a default no-op reporter is used that si
368
797
 
369
798
  ### Tools
370
799
 
371
- MCP spec includes [Tools](https://modelcontextprotocol.io/specification/2025-06-18/server/tools) which provide functionality to LLM apps.
800
+ MCP spec includes [Tools](https://modelcontextprotocol.io/specification/latest/server/tools) which provide functionality to LLM apps.
372
801
 
373
802
  This gem provides a `MCP::Tool` class that can be used to create tools in three ways:
374
803
 
@@ -376,7 +805,7 @@ This gem provides a `MCP::Tool` class that can be used to create tools in three
376
805
 
377
806
  ```ruby
378
807
  class MyTool < MCP::Tool
379
- title "My Tool" # WARNING: This is a `Draft` and is not supported in the `Version 2025-06-18 (latest)` specification.
808
+ title "My Tool"
380
809
  description "This tool performs specific functionality..."
381
810
  input_schema(
382
811
  properties: {
@@ -413,13 +842,13 @@ tool = MyTool
413
842
  ```ruby
414
843
  tool = MCP::Tool.define(
415
844
  name: "my_tool",
416
- title: "My Tool", # WARNING: This is a `Draft` and is not supported in the `Version 2025-06-18 (latest)` specification.
845
+ title: "My Tool",
417
846
  description: "This tool performs specific functionality...",
418
847
  annotations: {
419
848
  read_only_hint: true,
420
849
  title: "My Tool"
421
850
  }
422
- ) do |args, server_context|
851
+ ) do |args, server_context:|
423
852
  MCP::Tool::Response.new([{ type: "text", text: "OK" }])
424
853
  end
425
854
  ```
@@ -435,7 +864,7 @@ server.define_tool(
435
864
  title: "My Tool",
436
865
  read_only_hint: true
437
866
  }
438
- ) do |args, server_context|
867
+ ) do |args, server_context:|
439
868
  Tool::Response.new([{ type: "text", text: "OK" }])
440
869
  end
441
870
  ```
@@ -525,7 +954,7 @@ tool = MCP::Tool.define(
525
954
  },
526
955
  required: ["mean", "median", "count"]
527
956
  }
528
- ) do |args, server_context|
957
+ ) do |args, server_context:|
529
958
  # Calculate statistics and validate against schema
530
959
  MCP::Tool::Response.new([{ type: "text", text: "Statistics calculated" }])
531
960
  end
@@ -551,7 +980,7 @@ Output schema may also describe an array of objects:
551
980
  class WeatherTool < MCP::Tool
552
981
  output_schema(
553
982
  type: "array",
554
- item: {
983
+ items: {
555
984
  properties: {
556
985
  temperature: { type: "number" },
557
986
  condition: { type: "string" },
@@ -566,7 +995,7 @@ end
566
995
  Please note: in this case, you must provide `type: "array"`. The default type
567
996
  for output schemas is `object`.
568
997
 
569
- MCP spec for the [Output Schema](https://modelcontextprotocol.io/specification/2025-06-18/server/tools#output-schema) specifies that:
998
+ MCP spec for the [Output Schema](https://modelcontextprotocol.io/specification/latest/server/tools#output-schema) specifies that:
570
999
 
571
1000
  - **Server Validation**: Servers MUST provide structured results that conform to the output schema
572
1001
  - **Client Validation**: Clients SHOULD validate structured results against the output schema
@@ -582,10 +1011,10 @@ Tools can return structured data alongside text content using the `structured_co
582
1011
  The structured content will be included in the JSON-RPC response as the `structuredContent` field.
583
1012
 
584
1013
  ```ruby
585
- class APITool < MCP::Tool
1014
+ class WeatherTool < MCP::Tool
586
1015
  description "Get current weather and return structured data"
587
1016
 
588
- def self.call(endpoint:, server_context:)
1017
+ def self.call(location:, units: "celsius", server_context:)
589
1018
  # Call weather API and structure the response
590
1019
  api_response = WeatherAPI.fetch(location, units)
591
1020
  weather_data = {
@@ -607,9 +1036,35 @@ class APITool < MCP::Tool
607
1036
  end
608
1037
  ```
609
1038
 
1039
+ ### Tool Responses with Errors
1040
+
1041
+ Tools can return error information alongside text content using the `error` parameter.
1042
+
1043
+ The error will be included in the JSON-RPC response as the `isError` field.
1044
+
1045
+ ```ruby
1046
+ class WeatherTool < MCP::Tool
1047
+ description "Get current weather and return structured data"
1048
+
1049
+ def self.call(server_context:)
1050
+ # Do something here
1051
+ content = {}
1052
+
1053
+ MCP::Tool::Response.new(
1054
+ [{
1055
+ type: "text",
1056
+ text: content.to_json
1057
+ }],
1058
+ structured_content: content,
1059
+ error: true
1060
+ )
1061
+ end
1062
+ end
1063
+ ```
1064
+
610
1065
  ### Prompts
611
1066
 
612
- MCP spec includes [Prompts](https://modelcontextprotocol.io/specification/2025-06-18/server/prompts), which enable servers to define reusable prompt templates and workflows that clients can easily surface to users and LLMs.
1067
+ MCP spec includes [Prompts](https://modelcontextprotocol.io/specification/latest/server/prompts), which enable servers to define reusable prompt templates and workflows that clients can easily surface to users and LLMs.
613
1068
 
614
1069
  The `MCP::Prompt` class provides three ways to create prompts:
615
1070
 
@@ -618,7 +1073,7 @@ The `MCP::Prompt` class provides three ways to create prompts:
618
1073
  ```ruby
619
1074
  class MyPrompt < MCP::Prompt
620
1075
  prompt_name "my_prompt" # Optional - defaults to underscored class name
621
- title "My Prompt" # WARNING: This is a `Draft` and is not supported in the `Version 2025-06-18 (latest)` specification.
1076
+ title "My Prompt"
622
1077
  description "This prompt performs specific functionality..."
623
1078
  arguments [
624
1079
  MCP::Prompt::Argument.new(
@@ -657,7 +1112,7 @@ prompt = MyPrompt
657
1112
  ```ruby
658
1113
  prompt = MCP::Prompt.define(
659
1114
  name: "my_prompt",
660
- title: "My Prompt", # WARNING: This is a `Draft` and is not supported in the `Version 2025-06-18 (latest)` specification.
1115
+ title: "My Prompt",
661
1116
  description: "This prompt performs specific functionality...",
662
1117
  arguments: [
663
1118
  MCP::Prompt::Argument.new(
@@ -745,34 +1200,9 @@ The server will handle prompt listing and execution through the MCP protocol met
745
1200
  - `prompts/list` - Lists all registered prompts and their schemas
746
1201
  - `prompts/get` - Retrieves and executes a specific prompt with arguments
747
1202
 
748
- ### Instrumentation
749
-
750
- The server allows registering a callback to receive information about instrumentation.
751
- To register a handler pass a proc/lambda to as `instrumentation_callback` into the server constructor.
752
-
753
- ```ruby
754
- MCP.configure do |config|
755
- config.instrumentation_callback = ->(data) {
756
- puts "Got instrumentation data #{data.inspect}"
757
- }
758
- end
759
- ```
760
-
761
- The data contains the following keys:
762
-
763
- - `method`: the method called, e.g. `ping`, `tools/list`, `tools/call` etc
764
- - `tool_name`: the name of the tool called
765
- - `prompt_name`: the name of the prompt called
766
- - `resource_uri`: the uri of the resource called
767
- - `error`: if looking up tools/prompts etc failed, e.g. `tool_not_found`
768
- - `duration`: the duration of the call in seconds
769
-
770
- `tool_name`, `prompt_name` and `resource_uri` are only populated if a matching handler is registered.
771
- This is to avoid potential issues with metric cardinality
772
-
773
1203
  ### Resources
774
1204
 
775
- MCP spec includes [Resources](https://modelcontextprotocol.io/specification/2025-06-18/server/resources).
1205
+ MCP spec includes [Resources](https://modelcontextprotocol.io/specification/latest/server/resources).
776
1206
 
777
1207
  ### Reading Resources
778
1208
 
@@ -782,7 +1212,7 @@ The `MCP::Resource` class provides a way to register resources with the server.
782
1212
  resource = MCP::Resource.new(
783
1213
  uri: "https://example.com/my_resource",
784
1214
  name: "my-resource",
785
- title: "My Resource", # WARNING: This is a `Draft` and is not supported in the `Version 2025-06-18 (latest)` specification.
1215
+ title: "My Resource",
786
1216
  description: "Lorem ipsum dolor sit amet",
787
1217
  mime_type: "text/html",
788
1218
  )
@@ -815,7 +1245,7 @@ The `MCP::ResourceTemplate` class provides a way to register resource templates
815
1245
  resource_template = MCP::ResourceTemplate.new(
816
1246
  uri_template: "https://example.com/my_resource_template",
817
1247
  name: "my-resource-template",
818
- title: "My Resource Template", # WARNING: This is a `Draft` and is not supported in the `Version 2025-06-18 (latest)` specification.
1248
+ title: "My Resource Template",
819
1249
  description: "Lorem ipsum dolor sit amet",
820
1250
  mime_type: "text/html",
821
1251
  )
@@ -835,7 +1265,11 @@ This class supports:
835
1265
  - Tool listing via the `tools/list` method (`MCP::Client#tools`)
836
1266
  - Tool invocation via the `tools/call` method (`MCP::Client#call_tools`)
837
1267
  - Resource listing via the `resources/list` method (`MCP::Client#resources`)
1268
+ - Resource template listing via the `resources/templates/list` method (`MCP::Client#resource_templates`)
838
1269
  - Resource reading via the `resources/read` method (`MCP::Client#read_resources`)
1270
+ - Prompt listing via the `prompts/list` method (`MCP::Client#prompts`)
1271
+ - Prompt retrieval via the `prompts/get` method (`MCP::Client#get_prompt`)
1272
+ - Completion requests via the `completion/complete` method (`MCP::Client#complete`)
839
1273
  - Automatic JSON-RPC 2.0 message formatting
840
1274
  - UUID request ID generation
841
1275
 
@@ -864,6 +1298,52 @@ class CustomTransport
864
1298
  end
865
1299
  ```
866
1300
 
1301
+ ### Stdio Transport Layer
1302
+
1303
+ Use the `MCP::Client::Stdio` transport to interact with MCP servers running as subprocesses over standard input/output.
1304
+
1305
+ `MCP::Client::Stdio.new` accepts the following keyword arguments:
1306
+
1307
+ | Parameter | Required | Description |
1308
+ |---|---|---|
1309
+ | `command:` | Yes | The command to spawn the server process (e.g., `"ruby"`, `"bundle"`, `"npx"`). |
1310
+ | `args:` | No | An array of arguments passed to the command. Defaults to `[]`. |
1311
+ | `env:` | No | A hash of environment variables to set for the server process. Defaults to `nil`. |
1312
+ | `read_timeout:` | No | Timeout in seconds for waiting for a server response. Defaults to `nil` (no timeout). |
1313
+
1314
+ Example usage:
1315
+
1316
+ ```ruby
1317
+ stdio_transport = MCP::Client::Stdio.new(
1318
+ command: "bundle",
1319
+ args: ["exec", "ruby", "path/to/server.rb"],
1320
+ env: { "API_KEY" => "my_secret_key" },
1321
+ read_timeout: 30
1322
+ )
1323
+ client = MCP::Client.new(transport: stdio_transport)
1324
+
1325
+ # List available tools.
1326
+ tools = client.tools
1327
+ tools.each do |tool|
1328
+ puts "Tool: #{tool.name} - #{tool.description}"
1329
+ end
1330
+
1331
+ # Call a specific tool.
1332
+ response = client.call_tool(
1333
+ tool: tools.first,
1334
+ arguments: { message: "Hello, world!" }
1335
+ )
1336
+
1337
+ # Close the transport when done.
1338
+ stdio_transport.close
1339
+ ```
1340
+
1341
+ The stdio transport automatically handles:
1342
+
1343
+ - Spawning the server process with `Open3.popen3`
1344
+ - MCP protocol initialization handshake (`initialize` request + `notifications/initialized`)
1345
+ - JSON-RPC 2.0 message framing over newline-delimited JSON
1346
+
867
1347
  ### HTTP Transport Layer
868
1348
 
869
1349
  Use the `MCP::Client::HTTP` transport to interact with MCP servers using simple HTTP requests.
@@ -896,8 +1376,17 @@ response = client.call_tool(
896
1376
  tool: tools.first,
897
1377
  arguments: { message: "Hello, world!" }
898
1378
  )
1379
+
1380
+ # Call a tool with progress tracking.
1381
+ response = client.call_tool(
1382
+ tool: tools.first,
1383
+ arguments: { count: 10 },
1384
+ progress_token: "my-progress-token"
1385
+ )
899
1386
  ```
900
1387
 
1388
+ The server will send `notifications/progress` back to the client during execution.
1389
+
901
1390
  #### HTTP Authorization
902
1391
 
903
1392
  By default, the HTTP transport layer provides no authentication to the server, but you can provide custom headers if you need authentication. For example, to use Bearer token authentication:
@@ -924,15 +1413,13 @@ The client provides a wrapper class for tools returned by the server:
924
1413
 
925
1414
  This class provides easy access to tool properties like name, description, input schema, and output schema.
926
1415
 
927
- ## Releases
1416
+ ## Conformance Testing
928
1417
 
929
- This gem is published to [RubyGems.org](https://rubygems.org/gems/mcp)
1418
+ The `conformance/` directory contains a test server and runner that validate the SDK against the MCP specification using [`@modelcontextprotocol/conformance`](https://github.com/modelcontextprotocol/conformance).
930
1419
 
931
- Releases are triggered by PRs to the `main` branch updating the version number in `lib/mcp/version.rb`.
1420
+ See [conformance/README.md](conformance/README.md) for usage instructions.
932
1421
 
933
- 1. **Update the version number** in `lib/mcp/version.rb`, following [semver](https://semver.org/)
934
- 1. **Update CHANGELOG.md**, backfilling the changes since the last release if necessary, and adding a new section for the new version, clearing out the Unreleased section
935
- 1. **Create a PR and get approval from a maintainer**
936
- 1. **Merge your PR to the main branch** - This will automatically trigger the release workflow via GitHub Actions
1422
+ ## Documentation
937
1423
 
938
- When changes are merged to the `main` branch, the GitHub Actions workflow (`.github/workflows/release.yml`) is triggered and the gem is published to RubyGems.
1424
+ - [SDK API documentation](https://rubydoc.info/gems/mcp)
1425
+ - [Model Context Protocol documentation](https://modelcontextprotocol.io)