model-context-protocol-rb 0.6.0 → 0.7.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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +26 -2
  3. data/README.md +174 -978
  4. data/lib/model_context_protocol/rspec/helpers.rb +54 -0
  5. data/lib/model_context_protocol/rspec/matchers/be_mcp_error_response.rb +123 -0
  6. data/lib/model_context_protocol/rspec/matchers/be_valid_mcp_class.rb +103 -0
  7. data/lib/model_context_protocol/rspec/matchers/be_valid_mcp_prompt_response.rb +126 -0
  8. data/lib/model_context_protocol/rspec/matchers/be_valid_mcp_resource_response.rb +121 -0
  9. data/lib/model_context_protocol/rspec/matchers/be_valid_mcp_tool_response.rb +135 -0
  10. data/lib/model_context_protocol/rspec/matchers/have_audio_content.rb +109 -0
  11. data/lib/model_context_protocol/rspec/matchers/have_embedded_resource_content.rb +150 -0
  12. data/lib/model_context_protocol/rspec/matchers/have_image_content.rb +109 -0
  13. data/lib/model_context_protocol/rspec/matchers/have_message_count.rb +87 -0
  14. data/lib/model_context_protocol/rspec/matchers/have_message_with_role.rb +152 -0
  15. data/lib/model_context_protocol/rspec/matchers/have_resource_annotations.rb +135 -0
  16. data/lib/model_context_protocol/rspec/matchers/have_resource_blob.rb +108 -0
  17. data/lib/model_context_protocol/rspec/matchers/have_resource_link_content.rb +138 -0
  18. data/lib/model_context_protocol/rspec/matchers/have_resource_mime_type.rb +103 -0
  19. data/lib/model_context_protocol/rspec/matchers/have_resource_text.rb +112 -0
  20. data/lib/model_context_protocol/rspec/matchers/have_structured_content.rb +88 -0
  21. data/lib/model_context_protocol/rspec/matchers/have_text_content.rb +113 -0
  22. data/lib/model_context_protocol/rspec/matchers.rb +31 -0
  23. data/lib/model_context_protocol/rspec.rb +23 -0
  24. data/lib/model_context_protocol/server/client_logger.rb +1 -1
  25. data/lib/model_context_protocol/server/configuration.rb +195 -91
  26. data/lib/model_context_protocol/server/content_helpers.rb +1 -1
  27. data/lib/model_context_protocol/server/prompt.rb +0 -14
  28. data/lib/model_context_protocol/server/redis_client_proxy.rb +2 -14
  29. data/lib/model_context_protocol/server/redis_config.rb +5 -7
  30. data/lib/model_context_protocol/server/redis_pool_manager.rb +10 -13
  31. data/lib/model_context_protocol/server/registry.rb +8 -0
  32. data/lib/model_context_protocol/server/router.rb +279 -4
  33. data/lib/model_context_protocol/server/server_logger.rb +5 -2
  34. data/lib/model_context_protocol/server/stdio_configuration.rb +114 -0
  35. data/lib/model_context_protocol/server/stdio_transport/request_store.rb +0 -41
  36. data/lib/model_context_protocol/server/streamable_http_configuration.rb +218 -0
  37. data/lib/model_context_protocol/server/streamable_http_transport/event_counter.rb +0 -13
  38. data/lib/model_context_protocol/server/streamable_http_transport/notification_queue.rb +0 -41
  39. data/lib/model_context_protocol/server/streamable_http_transport/request_store.rb +0 -103
  40. data/lib/model_context_protocol/server/streamable_http_transport/server_request_store.rb +0 -64
  41. data/lib/model_context_protocol/server/streamable_http_transport/session_message_queue.rb +0 -58
  42. data/lib/model_context_protocol/server/streamable_http_transport/session_store.rb +17 -31
  43. data/lib/model_context_protocol/server/streamable_http_transport/stream_registry.rb +0 -34
  44. data/lib/model_context_protocol/server/streamable_http_transport.rb +192 -56
  45. data/lib/model_context_protocol/server/tool.rb +67 -1
  46. data/lib/model_context_protocol/server.rb +203 -262
  47. data/lib/model_context_protocol/version.rb +1 -1
  48. data/lib/model_context_protocol.rb +4 -1
  49. data/lib/puma/plugin/mcp.rb +39 -0
  50. data/tasks/mcp.rake +26 -0
  51. data/tasks/templates/dev-http-puma.erb +251 -0
  52. data/tasks/templates/dev-http.erb +166 -184
  53. data/tasks/templates/dev.erb +29 -7
  54. metadata +26 -2
data/README.md CHANGED
@@ -4,27 +4,6 @@ An implementation of the [Model Context Protocol (MCP)](https://spec.modelcontex
4
4
 
5
5
  Provides simple abstractions that allow you to serve prompts, resources, resource templates, and tools via MCP locally (stdio) or in production (streamable HTTP backed by Redis) with minimal effort.
6
6
 
7
- ## Table of Contents
8
-
9
- - [Feature Support (Server)](#feature-support-server)
10
- - [Quick Start with Rails](#quick-start-with-rails)
11
- - [Installation](#installation)
12
- - [Building an MCP Server](#building-an-mcp-server)
13
- - [Server Configuration Options](#server-configuration-options)
14
- - [Pagination Configuration Options](#pagination-configuration-options)
15
- - [Transport Configuration Options](#transport-configuration-options)
16
- - [Redis Configuration](#redis-configuration)
17
- - [Server Logging Configuration](#server-logging-configuration)
18
- - [Registry Configuration Options](#registry-configuration-options)
19
- - [Prompts](#prompts)
20
- - [Resources](#resources)
21
- - [Resource Templates](#resource-templates)
22
- - [Tools](#tools)
23
- - [Completions](#completions)
24
- - [Development](#development)
25
- - [Contributing](#contributing)
26
- - [License](#license)
27
-
28
7
  ## Feature Support (Server)
29
8
 
30
9
  | Status | Feature |
@@ -39,130 +18,14 @@ Provides simple abstractions that allow you to serve prompts, resources, resourc
39
18
  | ✅ | [Environment Variables](https://modelcontextprotocol.io/legacy/tools/debugging#environment-variables) |
40
19
  | ✅ | [STDIO Transport](https://modelcontextprotocol.io/specification/2025-06-18/basic/transports#stdio) |
41
20
  | ✅ | [Streamable HTTP Transport](https://modelcontextprotocol.io/specification/2025-06-18/basic/transports#streamable-http) |
42
- | | [List Changed Notification (Prompts)](https://modelcontextprotocol.io/specification/2025-06-18/server/prompts#list-changed-notification) |
43
- | | [List Changed Notification (Resources)](https://modelcontextprotocol.io/specification/2025-06-18/server/resources#list-changed-notification) |
21
+ | | [List Changed Notification (Prompts)](https://modelcontextprotocol.io/specification/2025-06-18/server/prompts#list-changed-notification) ¹ |
22
+ | | [List Changed Notification (Resources)](https://modelcontextprotocol.io/specification/2025-06-18/server/resources#list-changed-notification) ¹ |
44
23
  | ❌ | [Subscriptions (Resources)](https://modelcontextprotocol.io/specification/2025-06-18/server/resources#subscriptions) |
45
- | | [List Changed Notification (Tools)](https://modelcontextprotocol.io/specification/2025-06-18/server/tools#list-changed-notification) |
24
+ | | [List Changed Notification (Tools)](https://modelcontextprotocol.io/specification/2025-06-18/server/tools#list-changed-notification) ¹ |
46
25
  | ✅ | [Cancellation](https://modelcontextprotocol.io/specification/2025-06-18/basic/utilities/cancellation) |
47
26
  | ✅ | [Ping](https://modelcontextprotocol.io/specification/2025-06-18/basic/utilities/ping) |
48
27
  | ✅ | [Progress](https://modelcontextprotocol.io/specification/2025-06-18/basic/utilities/progress) |
49
28
 
50
- ## Quick Start with Rails
51
-
52
- The `model-context-protocol-rb` works out of the box with any valid Rack request. Currently, this project has no plans for building a deeper Rails integration, but it is fairly simple to build it out yourself. To support modern application deployments across multiple servers, the streamable HTTP transport requires Redis as an external dependency.
53
-
54
- Here's an example of how you can easily integrate with Rails.
55
-
56
- First, configure Redis in an initializer:
57
-
58
- ```ruby
59
- # config/initializers/model_context_protocol.rb
60
- require "model_context_protocol"
61
-
62
- ModelContextProtocol::Server.configure_redis do |config|
63
- config.redis_url = ENV.fetch("REDIS_URL", "redis://localhost:6379/0")
64
- config.pool_size = 20
65
- config.pool_timeout = 5
66
- config.enable_reaper = true
67
- config.reaper_interval = 60
68
- config.idle_timeout = 300
69
- end
70
- ```
71
-
72
- Then, set the routes:
73
-
74
- ```ruby
75
- constraints format: :json do
76
- get "/mcp", to: "model_context_protocol#handle", as: :mcp_get
77
- post "/mcp", to: "model_context_protocol#handle", as: :mcp_post
78
- delete "/mcp", to: "model_context_protocol#handle", as: :mcp_delete
79
- end
80
- ```
81
-
82
- Then, implement a controller endpoint to handle the requests.
83
-
84
- ```ruby
85
- class ModelContextProtocolController < ActionController::API
86
- include ActionController::Live
87
-
88
- before_action :authenticate_user
89
-
90
- def handle
91
- server = ModelContextProtocol::Server.new do |config|
92
- config.name = "MyMCPServer"
93
- config.title = "My MCP Server"
94
- config.version = "1.0.0"
95
- config.registry = build_registry
96
- config.context = {
97
- user_id: current_user.id,
98
- request_id: request.id
99
- }
100
- config.transport = {
101
- type: :streamable_http,
102
- env: request.env
103
- }
104
- config.instructions = <<~INSTRUCTIONS
105
- This server provides prompts, tools, and resources for interacting with my app.
106
-
107
- Key capabilities:
108
- - Does this one thing
109
- - Does this other thing
110
- - Oh, yeah, and it does that one thing, too
111
-
112
- Use this server when you need to do stuff.
113
- INSTRUCTIONS
114
- end
115
-
116
- result = server.start
117
- handle_mcp_response(result)
118
- end
119
-
120
- private
121
-
122
- def build_registry
123
- ModelContextProtocol::Server::Registry.new do
124
- tools do
125
- # Implement user authorization logic to dynamically build registry
126
- register TestTool if current_user.authorized_for?(TestTool)
127
- end
128
- end
129
- end
130
-
131
- def handle_mcp_response(result)
132
- if result[:headers]&.dig("Content-Type") == "text/event-stream"
133
- setup_streaming_headers
134
- stream_response(result[:stream_proc])
135
- else
136
- render_json_response(result)
137
- end
138
- end
139
-
140
- def setup_streaming_headers
141
- response.headers.merge!(
142
- "Content-Type" => "text/event-stream",
143
- "Cache-Control" => "no-cache",
144
- "Connection" => "keep-alive"
145
- )
146
- end
147
-
148
- def stream_response(stream_proc)
149
- stream_proc&.call(response.stream)
150
- ensure
151
- response.stream.close rescue nil
152
- end
153
-
154
- def render_json_response(result)
155
- render json: result[:json],
156
- status: result[:status] || 200,
157
- headers: result[:headers] || {}
158
- end
159
- end
160
- ```
161
-
162
- Read more about the [server configuration options](building-an-mcp-server) to better understand how you can customize your MCP server.
163
-
164
- From here, you can get started building [prompts](#prompts), [resources](#resources), [resource templates](#resource-templates), and [tools](#tools).
165
-
166
29
  ## Installation
167
30
 
168
31
  Add this line to your application's Gemfile:
@@ -183,644 +46,249 @@ Or install it yourself as:
183
46
  gem install model-context-protocol-rb
184
47
  ```
185
48
 
186
- ## Building an MCP Server
49
+ For detailed installation instructions, see the [Installation wiki page](https://github.com/dickdavis/model-context-protocol-rb/wiki/Installation).
187
50
 
188
- Build a simple MCP server by registering your prompts, resources, resource templates, and tools. Then, configure and run the server. Messages from the MCP client will be routed to the appropriate custom handler. This SDK provides several classes that should be used to build your handlers.
51
+ ## Building an MCP Server
189
52
 
190
- ```ruby
191
- server = ModelContextProtocol::Server.new do |config|
192
- # Name of the MCP server (intended for programmatic use)
193
- config.name = "MCPDevelopmentServer"
53
+ Build a simple MCP server by registering your prompts, resources, resource templates, and tools. Messages from the MCP client will be routed to the appropriate custom handler.
194
54
 
195
- # Version of the MCP server
196
- config.version = "1.0.0"
55
+ ### STDIO Transport (Default)
197
56
 
198
- # Optional: human-readable display name for the MCP server
199
- config.title = "My Awesome Server"
57
+ For command-line MCP servers that communicate via standard input/output:
200
58
 
201
- # Optional: instuctions for how the MCP server should be used by LLMs
59
+ ```ruby
60
+ server = ModelContextProtocol::Server.with_stdio_transport do |config|
61
+ config.name = "MyMCPServer"
62
+ config.title = "My Server"
63
+ config.version = "1.0.0"
202
64
  config.instructions = <<~INSTRUCTIONS
203
- This server provides file system access and development tools.
65
+ This server provides prompts, tools, and resources for interacting with my app.
204
66
 
205
67
  Key capabilities:
206
- - Read and write files in the project directory
207
- - Execute shell commands for development tasks
208
- - Analyze code structure and dependencies
68
+ - Does something
69
+ - Does something else
70
+ - Oh, and it does this other thing, too
209
71
 
210
- Use this server when you need to interact with the local development environment.
72
+ Use this server when you need to do things.
211
73
  INSTRUCTIONS
212
74
 
213
- # Configure pagination options for the following methods:
214
- # prompts/list, resources/list, resource_template/list, tools/list
215
- config.pagination = {
216
- default_page_size: 50, # Default items per page
217
- max_page_size: 500, # Maximum allowed page size
218
- cursor_ttl: 1800 # Cursor expiry in seconds (30 minutes)
219
- }
220
-
221
- # Disable pagination support (enabled by default)
222
- # config.pagination = false
223
-
224
- # Optional: require specific environment variables to be set
225
- config.require_environment_variable("API_KEY")
226
-
227
- # Optional: set environment variables programmatically
228
- config.set_environment_variable("DEBUG_MODE", "true")
229
-
230
- # Optional: provide prompts, resources, and tools with contextual variables
231
- config.context = {
232
- user_id: "123456",
233
- request_id: SecureRandom.uuid
234
- }
235
-
236
- # Optional: explicitly specify STDIO as the transport
237
- # This is not necessary as STDIO is the default transport
238
- # config.transport = { type: :stdio }
239
-
240
- # Optional: configure streamable HTTP transport if required
241
- # config.transport = {
242
- # type: :streamable_http,
243
- # env: request.env,
244
- # session_ttl: 3600 # Optional: session timeout in seconds (default: 3600)
245
- # }
246
-
247
- # Register prompts, resources, resource templates, and tools
248
- config.registry = ModelContextProtocol::Server::Registry.new do
75
+ config.registry do
249
76
  prompts do
250
- register TestPrompt
77
+ register MyPrompt
251
78
  end
252
79
 
253
80
  resources do
254
- register TestResource
255
- end
256
-
257
- resource_templates do
258
- register TestResourceTemplate
81
+ register MyResource
259
82
  end
260
83
 
261
84
  tools do
262
- register TestTool
85
+ register MyTool
263
86
  end
264
87
  end
265
88
  end
266
89
 
267
- # Start the MCP server
268
90
  server.start
269
91
  ```
270
92
 
271
- ### Server Configuration Options
93
+ ### Streamable HTTP Transport
272
94
 
273
- The following table details all available configuration options for the MCP server:
95
+ For HTTP-based MCP servers (e.g., Rails applications), you'll need to setup your MCP server in an initializer with Redis configuration. Redis enables coordination across multiple server processes in production deployments (e.g., Puma workers), where no single process can hold all state in memory.
274
96
 
275
- | Option | Type | Required | Default | Description |
276
- |--------|------|----------|---------|-------------|
277
- | `name` | String | Yes | - | Name of the MCP server for programmatic use |
278
- | `version` | String | Yes | - | Version of the MCP server |
279
- | `title` | String | No | - | Human-readable display name for the MCP server |
280
- | `instructions` | String | No | - | Instructions for how the MCP server should be used by LLMs |
281
- | `pagination` | Hash/Boolean | No | See pagination table | Pagination configuration (or `false` to disable) |
282
- | `context` | Hash | No | `{}` | Contextual variables available to prompts, resources, and tools |
283
- | `transport` | Hash | No | `{ type: :stdio }` | Transport configuration |
284
- | `registry` | Registry | Yes | - | Registry containing prompts, resources, and tools |
285
-
286
- ### Pagination Configuration Options
97
+ ```ruby
98
+ # config/initializers/model_context_protocol.rb
99
+ require "model_context_protocol"
287
100
 
288
- When `pagination` is set to a Hash, the following options are available:
101
+ # Assuming your handlers will be organized in the app/mcp folder.
102
+ Dir[Rails.root.join("app/mcp/**/*.rb")].sort.each { |f| require f }
289
103
 
290
- | Option | Type | Required | Default | Description |
291
- |--------|------|----------|---------|-------------|
292
- | `default_page_size` | Integer | No | `50` | Default number of items per page |
293
- | `max_page_size` | Integer | No | `500` | Maximum allowed page size |
294
- | `cursor_ttl` | Integer | No | `1800` | Cursor expiry time in seconds (30 minutes) |
104
+ ModelContextProtocol::Server.with_streamable_http_transport do |config|
105
+ config.name = "MyServer"
106
+ config.title = "My Server"
107
+ config.version = "1.0.0"
108
+ config.instructions = <<~INSTRUCTIONS
109
+ This server provides prompts, tools, and resources for interacting with my app.
295
110
 
296
- **Note:** Set `config.pagination = false` to completely disable pagination support.
111
+ Key capabilities:
112
+ - Does something
113
+ - Does something else
114
+ - Oh, and it does this other thing, too
297
115
 
298
- ### Transport Configuration Options
116
+ Use this server when you need to do things.
117
+ INSTRUCTIONS
299
118
 
300
- The transport configuration supports two types: `:stdio` (default) and `:streamable_http`.
119
+ config.redis_url = ENV.fetch("REDIS_URL", "redis://localhost:6379/0")
120
+ config.redis_pool_size = 20
121
+ config.redis_pool_timeout = 5
122
+ config.redis_enable_reaper = true
123
+ config.redis_reaper_interval = 60
124
+ config.redis_idle_timeout = 300
125
+ config.redis_ssl_params = {verify_mode: OpenSSL::SSL::VERIFY_NONE}
126
+
127
+ config.registry do
128
+ prompts do
129
+ register MyPrompt
130
+ end
301
131
 
302
- #### STDIO Transport
132
+ resources do
133
+ register MyResource
134
+ end
303
135
 
304
- ```ruby
305
- config.transport = { type: :stdio } # This is the default, can be omitted
136
+ tools do
137
+ register MyTool
138
+ end
139
+ end
140
+ end
306
141
  ```
307
142
 
308
- #### Streamable HTTP Transport
143
+ If your app uses Puma (>= 7.0) as the web server, use the provided plugin to start the MCP server.
309
144
 
310
145
  ```ruby
311
- config.transport = { type: :streamable_http, env: request.env }
146
+ # config/puma.rb
147
+ plugin :mcp
312
148
  ```
313
149
 
314
- When using `:streamable_http` transport, the following options are available:
315
-
316
- | Option | Type | Required | Default | Description |
317
- |--------|------|----------|---------|-------------|
318
- | `type` | Symbol | Yes | `:stdio` | Must be `:streamable_http` for HTTP transport |
319
- | `session_ttl` | Integer | No | `3600` | Session timeout in seconds (1 hour) |
320
- | `env` | Hash | No | - | Rack environment hash (for Rails integration) |
321
-
322
- ### Redis Configuration
323
-
324
- The `:streamable_http` transport requires Redis to be configured globally before use:
150
+ For Puma 6.x or other web servers, you'll need to manually implement logic for starting and shutting the MCP server appropriately. This example shows how to do so for Puma 6.x; you can adapt it for your server of choice:
325
151
 
326
152
  ```ruby
327
- ModelContextProtocol::Server.configure_redis do |config|
328
- config.redis_url = ENV.fetch('REDIS_URL')
329
- config.pool_size = 20
330
- config.pool_timeout = 5
331
- config.enable_reaper = true
332
- config.reaper_interval = 60
333
- config.idle_timeout = 300
334
- end
335
- ```
153
+ # config/puma.rb
336
154
 
337
- | Option | Type | Required | Default | Description |
338
- |--------|------|----------|---------|-------------|
339
- | `redis_url` | String | Yes | - | Redis connection URL |
340
- | `pool_size` | Integer | No | `20` | Connection pool size |
341
- | `pool_timeout` | Integer | No | `5` | Pool checkout timeout in seconds |
342
- | `enable_reaper` | Boolean | No | `true` | Enable connection reaping |
343
- | `reaper_interval` | Integer | No | `60` | Reaper check interval in seconds |
344
- | `idle_timeout` | Integer | No | `300` | Idle connection timeout in seconds |
155
+ workers ENV.fetch("WEB_CONCURRENCY", 0).to_i
345
156
 
346
- ### Server Logging Configuration
157
+ mcp_start = ->(*) { ModelContextProtocol::Server.start unless ModelContextProtocol::Server.running? }
158
+ mcp_shutdown = ->(*) { ModelContextProtocol::Server.shutdown }
347
159
 
348
- Server logging can be configured globally to customize how your MCP server writes debug and operational logs. This logging is separate from client logging (which sends messages to MCP clients via the protocol) and is used for server-side debugging, monitoring, and troubleshooting:
160
+ # Clustered mode: workers fork, so start threads after fork
161
+ on_worker_boot(&mcp_start)
162
+ on_worker_shutdown(&mcp_shutdown)
349
163
 
350
- ```ruby
351
- ModelContextProtocol::Server.configure_server_logging do |config|
352
- config.logdev = $stderr # or a file path like '/var/log/mcp-server.log'
353
- config.level = Logger::INFO # Logger::DEBUG, Logger::INFO, Logger::WARN, Logger::ERROR, Logger::FATAL
354
- config.progname = "MyMCPServer" # Program name for log entries
355
- config.formatter = proc do |severity, datetime, progname, msg|
356
- "#{datetime.strftime('%Y-%m-%d %H:%M:%S')} #{severity} [#{progname}] #{msg}\n"
357
- end
164
+ # Single mode: start directly (no forking)
165
+ if ENV.fetch("WEB_CONCURRENCY", 0).to_i.zero?
166
+ mcp_start.call
167
+ at_exit(&mcp_shutdown)
358
168
  end
359
169
  ```
360
170
 
361
- | Option | Type | Required | Default | Description |
362
- |--------|------|----------|---------|-------------|
363
- | `logdev` | IO/String | No | `$stderr` | Log destination (IO object or file path) |
364
- | `level` | Integer | No | `Logger::INFO` | Minimum log level to output |
365
- | `progname` | String | No | `"MCP-Server"` | Program name for log entries |
366
- | `formatter` | Proc | No | Default timestamp format | Custom log formatter |
367
-
368
- **Note:** When using `:stdio` transport, server logging must not use `$stdout` as it conflicts with the MCP protocol communication. Use `$stderr` or a file instead.
369
-
370
- ### Registry Configuration Options
171
+ Then, implement a controller action to handle the requests to the MCP server.
371
172
 
372
- The registry is configured using `ModelContextProtocol::Server::Registry.new` and supports the following block types:
373
-
374
- | Block Type | Options | Description |
375
- |------------|---------|-------------|
376
- | `prompts` | `list_changed: Boolean` | Register prompt handlers with optional list change notifications |
377
- | `resources` | `list_changed: Boolean`, `subscribe: Boolean` | Register resource handlers with optional list change notifications and subscriptions |
378
- | `resource_templates` | - | Register resource template handlers |
379
- | `tools` | `list_changed: Boolean` | Register tool handlers with optional list change notifications |
380
-
381
- Within each block, use `register ClassName` to register your handlers.
382
-
383
- **Note:** The `list_changed` and `subscribe` options are accepted for capability advertisement but the list changed notification functionality is not yet implemented (see [Feature Support](#feature-support-server)).
384
-
385
- **Example:**
386
173
  ```ruby
387
- config.registry = ModelContextProtocol::Server::Registry.new do
388
- prompts do
389
- register MyPrompt
390
- register AnotherPrompt
391
- end
174
+ class ModelContextProtocolController < ActionController::API
175
+ include ActionController::Live
392
176
 
393
- resources do
394
- register MyResource
395
- end
177
+ def handle
178
+ result = ModelContextProtocol::Server.serve(
179
+ env: request.env,
180
+ session_context: { user_id: current_user.id }
181
+ )
182
+
183
+ response.headers.merge!(result[:headers]) if result[:headers]
396
184
 
397
- tools do
398
- register MyTool
185
+ if result[:stream]
186
+ result[:stream_proc]&.call(response.stream)
187
+ else
188
+ render json: result[:json], status: result[:status] || 200
189
+ end
190
+ ensure
191
+ response.stream.close rescue nil
399
192
  end
400
193
  end
401
194
  ```
402
195
 
403
- ---
404
-
405
- ## Prompts
406
-
407
- The `ModelContextProtocol::Server::Prompt` base class allows subclasses to define a prompt that the MCP client can use.
408
-
409
- Define the prompt properties and then implement the `call` method to build your prompt. Any arguments passed to the prompt from the MCP client will be available in the `arguments` hash with symbol keys (e.g., `arguments[:argument_name]`), and any context values provided in the server configuration will be available in the `context` hash. Use the `respond_with` instance method to ensure your prompt responds with appropriately formatted response data.
410
-
411
- You can also send MCP log messages to clients from within your prompt by calling a valid logger level method on the `client_logger` and passing a string message. For server-side debugging and monitoring, use the `server_logger` to write logs that are not sent to clients.
412
-
413
- ### Prompt Definition
414
-
415
- Use the `define` block to set [prompt properties](https://spec.modelcontextprotocol.io/specification/2025-06-18/server/prompts/) and configure arguments.
416
-
417
- | Property | Description |
418
- |----------|-------------|
419
- | `name` | The programmatic name of the prompt |
420
- | `title` | Human-readable display name |
421
- | `description` | Short description of what the prompt does |
422
- | `argument` | Define an argument block with name, description, required flag, and completion |
423
-
424
- ### Argument Definition
425
-
426
- Define any arguments using `argument` blocks nested within the `define` block. You can mark an argument as required, and you can optionally provide a completion class. See [Completions](#completions) for more information.
427
-
428
- | Property | Description |
429
- |----------|-------------|
430
- | `name` | The name of the argument |
431
- | `description` | A short description of the argument |
432
- | `required` | Whether the argument is required (boolean) |
433
- | `completion` | Available hints for completions (array or completion class) |
434
-
435
- ### Prompt Methods
436
-
437
- Define your prompt properties and arguments, implement the `call` method using the `message_history` DSL to build prompt messages and `respond_with` to serialize them. You can wrap long running operations in a `cancellable` block to allow clients to cancel the request. Also, you can automatically send progress notifications to clients by wrapping long-running operations in a `progressable` block.
438
-
439
- | Method | Context | Description |
440
- |--------|---------|-------------|
441
- | `define` | Class definition | Block for defining prompt metadata and arguments |
442
- | `call` | Instance method | Main method to implement prompt logic and build response |
443
- | `cancellable` | Within `call` | Wrap long-running operations to allow client cancellation (e.g., `cancellable { slow_operation }`) |
444
- | `progressable` | Within `call` | Wrap long-running operations to send clients progress notifications (e.g., `progressable { slow_operation }`) |
445
- | `message_history` | Within `call` | DSL method to build an array of user and assistant messages |
446
- | `respond_with` | Within `call` | Return properly formatted response data (e.g., `respond_with messages:`) |
196
+ Lastly, add the necessary routes:
447
197
 
448
- ### Message History DSL
449
-
450
- Build a message history using the an intuitive DSL, creating an ordered history of user and assistant messages with flexible content blocks that can include text, image, audio, embedded resources, and resource links.
451
-
452
- | Method | Context | Description |
453
- |--------|---------|-------------|
454
- | `user_message` | Within `message_history` | Create a message with user role |
455
- | `assistant_message` | Within `message_history` | Create a message with assistant role |
456
-
457
- ### Content Blocks
458
-
459
- Use content blocks to properly format the content included in messages.
198
+ ```ruby
199
+ get "/", to: "model_context_protocol#handle", as: :mcp_get
200
+ post "/", to: "model_context_protocol#handle", as: :mcp_post
201
+ delete "/", to: "model_context_protocol#handle", as: :mcp_delete
202
+ ```
460
203
 
461
- | Method | Context | Description |
462
- |--------|---------|-------------|
463
- | `text_content` | Within message blocks | Create text content block |
464
- | `image_content` | Within message blocks | Create image content block (requires `data:` and `mime_type:`) |
465
- | `audio_content` | Within message blocks | Create audio content block (requires `data:` and `mime_type:`) |
466
- | `embedded_resource_content` | Within message blocks | Create embedded resource content block (requires `resource:`) |
467
- | `resource_link` | Within message blocks | Create resource link content block (requires `name:` and `uri:`) |
204
+ For a complete Rails integration example, see the [Quick Start with Rails](https://github.com/dickdavis/model-context-protocol-rb/wiki/Quick-Start-with-Rails) guide.
468
205
 
469
- ### Available Instance Variables
206
+ For complete configuration details including all server options, Redis configuration, and logging options, see the [Building an MCP Server](https://github.com/dickdavis/model-context-protocol-rb/wiki/Building-an-MCP-Server) wiki page.
470
207
 
471
- The `arguments` passed from an MCP client are available, as well as the `context` values passed in at server initialization.
208
+ ## Claude Code Plugin
472
209
 
473
- | Variable | Context | Description |
474
- |----------|---------|-------------|
475
- | `arguments` | Within `call` | Hash containing client-provided arguments (symbol keys) |
476
- | `context` | Within `call` | Hash containing server configuration context values |
477
- | `client_logger` | Within `call` | Client logger instance for sending MCP log messages (e.g., `client_logger.info("message")`) |
478
- | `server_logger` | Within `call` | Server logger instance for debugging and monitoring (e.g., `server_logger.debug("message")`) |
210
+ The [mcp-rb](https://github.com/dickdavis/model-context-protocol-rb-plugin) Claude Code plugin teaches Claude how to build MCP handlers and configure servers using this gem's DSL. It provides skills for creating tools, prompts, resources, and server configurations.
479
211
 
480
- ### Examples
212
+ ## Prompts
481
213
 
482
- This is an example prompt that returns a properly formatted response:
214
+ Define prompts that MCP clients can use to generate contextual message sequences.
483
215
 
484
216
  ```ruby
485
217
  class TestPrompt < ModelContextProtocol::Server::Prompt
486
218
  define do
487
- # The name of the prompt for programmatic use
488
219
  name "brainstorm_excuses"
489
- # The human-readable prompt name for display in UI
490
- title "Brainstorm Excuses"
491
- # A short description of what the tool does
492
- description "A prompt for brainstorming excuses to get out of something"
493
-
494
- # Define arguments to be used with your prompt
495
- argument do
496
- # The name of the argument
497
- name "tone"
498
- # A short description of the argument
499
- description "The general tone to be used in the generated excuses"
500
- # If the argument is required
501
- required false
502
- # Available hints for completions
503
- completion ["whiny", "angry", "callous", "desperate", "nervous", "sneaky"]
504
- end
505
-
506
- argument do
507
- name "undesirable_activity"
508
- description "The thing to get out of"
509
- required true
510
- end
220
+ description "A prompt for brainstorming excuses"
221
+ argument { name "tone"; required false }
511
222
  end
512
223
 
513
- # You can optionally define a custom completion for an argument and pass it to completions.
514
- # ToneCompletion = ModelContextProtocol::Server::Completion.define do
515
- # hints = ["whiny", "angry", "callous", "desperate", "nervous", "sneaky"]
516
- # values = hints.grep(/#{argument_value}/)
517
- # respond_with values:
518
- # end
519
- # ...
520
- # define do
521
- # argument do
522
- # name "tone"
523
- # description "The general tone to be used in the generated excuses"
524
- # required false
525
- # completion ToneCompletion
526
- # end
527
- # end
528
-
529
- # The call method is invoked by the MCP Server to generate a response to resource/read requests
530
224
  def call
531
- # You can use the client_logger
532
- client_logger.info("Brainstorming excuses...")
533
-
534
- # Server logging for debugging and monitoring (not sent to client)
535
- server_logger.debug("Prompt called with arguments: #{arguments}")
536
- server_logger.info("Generating excuse brainstorming prompt")
537
-
538
- # Build an array of user and assistant messages
539
225
  messages = message_history do
540
- # Create a message with the user role
541
- user_message do
542
- # Use any type of content block in a message (text, image, audio, embedded_resource, or resource_link)
543
- text_content(text: "My wife wants me to: #{arguments[:undesirable_activity]}... Can you believe it?")
544
- end
545
-
546
- # You can also create messages with the assistant role
547
- assistant_message do
548
- text_content(text: "Oh, that's just downright awful. How can I help?")
549
- end
550
-
551
- user_message do
552
- # Reference any inputs from the client by accessing the appropriate key in the arguments hash
553
- text_content(text: "Can you generate some excuses for me?" + (arguments[:tone] ? " Make them as #{arguments[:tone]} as possible." : ""))
554
- end
226
+ user_message { text_content(text: "Generate excuses with #{arguments[:tone]} tone") }
555
227
  end
556
-
557
- # Respond with the messages
558
228
  respond_with messages:
559
229
  end
560
230
  end
561
231
  ```
562
232
 
563
- ---
564
-
565
- ## Resources
566
-
567
- The `ModelContextProtocol::Server::Resource` base class allows subclasses to define a resource that the MCP client can use.
568
-
569
- Define the resource properties and optionally annotations, then implement the `call` method to build your resource. Use the `respond_with` instance method to ensure your resource responds with appropriately formatted response data.
570
-
571
- You can also send MCP log messages to clients from within your resource by calling a valid logger level method on the `client_logger` and passing a string message. For server-side debugging and monitoring, use the `server_logger` to write logs that are not sent to clients.
572
-
573
- ### Resource Definition
574
-
575
- Use the `define` block to set [resource properties](https://spec.modelcontextprotocol.io/specification/2025-06-18/server/resources/) and configure annotations.
576
-
577
- | Property | Description |
578
- |----------|-------------|
579
- | `name` | The name of the resource |
580
- | `title` | Human-readable display name |
581
- | `description` | Short description of what the resource contains |
582
- | `mime_type` | MIME type of the resource content |
583
- | `uri` | URI identifier for the resource |
584
- | `annotations` | Block for defining resource annotations |
585
-
586
- ### Annotation Definition
587
-
588
- Define any [resource annotations](https://modelcontextprotocol.io/specification/2025-06-18/server/resources#annotations) using an `annotations` block nested within the `define` block.
233
+ Key features:
234
+ - Define arguments with validation and completion hints
235
+ - Build message histories with user and assistant messages
236
+ - Support for text, image, audio, and embedded resource content
589
237
 
590
- | Property | Description |
591
- |----------|-------------|
592
- | `audience` | Target audience for the resource (array of symbols like `[:user, :assistant]`) |
593
- | `priority` | Priority level (numeric value, e.g., `0.9`) |
594
- | `last_modified` | Last modified timestamp (ISO 8601 string) |
238
+ For complete documentation and examples, see the [Prompts](https://github.com/dickdavis/model-context-protocol-rb/wiki/Prompts) wiki page.
595
239
 
596
- ### Resource Methods
597
-
598
- Define your resource properties and annotations, implement the `call` method to build resource content and `respond_with` to serialize the response. You can wrap long running operations in a `cancellable` block to allow clients to cancel the request. Also, you can automatically send progress notifications to clients by wrapping long-running operations in a `progressable` block.
599
-
600
- | Method | Context | Description |
601
- |--------|---------|-------------|
602
- | `define` | Class definition | Block for defining resource metadata and annotations |
603
- | `call` | Instance method | Main method to implement resource logic and build response |
604
- | `cancellable` | Within `call` | Wrap long-running operations to allow client cancellation (e.g., `cancellable { slow_operation }`) |
605
- | `progressable` | Within `call` | Wrap long-running operations to send clients progress notifications (e.g., `progressable { slow_operation }`) |
606
- | `respond_with` | Within `call` | Return properly formatted response data (e.g., `respond_with text:` or `respond_with binary:`) |
607
-
608
- ### Available Instance Variables
609
-
610
- Resources have access to their configured properties and server context.
611
-
612
- | Variable | Context | Description |
613
- |----------|---------|-------------|
614
- | `mime_type` | Within `call` | The configured MIME type for this resource |
615
- | `uri` | Within `call` | The configured URI identifier for this resource |
616
- | `client_logger` | Within `call` | Client logger instance for sending MCP log messages (e.g., `client_logger.info("message")`) |
617
- | `server_logger` | Within `call` | Server logger instance for debugging and monitoring (e.g., `server_logger.debug("message")`) |
618
- | `context` | Within `call` | Hash containing server configuration context values |
619
-
620
- ### Examples
240
+ ## Resources
621
241
 
622
- This is an example resource that returns a text response:
242
+ Expose data and content to MCP clients through defined resources.
623
243
 
624
244
  ```ruby
625
245
  class TestResource < ModelContextProtocol::Server::Resource
626
246
  define do
627
- name "top-secret-plans.txt"
628
- title "Top Secret Plans"
629
- description "Top secret plans to do top secret things"
630
- mime_type "text/plain"
631
- uri "file:///top-secret-plans.txt"
632
- end
633
-
634
- def call
635
- respond_with text: "I'm finna eat all my wife's leftovers."
636
- end
637
- end
638
- ```
639
-
640
- This is an example resource with annotations:
641
-
642
- ```ruby
643
- class TestAnnotatedResource < ModelContextProtocol::Server::Resource
644
- define do
645
- name "annotated-document.md"
646
- description "A document with annotations showing priority and audience"
647
- mime_type "text/markdown"
648
- uri "file:///docs/annotated-document.md"
649
- annotations do
650
- audience [:user, :assistant]
651
- priority 0.9
652
- last_modified "2025-01-12T15:00:58Z"
653
- end
247
+ name "config.json"
248
+ description "Application configuration"
249
+ mime_type "application/json"
250
+ uri "file:///config.json"
654
251
  end
655
252
 
656
253
  def call
657
- respond_with text: "# Annotated Document\n\nThis document has annotations."
254
+ respond_with text: { setting: "value" }.to_json
658
255
  end
659
256
  end
660
257
  ```
661
258
 
662
- This is an example resource that returns binary data:
259
+ Key features:
260
+ - Define metadata including MIME type and URI
261
+ - Return text or binary content
262
+ - Add annotations for audience and priority
663
263
 
664
- ```ruby
665
- class TestBinaryResource < ModelContextProtocol::Server::Resource
666
- define do
667
- name "project-logo.png"
668
- description "The logo for the project"
669
- mime_type "image/png"
670
- uri "file:///project-logo.png"
671
- end
672
-
673
- def call
674
- # In a real implementation, we would retrieve the binary resource
675
- # This is a small valid base64 encoded string (represents "test")
676
- data = "dGVzdA=="
677
- respond_with binary: data
678
- end
679
- end
680
- ```
681
-
682
- ---
264
+ For complete documentation and examples, see the [Resources](https://github.com/dickdavis/model-context-protocol-rb/wiki/Resources) wiki page.
683
265
 
684
266
  ## Resource Templates
685
267
 
686
- The `ModelContextProtocol::Server::ResourceTemplate` base class allows subclasses to define a resource template that the MCP client can use.
687
-
688
- Define the resource template properties and URI template with optional parameter completions. Resource templates are used to define parameterized resources that clients can instantiate.
689
-
690
- ### Resource Template Definition
691
-
692
- Use the `define` block to set [resource template properties](https://modelcontextprotocol.io/specification/2025-06-18/server/resources#resource-templates).
693
-
694
- | Property | Description |
695
- |----------|-------------|
696
- | `name` | The name of the resource template |
697
- | `description` | Short description of what the template provides |
698
- | `mime_type` | MIME type of resources created from this template |
699
- | `uri_template` | URI template with parameters (e.g., `"file:///{name}"`) |
700
-
701
- ### URI Template Configuration
702
-
703
- Define the URI template and configure parameter completions within the `uri_template` block.
704
-
705
- | Method | Context | Description |
706
- |--------|---------|-------------|
707
- | `completion` | Within `uri_template` block | Define completion for a URI parameter (e.g., `completion :name, ["value1", "value2"]`) |
708
-
709
- ### Resource Template Methods
710
-
711
- Resource templates only use the `define` method to configure their properties - they don't have a `call` method.
712
-
713
- | Method | Context | Description |
714
- |--------|---------|-------------|
715
- | `define` | Class definition | Block for defining resource template metadata and URI template |
716
-
717
- ### Examples
718
-
719
- This is an example resource template that provides a completion for a parameter of the URI template:
268
+ Define parameterized resources with URI templates that clients can instantiate.
720
269
 
721
270
  ```ruby
722
271
  class TestResourceTemplate < ModelContextProtocol::Server::ResourceTemplate
723
272
  define do
724
- name "project-document-resource-template"
725
- description "A resource template for retrieving project documents"
273
+ name "document-template"
274
+ description "Template for retrieving documents"
726
275
  mime_type "text/plain"
727
276
  uri_template "file:///{name}" do
728
- completion :name, ["top-secret-plans.txt"]
277
+ completion :name, ["readme.txt", "config.json"]
729
278
  end
730
279
  end
731
-
732
- # You can optionally define a custom completion for an argument and pass it to completions.
733
- # Completion = ModelContextProtocol::Server::Completion.define do
734
- # hints = {
735
- # "name" => ["top-secret-plans.txt"]
736
- # }
737
- # values = hints[argument_name].grep(/#{argument_value}/)
738
-
739
- # respond_with values:
740
- # end
741
-
742
- # define do
743
- # name "project-document-resource-template"
744
- # description "A resource template for retrieving project documents"
745
- # mime_type "text/plain"
746
- # uri_template "file:///{name}" do
747
- # completion :name, Completion
748
- # end
749
- # end
750
280
  end
751
281
  ```
752
282
 
753
- ---
754
-
755
- ## Tools
756
-
757
- The `ModelContextProtocol::Server::Tool` base class allows subclasses to define a tool that the MCP client can use.
758
-
759
- Define the tool properties and schemas, then implement the `call` method to build your tool. Any arguments passed to the tool from the MCP client will be available in the `arguments` hash with symbol keys (e.g., `arguments[:argument_name]`), and any context values provided in the server configuration will be available in the `context` hash. Use the `respond_with` instance method to ensure your prompt responds with appropriately formatted response data.
760
-
761
- You can also send MCP log messages to clients from within your tool by calling a valid logger level method on the `client_logger` and passing a string message. For server-side debugging and monitoring, use the `server_logger` to write logs that are not sent to clients.
762
-
763
- ### Tool Definition
283
+ Key features:
284
+ - Define URI templates with parameters
285
+ - Provide completion hints for template parameters
764
286
 
765
- Use the `define` block to set [tool properties](https://spec.modelcontextprotocol.io/specification/2025-06-18/server/tools/) and configure schemas.
287
+ For complete documentation and examples, see the [Resource Templates](https://github.com/dickdavis/model-context-protocol-rb/wiki/Resource-Templates) wiki page.
766
288
 
767
- | Property | Description |
768
- |----------|-------------|
769
- | `name` | The programmatic name of the tool |
770
- | `title` | Human-readable display name |
771
- | `description` | Short description of what the tool does |
772
- | `input_schema` | JSON schema block for validating tool inputs |
773
- | `output_schema` | JSON schema block for validating structured content outputs |
774
-
775
- ### Tool Methods
776
-
777
- Define your tool properties and schemas, implement the `call` method using content helpers and `respond_with` to serialize responses. You can wrap long running operations in a `cancellable` block to allow clients to cancel the request. Also, you can automatically send progress notifications to clients by wrapping long-running operations in a `progressable` block.
778
-
779
- | Method | Context | Description |
780
- |--------|---------|-------------|
781
- | `define` | Class definition | Block for defining tool metadata and schemas |
782
- | `call` | Instance method | Main method to implement tool logic and build response |
783
- | `cancellable` | Within `call` | Wrap long-running operations to allow client cancellation (e.g., `cancellable { slow_operation }`) |
784
- | `progressable` | Within `call` | Wrap long-running operations to send clients progress notifications (e.g., `progressable { slow_operation }`) |
785
- | `respond_with` | Within `call` | Return properly formatted response data with various content types |
786
-
787
- ### Content Blocks
788
-
789
- Use content blocks to properly format the content included in tool responses.
790
-
791
- | Method | Context | Description |
792
- |--------|---------|-------------|
793
- | `text_content` | Within `call` | Create text content block |
794
- | `image_content` | Within `call` | Create image content block (requires `data:` and `mime_type:`) |
795
- | `audio_content` | Within `call` | Create audio content block (requires `data:` and `mime_type:`) |
796
- | `embedded_resource_content` | Within `call` | Create embedded resource content block (requires `resource:`) |
797
- | `resource_link` | Within `call` | Create resource link content block (requires `name:` and `uri:`) |
798
-
799
- ### Response Types
800
-
801
- Tools can return different types of responses using `respond_with`.
802
-
803
- | Response Type | Usage | Description |
804
- |---------------|-------|-------------|
805
- | `structured_content:` | `respond_with structured_content: data` | Return structured data validated against output schema |
806
- | `content:` | `respond_with content: content_block` | Return single content block |
807
- | `content:` | `respond_with content: [content_blocks]` | Return array of mixed content blocks |
808
- | `error:` | `respond_with error: "message"` | Return tool error response |
809
-
810
- ### Available Instance Variables
811
-
812
- Arguments from MCP clients and server context are available, along with logging capabilities.
813
-
814
- | Variable | Context | Description |
815
- |----------|---------|-------------|
816
- | `arguments` | Within `call` | Hash containing client-provided arguments (symbol keys) |
817
- | `context` | Within `call` | Hash containing server configuration context values |
818
- | `client_logger` | Within `call` | Client logger instance for sending MCP log messages (e.g., `client_logger.info("message")`) |
819
- | `server_logger` | Within `call` | Server logger instance for debugging and monitoring (e.g., `server_logger.debug("message")`) |
820
-
821
- ### Examples
289
+ ## Tools
822
290
 
823
- This is an example of a tool that returns structured content validated by an output schema:
291
+ Create callable functions that MCP clients can invoke with validated inputs.
824
292
 
825
293
  ```ruby
826
294
  class TestToolWithStructuredContentResponse < ModelContextProtocol::Server::Tool
@@ -865,6 +333,13 @@ class TestToolWithStructuredContentResponse < ModelContextProtocol::Server::Tool
865
333
  required: ["temperature", "conditions", "humidity"]
866
334
  }
867
335
  end
336
+ # Optional security requirements for the tool
337
+ security_schemes do
338
+ [
339
+ {type: "noauth"},
340
+ {type: "oauth2", scopes: ["search.read"]}
341
+ ]
342
+ end
868
343
  end
869
344
 
870
345
  def call
@@ -896,337 +371,55 @@ class TestToolWithStructuredContentResponse < ModelContextProtocol::Server::Tool
896
371
  end
897
372
  ```
898
373
 
899
- This is an example tool that returns a text response:
900
-
901
- ```ruby
902
- class TestToolWithTextResponse < ModelContextProtocol::Server::Tool
903
- define do
904
- name "double"
905
- title "Number Doubler"
906
- description "Doubles the provided number"
907
- input_schema do
908
- {
909
- type: "object",
910
- properties: {
911
- number: {
912
- type: "string"
913
- }
914
- },
915
- required: ["number"]
916
- }
917
- end
918
- end
919
-
920
- def call
921
- client_logger.info("Silly user doesn't know how to double a number")
922
- number = arguments[:number].to_i
923
- calculation = number * 2
924
-
925
- user_id = context[:user_id]
926
- salutation = user_id ? "User #{user_id}, " : ""
927
- text_content = text_content(text: salutation << "#{number} doubled is #{calculation}")
928
-
929
- respond_with content: text_content
930
- end
931
- end
932
- ```
374
+ Key features:
375
+ - Define input and output JSON schemas
376
+ - Declare tool security schemes (e.g., noauth, oauth2 scopes)
377
+ - Return text, image, audio, or embedded resource content
378
+ - Support for structured content responses
379
+ - Cancellable and progressable operations
933
380
 
934
- This is an example of a tool that returns an image:
381
+ For complete documentation and 7 detailed examples, see the [Tools](https://github.com/dickdavis/model-context-protocol-rb/wiki/Tools) wiki page.
935
382
 
936
- ```ruby
937
- class TestToolWithImageResponse < ModelContextProtocol::Server::Tool
938
- define do
939
- name "custom-chart-generator"
940
- description "Generates a chart in various formats"
941
- input_schema do
942
- {
943
- type: "object",
944
- properties: {
945
- chart_type: {
946
- type: "string",
947
- description: "Type of chart (pie, bar, line)"
948
- },
949
- format: {
950
- type: "string",
951
- description: "Image format (jpg, svg, etc)"
952
- }
953
- },
954
- required: ["chart_type", "format"]
955
- }
956
- end
957
- end
958
-
959
- def call
960
- # Map format to mime type
961
- mime_type = case arguments[:format].downcase
962
- when "svg"
963
- "image/svg+xml"
964
- when "jpg", "jpeg"
965
- "image/jpeg"
966
- else
967
- "image/png"
968
- end
969
-
970
- # In a real implementation, we would generate an actual chart
971
- # This is a small valid base64 encoded string (represents "test")
972
- data = "dGVzdA=="
973
- image_content = image_content(data:, mime_type:)
974
- respond_with content: image_content
975
- end
976
- end
977
- ```
978
-
979
- This is an example of a tool that returns an embedded resource response:
980
-
981
- ```ruby
982
- class TestToolWithResourceResponse < ModelContextProtocol::Server::Tool
983
- define do
984
- name "resource-finder"
985
- description "Finds a resource given a name"
986
- input_schema do
987
- {
988
- type: "object",
989
- properties: {
990
- name: {
991
- type: "string",
992
- description: "The name of the resource"
993
- }
994
- },
995
- required: ["name"]
996
- }
997
- end
998
- end
999
-
1000
- RESOURCE_MAPPINGS = {
1001
- test_annotated_resource: TestAnnotatedResource,
1002
- test_binary_resource: TestBinaryResource,
1003
- test_resource: TestResource
1004
- }.freeze
1005
-
1006
- def call
1007
- name = arguments[:name]
1008
- resource_klass = RESOURCE_MAPPINGS[name.downcase.to_sym]
1009
- unless resource_klass
1010
- return respond_with :error, text: "Resource `#{name}` not found"
1011
- end
1012
-
1013
- resource_data = resource_klass.call(client_logger, context)
1014
-
1015
- respond_with content: embedded_resource_content(resource: resource_data)
1016
- end
1017
- end
1018
- ```
1019
-
1020
- This is an example of a tool that returns mixed content:
1021
-
1022
- ```ruby
1023
- class TestToolWithMixedContentResponse < ModelContextProtocol::Server::Tool
1024
- define do
1025
- name "get_temperature_history"
1026
- description "Gets comprehensive temperature history for a zip code"
1027
- input_schema do
1028
- {
1029
- type: "object",
1030
- properties: {
1031
- zip: {
1032
- type: "string"
1033
- }
1034
- },
1035
- required: ["zip"]
1036
- }
1037
- end
1038
- end
1039
-
1040
- def call
1041
- client_logger.info("Getting comprehensive temperature history data")
1042
-
1043
- zip = arguments[:zip]
1044
- temperature_history = retrieve_temperature_history(zip:)
1045
- temperature_history_block = text_content(text: temperature_history.join(", "))
1046
-
1047
- temperature_chart = generate_weather_history_chart(temperature_history)
1048
- temperature_chart_block = image_content(
1049
- data: temperature_chart[:base64_chart_data],
1050
- mime_type: temperature_chart[:mime_type]
1051
- )
1052
-
1053
- respond_with content: [temperature_history_block, temperature_chart_block]
1054
- end
1055
-
1056
- private
1057
-
1058
- def retrieve_temperature_history(zip:)
1059
- # Simulates a call to an API or DB to retrieve weather history
1060
- [85.2, 87.4, 89.0, 95.3, 96.0]
1061
- end
1062
-
1063
- def generate_weather_history_chart(history)
1064
- # SImulate a call to generate a chart given the weather history
1065
- {
1066
- base64_chart_data: "dGVzdA==",
1067
- mime_type: "image/png"
1068
- }
1069
- end
1070
- end
1071
- ```
383
+ ## Completions
1072
384
 
1073
- This is an example of a tool that returns a tool error response:
385
+ Provide argument completion hints for prompts and resource templates.
1074
386
 
1075
387
  ```ruby
1076
- class TestToolWithToolErrorResponse < ModelContextProtocol::Server::Tool
1077
- define do
1078
- name "api-caller"
1079
- description "Makes calls to external APIs"
1080
- input_schema do
1081
- {
1082
- type: "object",
1083
- properties: {
1084
- api_endpoint: {
1085
- type: "string",
1086
- description: "API endpoint URL"
1087
- },
1088
- method: {
1089
- type: "string",
1090
- description: "HTTP method (GET, POST, etc)"
1091
- }
1092
- },
1093
- required: ["api_endpoint", "method"]
1094
- }
1095
- end
1096
- end
1097
-
388
+ class TestCompletion < ModelContextProtocol::Server::Completion
1098
389
  def call
1099
- # Simulate an API call failure
1100
- respond_with error: "Failed to call API at #{arguments[:api_endpoint]}: Connection timed out"
390
+ hints = { "tone" => ["whiny", "angry", "nervous"] }
391
+ values = hints[argument_name].grep(/#{argument_value}/)
392
+ respond_with values:
1101
393
  end
1102
394
  end
1103
395
  ```
1104
396
 
1105
- This is an example of a tool that allows a client to cancel a long-running operation:
397
+ For complete documentation, see the [Completions](https://github.com/dickdavis/model-context-protocol-rb/wiki/Completions) wiki page.
1106
398
 
1107
- ```ruby
1108
- class TestToolWithCancellableSleep < ModelContextProtocol::Server::Tool
1109
- define do
1110
- name "cancellable_sleep"
1111
- title "Cancellable Sleep Tool"
1112
- description "Sleep for 3 seconds with cancellation support"
1113
- input_schema do
1114
- {
1115
- type: "object",
1116
- properties: {},
1117
- additionalProperties: false
1118
- }
1119
- end
1120
- end
1121
-
1122
- def call
1123
- client_logger.info("Starting 3 second sleep operation")
1124
-
1125
- result = cancellable do
1126
- sleep 3
1127
- "Sleep completed successfully"
1128
- end
1129
-
1130
- respond_with content: text_content(text: result)
1131
- end
1132
- end
1133
- ```
399
+ ## Testing with RSpec
1134
400
 
1135
- This is an example of a tool that automatically sends progress notifications to the client and allows the client to cancel the operation:
401
+ This gem provides custom RSpec matchers and helpers for testing your MCP handlers.
1136
402
 
1137
403
  ```ruby
1138
- class TestToolWithProgressableAndCancellable < ModelContextProtocol::Server::Tool
1139
- define do
1140
- name "test_tool_with_progressable_and_cancellable"
1141
- description "A test tool that demonstrates combined progressable and cancellable functionality"
1142
-
1143
- input_schema do
1144
- {
1145
- type: "object",
1146
- properties: {
1147
- max_duration: {
1148
- type: "number",
1149
- description: "Expected maximum duration in seconds"
1150
- },
1151
- work_steps: {
1152
- type: "number",
1153
- description: "Number of work steps to perform"
1154
- }
1155
- },
1156
- required: ["max_duration"]
1157
- }
1158
- end
1159
- end
1160
-
1161
- def call
1162
- max_duration = arguments[:max_duration] || 10
1163
- work_steps = arguments[:work_steps] || 10
1164
- client_logger.info("Starting progressable call with max_duration=#{max_duration}, work_steps=#{work_steps}")
404
+ require "model_context_protocol/rspec"
405
+ ModelContextProtocol::RSpec.configure!
1165
406
 
1166
- result = progressable(max_duration:, message: "Processing #{work_steps} items") do
1167
- cancellable do
1168
- processed_items = []
1169
-
1170
- work_steps.times do |i|
1171
- sleep(max_duration / work_steps.to_f)
1172
- processed_items << "item_#{i + 1}"
1173
- end
1174
-
1175
- processed_items
1176
- end
1177
- end
1178
-
1179
- response = text_content(text: "Successfully processed #{result.length} items: #{result.join(", ")}")
1180
-
1181
- respond_with content: response
407
+ RSpec.describe WeatherTool, type: :mcp do
408
+ it "returns weather data" do
409
+ response = call_mcp_tool(WeatherTool, { location: "New York" })
410
+ expect(response).to be_valid_mcp_tool_response
411
+ expect(response).to have_text_content(/temperature/)
1182
412
  end
1183
413
  end
1184
414
  ```
1185
415
 
1186
- ---
416
+ Key features:
417
+ - Helper methods: `call_mcp_tool`, `call_mcp_prompt`, `call_mcp_resource`
418
+ - Class definition matchers: `be_valid_mcp_class(:tool)`
419
+ - Response matchers for text, image, audio, and structured content
420
+ - Prompt and resource-specific matchers
1187
421
 
1188
- ## Completions
1189
-
1190
- The `ModelContextProtocol::Server::Completion` base class allows subclasses to define a completion that the MCP client can use to obtain hints or suggestions for arguments to prompts and resources.
1191
-
1192
- Implement the `call` method to build your completion logic using the provided argument name and value. Completions are simpler than other server features - they don't use a `define` block and only provide filtered suggestion lists.
1193
-
1194
- ### Completion Methods
1195
-
1196
- Completions only implement the `call` method to provide completion logic.
1197
-
1198
- | Method | Context | Description |
1199
- |--------|---------|-------------|
1200
- | `call` | Instance method | Main method to implement completion logic and build response |
1201
- | `respond_with` | Within `call` | Return properly formatted completion response (e.g., `respond_with values:`) |
1202
-
1203
- ### Available Instance Variables
1204
-
1205
- Completions receive the argument name and current value being completed.
1206
-
1207
- | Variable | Context | Description |
1208
- |----------|---------|-------------|
1209
- | `argument_name` | Within `call` | String name of the argument being completed |
1210
- | `argument_value` | Within `call` | Current partial value being typed by the user |
1211
-
1212
- ### Examples
1213
-
1214
- This is an example completion that returns an array of values in the response:
1215
-
1216
- ```ruby
1217
- class TestCompletion < ModelContextProtocol::Server::Completion
1218
- def call
1219
- hints = {
1220
- "message" => ["hello", "world", "foo", "bar"]
1221
- }
1222
- values = hints[argument_name].grep(/#{argument_value}/)
1223
-
1224
- respond_with values:
1225
- end
1226
- end
1227
- ```
1228
-
1229
- ---
422
+ For complete matcher documentation and examples, see the [Testing with RSpec](https://github.com/dickdavis/model-context-protocol-rb/wiki/Testing-with-RSpec) wiki page.
1230
423
 
1231
424
  ## Development
1232
425
 
@@ -1242,6 +435,9 @@ bundle exec rake mcp:generate_stdio_server
1242
435
 
1243
436
  # generates bin/dev-http for streamable HTTP transport
1244
437
  bundle exec rake mcp:generate_streamable_http_server
438
+
439
+ # generates bin/dev-http-puma for streamable HTTP transport with Puma web server
440
+ bundle exec rake mcp:generate_puma_server
1245
441
  ```
1246
442
 
1247
443
  If you need to test with HTTPS (e.g., for clients that require SSL), generate self-signed certificates: