model-context-protocol-rb 0.3.4 → 0.5.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +26 -1
- data/README.md +886 -196
- data/lib/model_context_protocol/server/cancellable.rb +54 -0
- data/lib/model_context_protocol/server/configuration.rb +80 -8
- data/lib/model_context_protocol/server/content.rb +321 -0
- data/lib/model_context_protocol/server/content_helpers.rb +84 -0
- data/lib/model_context_protocol/server/pagination.rb +71 -0
- data/lib/model_context_protocol/server/progressable.rb +72 -0
- data/lib/model_context_protocol/server/prompt.rb +108 -14
- data/lib/model_context_protocol/server/redis_client_proxy.rb +134 -0
- data/lib/model_context_protocol/server/redis_config.rb +108 -0
- data/lib/model_context_protocol/server/redis_pool_manager.rb +110 -0
- data/lib/model_context_protocol/server/registry.rb +94 -18
- data/lib/model_context_protocol/server/resource.rb +98 -25
- data/lib/model_context_protocol/server/resource_template.rb +26 -13
- data/lib/model_context_protocol/server/router.rb +36 -3
- data/lib/model_context_protocol/server/stdio_transport/request_store.rb +102 -0
- data/lib/model_context_protocol/server/stdio_transport.rb +31 -6
- data/lib/model_context_protocol/server/streamable_http_transport/event_counter.rb +35 -0
- data/lib/model_context_protocol/server/streamable_http_transport/message_poller.rb +101 -0
- data/lib/model_context_protocol/server/streamable_http_transport/notification_queue.rb +80 -0
- data/lib/model_context_protocol/server/streamable_http_transport/request_store.rb +224 -0
- data/lib/model_context_protocol/server/streamable_http_transport/session_message_queue.rb +120 -0
- data/lib/model_context_protocol/server/{session_store.rb → streamable_http_transport/session_store.rb} +30 -16
- data/lib/model_context_protocol/server/streamable_http_transport/stream_registry.rb +119 -0
- data/lib/model_context_protocol/server/streamable_http_transport.rb +352 -112
- data/lib/model_context_protocol/server/tool.rb +79 -53
- data/lib/model_context_protocol/server.rb +124 -21
- data/lib/model_context_protocol/version.rb +1 -1
- data/tasks/mcp.rake +28 -2
- data/tasks/templates/dev-http.erb +288 -0
- data/tasks/templates/dev.erb +7 -1
- metadata +61 -3
data/README.md
CHANGED
@@ -1,18 +1,54 @@
|
|
1
1
|
# model-context-protocol-rb
|
2
2
|
|
3
|
-
An implementation of the [Model Context Protocol (MCP)](https://spec.modelcontextprotocol.io/specification/
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
3
|
+
An implementation of the [Model Context Protocol (MCP)](https://spec.modelcontextprotocol.io/specification/2025-06-18/) in Ruby.
|
4
|
+
|
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
|
+
|
7
|
+
## Table of Contents
|
8
|
+
|
9
|
+
- [Feature Support (Server)](#feature-support-server)
|
10
|
+
- [Usage](#usage)
|
11
|
+
- [Building an MCP Server](#building-an-mcp-server)
|
12
|
+
- [Server Configuration Options](#server-configuration-options)
|
13
|
+
- [Pagination Configuration Options](#pagination-configuration-options)
|
14
|
+
- [Transport Configuration Options](#transport-configuration-options)
|
15
|
+
- [STDIO Transport](#stdio-transport)
|
16
|
+
- [Streamable HTTP Transport](#streamable-http-transport)
|
17
|
+
- [Registry Configuration Options](#registry-configuration-options)
|
18
|
+
- [Integration with Rails](#integration-with-rails)
|
19
|
+
- [Server features](#server-features)
|
20
|
+
- [Prompts](#prompts)
|
21
|
+
- [Resources](#resources)
|
22
|
+
- [Resource Templates](#resource-templates)
|
23
|
+
- [Tools](#tools)
|
24
|
+
- [Completions](#completions)
|
25
|
+
- [Installation](#installation)
|
26
|
+
- [Development](#development)
|
27
|
+
- [Releases](#releases)
|
28
|
+
- [Contributing](#contributing)
|
29
|
+
- [License](#license)
|
30
|
+
|
31
|
+
## Feature Support (Server)
|
32
|
+
|
33
|
+
| Status | Feature |
|
34
|
+
|--------|---------|
|
35
|
+
| ✅ | [Prompts](https://modelcontextprotocol.io/specification/2025-06-18/server/prompts) |
|
36
|
+
| ✅ | [Resources](https://modelcontextprotocol.io/specification/2025-06-18/server/resources) |
|
37
|
+
| ✅ | [Resource Templates](https://modelcontextprotocol.io/specification/2025-06-18/server/resources#resource-templates) |
|
38
|
+
| ✅ | [Tools](https://modelcontextprotocol.io/specification/2025-06-18/server/tools) |
|
39
|
+
| ✅ | [Completion](https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/completion) |
|
40
|
+
| ✅ | [Logging](https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/logging) |
|
41
|
+
| ✅ | [Pagination](https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/pagination) |
|
42
|
+
| ✅ | [Environment Variables](https://modelcontextprotocol.io/legacy/tools/debugging#environment-variables) |
|
43
|
+
| ✅ | [STDIO Transport](https://modelcontextprotocol.io/specification/2025-06-18/basic/transports#stdio) |
|
44
|
+
| ✅ | [Streamable HTTP Transport](https://modelcontextprotocol.io/specification/2025-06-18/basic/transports#streamable-http) |
|
45
|
+
| ❌ | [List Changed Notification (Prompts)](https://modelcontextprotocol.io/specification/2025-06-18/server/prompts#list-changed-notification) |
|
46
|
+
| ❌ | [List Changed Notification (Resources)](https://modelcontextprotocol.io/specification/2025-06-18/server/resources#list-changed-notification) |
|
47
|
+
| ❌ | [Subscriptions (Resources)](https://modelcontextprotocol.io/specification/2025-06-18/server/resources#subscriptions) |
|
48
|
+
| ❌ | [List Changed Notification (Tools)](https://modelcontextprotocol.io/specification/2025-06-18/server/tools#list-changed-notification) |
|
49
|
+
| ✅ | [Cancellation](https://modelcontextprotocol.io/specification/2025-06-18/basic/utilities/cancellation) |
|
50
|
+
| ✅ | [Ping](https://modelcontextprotocol.io/specification/2025-06-18/basic/utilities/ping) |
|
51
|
+
| ✅ | [Progress](https://modelcontextprotocol.io/specification/2025-06-18/basic/utilities/progress) |
|
16
52
|
|
17
53
|
## Usage
|
18
54
|
|
@@ -24,27 +60,69 @@ require 'model_context_protocol'
|
|
24
60
|
|
25
61
|
### Building an MCP Server
|
26
62
|
|
27
|
-
Build a simple MCP server by registering your prompts, resources, resource templates, and tools. Then, configure and run the server.
|
63
|
+
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.
|
28
64
|
|
29
65
|
```ruby
|
30
66
|
server = ModelContextProtocol::Server.new do |config|
|
31
|
-
|
67
|
+
# Name of the MCP server (intended for programmatic use)
|
68
|
+
config.name = "MCPDevelopmentServer"
|
69
|
+
|
70
|
+
# Version of the MCP server
|
32
71
|
config.version = "1.0.0"
|
72
|
+
|
73
|
+
# Optional: human-readable display name for the MCP server
|
74
|
+
config.title = "My Awesome Server"
|
75
|
+
|
76
|
+
# Optional: instuctions for how the MCP server should be used by LLMs
|
77
|
+
config.instructions = <<~INSTRUCTIONS
|
78
|
+
This server provides file system access and development tools.
|
79
|
+
|
80
|
+
Key capabilities:
|
81
|
+
- Read and write files in the project directory
|
82
|
+
- Execute shell commands for development tasks
|
83
|
+
- Analyze code structure and dependencies
|
84
|
+
|
85
|
+
Use this server when you need to interact with the local development environment.
|
86
|
+
INSTRUCTIONS
|
87
|
+
|
88
|
+
# Enable or disable MCP server logging
|
33
89
|
config.logging_enabled = true
|
34
90
|
|
35
|
-
#
|
36
|
-
#
|
91
|
+
# Configure pagination options for the following methods:
|
92
|
+
# prompts/list, resources/list, resource_template/list, tools/list
|
93
|
+
config.pagination = {
|
94
|
+
default_page_size: 50, # Default items per page
|
95
|
+
max_page_size: 500, # Maximum allowed page size
|
96
|
+
cursor_ttl: 1800 # Cursor expiry in seconds (30 minutes)
|
97
|
+
}
|
98
|
+
|
99
|
+
# Disable pagination support (enabled by default)
|
100
|
+
# config.pagination = false
|
101
|
+
|
102
|
+
# Optional: require specific environment variables to be set
|
37
103
|
config.require_environment_variable("API_KEY")
|
38
104
|
|
39
|
-
#
|
105
|
+
# Optional: set environment variables programmatically
|
40
106
|
config.set_environment_variable("DEBUG_MODE", "true")
|
41
107
|
|
42
|
-
#
|
108
|
+
# Optional: provide prompts, resources, and tools with contextual variables
|
43
109
|
config.context = {
|
44
110
|
user_id: "123456",
|
45
111
|
request_id: SecureRandom.uuid
|
46
112
|
}
|
47
113
|
|
114
|
+
# Optional: explicitly specify STDIO as the transport
|
115
|
+
# This is not necessary as STDIO is the default transport
|
116
|
+
# config.transport = { type: :stdio }
|
117
|
+
|
118
|
+
# Optional: configure streamable HTTP transport if required
|
119
|
+
# config.transport = {
|
120
|
+
# type: :streamable_http,
|
121
|
+
# env: request.env,
|
122
|
+
# session_ttl: 3600 # Optional: session timeout in seconds (default: 3600)
|
123
|
+
# }
|
124
|
+
|
125
|
+
# Register prompts, resources, resource templates, and tools
|
48
126
|
config.registry = ModelContextProtocol::Server::Registry.new do
|
49
127
|
prompts list_changed: true do
|
50
128
|
register TestPrompt
|
@@ -64,204 +142,439 @@ server = ModelContextProtocol::Server.new do |config|
|
|
64
142
|
end
|
65
143
|
end
|
66
144
|
|
145
|
+
# Start the MCP server
|
67
146
|
server.start
|
68
147
|
```
|
69
148
|
|
70
|
-
|
149
|
+
#### Server Configuration Options
|
150
|
+
|
151
|
+
The following table details all available configuration options for the MCP server:
|
152
|
+
|
153
|
+
| Option | Type | Required | Default | Description |
|
154
|
+
|--------|------|----------|---------|-------------|
|
155
|
+
| `name` | String | Yes | - | Name of the MCP server for programmatic use |
|
156
|
+
| `version` | String | Yes | - | Version of the MCP server |
|
157
|
+
| `title` | String | No | - | Human-readable display name for the MCP server |
|
158
|
+
| `instructions` | String | No | - | Instructions for how the MCP server should be used by LLMs |
|
159
|
+
| `logging_enabled` | Boolean | No | `true` | Enable or disable MCP server logging |
|
160
|
+
| `pagination` | Hash/Boolean | No | See pagination table | Pagination configuration (or `false` to disable) |
|
161
|
+
| `context` | Hash | No | `{}` | Contextual variables available to prompts, resources, and tools |
|
162
|
+
| `transport` | Hash | No | `{ type: :stdio }` | Transport configuration |
|
163
|
+
| `registry` | Registry | Yes | - | Registry containing prompts, resources, and tools |
|
71
164
|
|
72
|
-
|
165
|
+
#### Pagination Configuration Options
|
73
166
|
|
74
|
-
|
167
|
+
When `pagination` is set to a Hash, the following options are available:
|
75
168
|
|
76
|
-
|
169
|
+
| Option | Type | Required | Default | Description |
|
170
|
+
|--------|------|----------|---------|-------------|
|
171
|
+
| `default_page_size` | Integer | No | `50` | Default number of items per page |
|
172
|
+
| `max_page_size` | Integer | No | `500` | Maximum allowed page size |
|
173
|
+
| `cursor_ttl` | Integer | No | `1800` | Cursor expiry time in seconds (30 minutes) |
|
77
174
|
|
175
|
+
**Note:** Set `config.pagination = false` to completely disable pagination support.
|
176
|
+
|
177
|
+
#### Transport Configuration Options
|
178
|
+
|
179
|
+
The transport configuration supports two types: `:stdio` (default) and `:streamable_http`.
|
180
|
+
|
181
|
+
##### STDIO Transport
|
78
182
|
```ruby
|
79
|
-
|
80
|
-
|
81
|
-
config.version = "1.0.0"
|
82
|
-
# No transport specified - uses stdio by default
|
83
|
-
config.registry = ModelContextProtocol::Server::Registry.new
|
84
|
-
end
|
183
|
+
config.transport = { type: :stdio } # This is the default, can be omitted
|
184
|
+
```
|
85
185
|
|
86
|
-
|
186
|
+
##### Streamable HTTP Transport
|
187
|
+
The `:streamable_http` transport requires Redis to be configured globally before use:
|
188
|
+
|
189
|
+
```ruby
|
190
|
+
ModelContextProtocol::Server.configure_redis do |config|
|
191
|
+
config.redis_url = ENV.fetch('REDIS_URL')
|
192
|
+
config.pool_size = 20
|
193
|
+
config.pool_timeout = 5
|
194
|
+
config.enable_reaper = true
|
195
|
+
config.reaper_interval = 60
|
196
|
+
config.idle_timeout = 300
|
197
|
+
end
|
87
198
|
```
|
88
199
|
|
89
|
-
|
200
|
+
| Option | Type | Required | Default | Description |
|
201
|
+
|--------|------|----------|---------|-------------|
|
202
|
+
| `redis_url` | String | Yes | - | Redis connection URL |
|
203
|
+
| `pool_size` | Integer | No | `20` | Connection pool size |
|
204
|
+
| `pool_timeout` | Integer | No | `5` | Pool checkout timeout in seconds |
|
205
|
+
| `enable_reaper` | Boolean | No | `true` | Enable connection reaping |
|
206
|
+
| `reaper_interval` | Integer | No | `60` | Reaper check interval in seconds |
|
207
|
+
| `idle_timeout` | Integer | No | `300` | Idle connection timeout in seconds |
|
90
208
|
|
91
|
-
|
209
|
+
When using `:streamable_http` transport, the following options are available:
|
92
210
|
|
93
|
-
|
94
|
-
|
211
|
+
| Option | Type | Required | Default | Description |
|
212
|
+
|--------|------|----------|---------|-------------|
|
213
|
+
| `type` | Symbol | Yes | `:stdio` | Must be `:streamable_http` for HTTP transport |
|
214
|
+
| `session_ttl` | Integer | No | `3600` | Session timeout in seconds (1 hour) |
|
215
|
+
| `env` | Hash | No | - | Rack environment hash (for Rails integration) |
|
95
216
|
|
96
|
-
|
97
|
-
config.name = "MCP Development Server"
|
98
|
-
config.version = "1.0.0"
|
217
|
+
#### Registry Configuration Options
|
99
218
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
219
|
+
The registry is configured using `ModelContextProtocol::Server::Registry.new` and supports the following block types:
|
220
|
+
|
221
|
+
| Block Type | Options | Description |
|
222
|
+
|------------|---------|-------------|
|
223
|
+
| `prompts` | `list_changed: Boolean` | Register prompt handlers with optional list change notifications |
|
224
|
+
| `resources` | `list_changed: Boolean`, `subscribe: Boolean` | Register resource handlers with optional list change notifications and subscriptions |
|
225
|
+
| `resource_templates` | - | Register resource template handlers |
|
226
|
+
| `tools` | `list_changed: Boolean` | Register tool handlers with optional list change notifications |
|
227
|
+
|
228
|
+
Within each block, use `register ClassName` to register your handlers.
|
106
229
|
|
107
|
-
|
230
|
+
**Example:**
|
231
|
+
```ruby
|
232
|
+
config.registry = ModelContextProtocol::Server::Registry.new do
|
233
|
+
prompts list_changed: true do
|
234
|
+
register MyPrompt
|
235
|
+
register AnotherPrompt
|
236
|
+
end
|
237
|
+
|
238
|
+
resources list_changed: true, subscribe: true do
|
239
|
+
register MyResource
|
240
|
+
end
|
241
|
+
|
242
|
+
tools do
|
243
|
+
register MyTool
|
244
|
+
end
|
108
245
|
end
|
246
|
+
```
|
247
|
+
|
248
|
+
#### Integration with Rails
|
249
|
+
|
250
|
+
The streamable HTTP transport works with any valid Rack request. Here's an example of how you can integrate with Rails.
|
109
251
|
|
110
|
-
|
111
|
-
|
112
|
-
|
252
|
+
First, configure Redis in an initializer:
|
253
|
+
|
254
|
+
```ruby
|
255
|
+
# config/initializers/model_context_protocol.rb
|
256
|
+
ModelContextProtocol::Server.configure_redis do |config|
|
257
|
+
config.redis_url = ENV.fetch('REDIS_URL')
|
258
|
+
config.pool_size = 20
|
259
|
+
config.pool_timeout = 5
|
260
|
+
config.enable_reaper = true
|
261
|
+
config.reaper_interval = 60
|
262
|
+
config.idle_timeout = 300
|
263
|
+
end
|
113
264
|
```
|
114
265
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
266
|
+
Then, set the routes:
|
267
|
+
|
268
|
+
```ruby
|
269
|
+
constraints format: :json do
|
270
|
+
get "/mcp", to: "model_context_protocol#handle", as: :mcp_get
|
271
|
+
post "/mcp", to: "model_context_protocol#handle", as: :mcp_post
|
272
|
+
delete "/mcp", to: "model_context_protocol#handle", as: :mcp_delete
|
273
|
+
end
|
274
|
+
```
|
120
275
|
|
121
|
-
|
276
|
+
Then, implement a controller endpoint to handle the requests.
|
122
277
|
|
123
278
|
```ruby
|
124
|
-
|
279
|
+
require 'model_context_protocol'
|
280
|
+
|
281
|
+
class ModelContextProtocolController < ApplicationController
|
282
|
+
before_action :authenticate_user
|
283
|
+
|
125
284
|
def handle
|
126
285
|
server = ModelContextProtocol::Server.new do |config|
|
127
|
-
config.name = "
|
286
|
+
config.name = "MyMCPServer"
|
287
|
+
config.title = "My MCP Server"
|
128
288
|
config.version = "1.0.0"
|
289
|
+
config.logging_enabled = true
|
290
|
+
config.context = {
|
291
|
+
user_id: current_user.id,
|
292
|
+
request_id: request.id
|
293
|
+
}
|
294
|
+
config.registry = build_registry
|
129
295
|
config.transport = {
|
130
296
|
type: :streamable_http,
|
131
|
-
|
132
|
-
request: request,
|
133
|
-
response: response
|
297
|
+
env: request.env
|
134
298
|
}
|
135
|
-
config.
|
299
|
+
config.instructions = <<~INSTRUCTIONS
|
300
|
+
This server provides prompts, tools, and resources for interacting with my app.
|
301
|
+
|
302
|
+
Key capabilities:
|
303
|
+
- Does this one thing
|
304
|
+
- Does this other thing
|
305
|
+
- Oh, yeah, and it does that one thing, too
|
306
|
+
|
307
|
+
Use this server when you need to do stuff.
|
308
|
+
INSTRUCTIONS
|
136
309
|
end
|
137
310
|
|
138
311
|
result = server.start
|
139
|
-
|
312
|
+
|
313
|
+
# For SSE responses
|
314
|
+
if result[:stream]
|
315
|
+
response.headers.merge!(result[:headers] || {})
|
316
|
+
response.content_type = result[:headers]["Content-Type"] || "text/event-stream"
|
317
|
+
# Handle streaming with result[:stream_proc]
|
318
|
+
else
|
319
|
+
# For regular JSON responses
|
320
|
+
render json: result[:json], status: result[:status], headers: result[:headers]
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
private
|
325
|
+
|
326
|
+
def build_registry
|
327
|
+
ModelContextProtocol::Server::Registry.new do
|
328
|
+
tools do
|
329
|
+
# Implement user authorization logic to dynamically build registry
|
330
|
+
register TestTool if current_user.authorized_for?(TestTool)
|
331
|
+
end
|
332
|
+
end
|
140
333
|
end
|
141
334
|
end
|
142
335
|
```
|
143
336
|
|
144
|
-
|
337
|
+
### Prompts
|
145
338
|
|
146
|
-
|
339
|
+
The `ModelContextProtocol::Server::Prompt` base class allows subclasses to define a prompt that the MCP client can use.
|
147
340
|
|
148
|
-
|
341
|
+
Define the prompt properties and then implement the `call` method to build your prompt. 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.
|
149
342
|
|
150
|
-
|
343
|
+
You can also log from within your prompt by calling a valid logger level method on the `logger` and passing a string message.
|
151
344
|
|
152
|
-
|
345
|
+
#### Prompt Definition
|
153
346
|
|
154
|
-
|
347
|
+
Use the `define` block to set [prompt properties](https://spec.modelcontextprotocol.io/specification/2025-06-18/server/prompts/) and configure arguments.
|
155
348
|
|
156
|
-
|
349
|
+
| Property | Description |
|
350
|
+
|----------|-------------|
|
351
|
+
| `name` | The programmatic name of the prompt |
|
352
|
+
| `title` | Human-readable display name |
|
353
|
+
| `description` | Short description of what the prompt does |
|
354
|
+
| `argument` | Define an argument block with name, description, required flag, and completion |
|
355
|
+
|
356
|
+
#### Argument Definition
|
357
|
+
|
358
|
+
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.
|
359
|
+
|
360
|
+
| Property | Description |
|
361
|
+
|----------|-------------|
|
362
|
+
| `name` | The name of the argument |
|
363
|
+
| `description` | A short description of the argument |
|
364
|
+
| `required` | Whether the argument is required (boolean) |
|
365
|
+
| `completion` | Available hints for completions (array or completion class) |
|
366
|
+
|
367
|
+
#### Prompt Methods
|
368
|
+
|
369
|
+
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.
|
370
|
+
|
371
|
+
| Method | Context | Description |
|
372
|
+
|--------|---------|-------------|
|
373
|
+
| `define` | Class definition | Block for defining prompt metadata and arguments |
|
374
|
+
| `call` | Instance method | Main method to implement prompt logic and build response |
|
375
|
+
| `cancellable` | Within `call` | Wrap long-running operations to allow client cancellation (e.g., `cancellable { slow_operation }`) |
|
376
|
+
| `progressable` | Within `call` | Wrap long-running operations to send clients progress notifications (e.g., `progressable { slow_operation }`) |
|
377
|
+
| `message_history` | Within `call` | DSL method to build an array of user and assistant messages |
|
378
|
+
| `respond_with` | Within `call` | Return properly formatted response data (e.g., `respond_with messages:`) |
|
379
|
+
|
380
|
+
#### Message History DSL
|
381
|
+
|
382
|
+
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.
|
383
|
+
|
384
|
+
| Method | Context | Description |
|
385
|
+
|--------|---------|-------------|
|
386
|
+
| `user_message` | Within `message_history` | Create a message with user role |
|
387
|
+
| `assistant_message` | Within `message_history` | Create a message with assistant role |
|
388
|
+
|
389
|
+
#### Content Blocks
|
390
|
+
|
391
|
+
Use content blocks to properly format the content included in messages.
|
392
|
+
|
393
|
+
| Method | Context | Description |
|
394
|
+
|--------|---------|-------------|
|
395
|
+
| `text_content` | Within message blocks | Create text content block |
|
396
|
+
| `image_content` | Within message blocks | Create image content block (requires `data:` and `mime_type:`) |
|
397
|
+
| `audio_content` | Within message blocks | Create audio content block (requires `data:` and `mime_type:`) |
|
398
|
+
| `embedded_resource_content` | Within message blocks | Create embedded resource content block (requires `resource:`) |
|
399
|
+
| `resource_link` | Within message blocks | Create resource link content block (requires `name:` and `uri:`) |
|
400
|
+
|
401
|
+
#### Available Instance Variables
|
402
|
+
|
403
|
+
The `arguments` passed from an MCP client are available, as well as the `context` values passed in at server initialization.
|
404
|
+
|
405
|
+
| Variable | Context | Description |
|
406
|
+
|----------|---------|-------------|
|
407
|
+
| `arguments` | Within `call` | Hash containing client-provided arguments (symbol keys) |
|
408
|
+
| `context` | Within `call` | Hash containing server configuration context values |
|
409
|
+
| `logger` | Within `call` | Logger instance for logging (e.g., `logger.info("message")`) |
|
410
|
+
|
411
|
+
#### Examples
|
157
412
|
|
158
413
|
This is an example prompt that returns a properly formatted response:
|
159
414
|
|
160
415
|
```ruby
|
161
416
|
class TestPrompt < ModelContextProtocol::Server::Prompt
|
162
|
-
|
163
|
-
|
164
|
-
values = hints.grep(/#{argument_value}/)
|
165
|
-
|
166
|
-
respond_with values:
|
167
|
-
end
|
168
|
-
|
169
|
-
with_metadata do
|
417
|
+
define do
|
418
|
+
# The name of the prompt for programmatic use
|
170
419
|
name "brainstorm_excuses"
|
420
|
+
# The human-readable prompt name for display in UI
|
421
|
+
title "Brainstorm Excuses"
|
422
|
+
# A short description of what the tool does
|
171
423
|
description "A prompt for brainstorming excuses to get out of something"
|
172
|
-
end
|
173
424
|
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
425
|
+
# Define arguments to be used with your prompt
|
426
|
+
argument do
|
427
|
+
# The name of the argument
|
428
|
+
name "tone"
|
429
|
+
# A short description of the argument
|
430
|
+
description "The general tone to be used in the generated excuses"
|
431
|
+
# If the argument is required
|
432
|
+
required false
|
433
|
+
# Available hints for completions
|
434
|
+
completion ["whiny", "angry", "callous", "desperate", "nervous", "sneaky"]
|
435
|
+
end
|
179
436
|
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
437
|
+
argument do
|
438
|
+
name "undesirable_activity"
|
439
|
+
description "The thing to get out of"
|
440
|
+
required true
|
441
|
+
end
|
185
442
|
end
|
186
443
|
|
444
|
+
# You can optionally define a custom completion for an argument and pass it to completions.
|
445
|
+
# ToneCompletion = ModelContextProtocol::Server::Completion.define do
|
446
|
+
# hints = ["whiny", "angry", "callous", "desperate", "nervous", "sneaky"]
|
447
|
+
# values = hints.grep(/#{argument_value}/)
|
448
|
+
# respond_with values:
|
449
|
+
# end
|
450
|
+
# ...
|
451
|
+
# define do
|
452
|
+
# argument do
|
453
|
+
# name "tone"
|
454
|
+
# description "The general tone to be used in the generated excuses"
|
455
|
+
# required false
|
456
|
+
# completion ToneCompletion
|
457
|
+
# end
|
458
|
+
# end
|
459
|
+
|
460
|
+
# The call method is invoked by the MCP Server to generate a response to resource/read requests
|
187
461
|
def call
|
462
|
+
# You can use the logger
|
188
463
|
logger.info("Brainstorming excuses...")
|
189
|
-
messages = [
|
190
|
-
{
|
191
|
-
role: "user",
|
192
|
-
content: {
|
193
|
-
type: "text",
|
194
|
-
text: "My wife wants me to: #{arguments[:undesirable_activity]}... Can you believe it?"
|
195
|
-
}
|
196
|
-
},
|
197
|
-
{
|
198
|
-
role: "assistant",
|
199
|
-
content: {
|
200
|
-
type: "text",
|
201
|
-
text: "Oh, that's just downright awful. What are you going to do?"
|
202
|
-
}
|
203
|
-
},
|
204
|
-
{
|
205
|
-
role: "user",
|
206
|
-
content: {
|
207
|
-
type: "text",
|
208
|
-
text: "Well, I'd like to get out of it, but I'm going to need your help."
|
209
|
-
}
|
210
|
-
},
|
211
|
-
{
|
212
|
-
role: "assistant",
|
213
|
-
content: {
|
214
|
-
type: "text",
|
215
|
-
text: "Anything for you."
|
216
|
-
}
|
217
|
-
},
|
218
|
-
{
|
219
|
-
role: "user",
|
220
|
-
content: {
|
221
|
-
type: "text",
|
222
|
-
text: "Can you generate some excuses for me?" + (arguments[:tone] ? "Make them as #{arguments[:tone]} as possible." : "")
|
223
|
-
}
|
224
|
-
}
|
225
|
-
]
|
226
464
|
|
227
|
-
|
465
|
+
# Build an array of user and assistant messages
|
466
|
+
messages = message_history do
|
467
|
+
# Create a message with the user role
|
468
|
+
user_message do
|
469
|
+
# Use any type of content block in a message (text, image, audio, embedded_resource, or resource_link)
|
470
|
+
text_content(text: "My wife wants me to: #{arguments[:undesirable_activity]}... Can you believe it?")
|
471
|
+
end
|
472
|
+
|
473
|
+
# You can also create messages with the assistant role
|
474
|
+
assistant_message do
|
475
|
+
text_content(text: "Oh, that's just downright awful. How can I help?")
|
476
|
+
end
|
477
|
+
|
478
|
+
user_message do
|
479
|
+
# Reference any inputs from the client by accessing the appropriate key in the arguments hash
|
480
|
+
text_content(text: "Can you generate some excuses for me?" + (arguments[:tone] ? " Make them as #{arguments[:tone]} as possible." : ""))
|
481
|
+
end
|
482
|
+
end
|
483
|
+
|
484
|
+
# Respond with the messages
|
485
|
+
respond_with messages:
|
228
486
|
end
|
229
487
|
end
|
230
488
|
```
|
231
489
|
|
232
|
-
|
490
|
+
### Resources
|
491
|
+
|
492
|
+
The `ModelContextProtocol::Server::Resource` base class allows subclasses to define a resource that the MCP client can use.
|
493
|
+
|
494
|
+
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.
|
495
|
+
|
496
|
+
#### Resource Definition
|
497
|
+
|
498
|
+
Use the `define` block to set [resource properties](https://spec.modelcontextprotocol.io/specification/2025-06-18/server/resources/) and configure annotations.
|
499
|
+
|
500
|
+
| Property | Description |
|
501
|
+
|----------|-------------|
|
502
|
+
| `name` | The name of the resource |
|
503
|
+
| `title` | Human-readable display name |
|
504
|
+
| `description` | Short description of what the resource contains |
|
505
|
+
| `mime_type` | MIME type of the resource content |
|
506
|
+
| `uri` | URI identifier for the resource |
|
507
|
+
| `annotations` | Block for defining resource annotations |
|
508
|
+
|
509
|
+
#### Annotation Definition
|
233
510
|
|
234
|
-
|
511
|
+
Define any [resource annotations](https://modelcontextprotocol.io/specification/2025-06-18/server/resources#annotations) using an `annotations` block nested within the `define` block.
|
235
512
|
|
236
|
-
|
513
|
+
| Property | Description |
|
514
|
+
|----------|-------------|
|
515
|
+
| `audience` | Target audience for the resource (array of symbols like `[:user, :assistant]`) |
|
516
|
+
| `priority` | Priority level (numeric value, e.g., `0.9`) |
|
517
|
+
| `last_modified` | Last modified timestamp (ISO 8601 string) |
|
237
518
|
|
238
|
-
|
519
|
+
#### Resource Methods
|
520
|
+
|
521
|
+
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.
|
522
|
+
|
523
|
+
| Method | Context | Description |
|
524
|
+
|--------|---------|-------------|
|
525
|
+
| `define` | Class definition | Block for defining resource metadata and annotations |
|
526
|
+
| `call` | Instance method | Main method to implement resource logic and build response |
|
527
|
+
| `cancellable` | Within `call` | Wrap long-running operations to allow client cancellation (e.g., `cancellable { slow_operation }`) |
|
528
|
+
| `progressable` | Within `call` | Wrap long-running operations to send clients progress notifications (e.g., `progressable { slow_operation }`) |
|
529
|
+
| `respond_with` | Within `call` | Return properly formatted response data (e.g., `respond_with text:` or `respond_with binary:`) |
|
530
|
+
|
531
|
+
#### Available Instance Variables
|
532
|
+
|
533
|
+
Resources are stateless and only have access to their configured properties.
|
534
|
+
|
535
|
+
| Variable | Context | Description |
|
536
|
+
|----------|---------|-------------|
|
537
|
+
| `mime_type` | Within `call` | The configured MIME type for this resource |
|
538
|
+
| `uri` | Within `call` | The configured URI identifier for this resource |
|
539
|
+
|
540
|
+
#### Examples
|
239
541
|
|
240
542
|
This is an example resource that returns a text response:
|
241
543
|
|
242
544
|
```ruby
|
243
545
|
class TestResource < ModelContextProtocol::Server::Resource
|
244
|
-
|
546
|
+
define do
|
245
547
|
name "top-secret-plans.txt"
|
548
|
+
title "Top Secret Plans"
|
246
549
|
description "Top secret plans to do top secret things"
|
247
550
|
mime_type "text/plain"
|
248
551
|
uri "file:///top-secret-plans.txt"
|
249
552
|
end
|
250
553
|
|
251
554
|
def call
|
252
|
-
|
253
|
-
logger.info("This fool thinks he can get my top secret plans...")
|
254
|
-
return respond_with :text, text: "Nothing to see here, move along."
|
255
|
-
end
|
256
|
-
|
257
|
-
respond_with :text, text: "I'm finna eat all my wife's leftovers."
|
555
|
+
respond_with text: "I'm finna eat all my wife's leftovers."
|
258
556
|
end
|
557
|
+
end
|
558
|
+
```
|
259
559
|
|
260
|
-
|
560
|
+
This is an example resource with annotations:
|
561
|
+
|
562
|
+
```ruby
|
563
|
+
class TestAnnotatedResource < ModelContextProtocol::Server::Resource
|
564
|
+
define do
|
565
|
+
name "annotated-document.md"
|
566
|
+
description "A document with annotations showing priority and audience"
|
567
|
+
mime_type "text/markdown"
|
568
|
+
uri "file:///docs/annotated-document.md"
|
569
|
+
annotations do
|
570
|
+
audience [:user, :assistant]
|
571
|
+
priority 0.9
|
572
|
+
last_modified "2025-01-12T15:00:58Z"
|
573
|
+
end
|
574
|
+
end
|
261
575
|
|
262
|
-
def
|
263
|
-
|
264
|
-
authorized_users.any?(user_id)
|
576
|
+
def call
|
577
|
+
respond_with text: "# Annotated Document\n\nThis document has annotations."
|
265
578
|
end
|
266
579
|
end
|
267
580
|
```
|
@@ -270,7 +583,7 @@ This is an example resource that returns binary data:
|
|
270
583
|
|
271
584
|
```ruby
|
272
585
|
class TestBinaryResource < ModelContextProtocol::Server::Resource
|
273
|
-
|
586
|
+
define do
|
274
587
|
name "project-logo.png"
|
275
588
|
description "The logo for the project"
|
276
589
|
mime_type "image/png"
|
@@ -281,53 +594,228 @@ class TestBinaryResource < ModelContextProtocol::Server::Resource
|
|
281
594
|
# In a real implementation, we would retrieve the binary resource
|
282
595
|
# This is a small valid base64 encoded string (represents "test")
|
283
596
|
data = "dGVzdA=="
|
284
|
-
respond_with
|
597
|
+
respond_with binary: data
|
285
598
|
end
|
286
599
|
end
|
287
600
|
```
|
288
601
|
|
289
|
-
|
602
|
+
### Resource Templates
|
603
|
+
|
604
|
+
The `ModelContextProtocol::Server::ResourceTemplate` base class allows subclasses to define a resource template that the MCP client can use.
|
605
|
+
|
606
|
+
Define the resource template properties and URI template with optional parameter completions. Resource templates are used to define parameterized resources that clients can instantiate.
|
607
|
+
|
608
|
+
#### Resource Template Definition
|
290
609
|
|
291
|
-
|
610
|
+
Use the `define` block to set [resource template properties](https://modelcontextprotocol.io/specification/2025-06-18/server/resources#resource-templates).
|
611
|
+
|
612
|
+
| Property | Description |
|
613
|
+
|----------|-------------|
|
614
|
+
| `name` | The name of the resource template |
|
615
|
+
| `description` | Short description of what the template provides |
|
616
|
+
| `mime_type` | MIME type of resources created from this template |
|
617
|
+
| `uri_template` | URI template with parameters (e.g., `"file:///{name}"`) |
|
618
|
+
|
619
|
+
#### URI Template Configuration
|
620
|
+
|
621
|
+
Define the URI template and configure parameter completions within the `uri_template` block.
|
622
|
+
|
623
|
+
| Method | Context | Description |
|
624
|
+
|--------|---------|-------------|
|
625
|
+
| `completion` | Within `uri_template` block | Define completion for a URI parameter (e.g., `completion :name, ["value1", "value2"]`) |
|
626
|
+
|
627
|
+
#### Resource Template Methods
|
628
|
+
|
629
|
+
Resource templates only use the `define` method to configure their properties - they don't have a `call` method.
|
630
|
+
|
631
|
+
| Method | Context | Description |
|
632
|
+
|--------|---------|-------------|
|
633
|
+
| `define` | Class definition | Block for defining resource template metadata and URI template |
|
634
|
+
|
635
|
+
#### Examples
|
292
636
|
|
293
637
|
This is an example resource template that provides a completion for a parameter of the URI template:
|
294
638
|
|
295
639
|
```ruby
|
296
640
|
class TestResourceTemplate < ModelContextProtocol::Server::ResourceTemplate
|
297
|
-
|
298
|
-
hints = {
|
299
|
-
"name" => ["top-secret-plans.txt"]
|
300
|
-
}
|
301
|
-
values = hints[argument_name].grep(/#{argument_value}/)
|
302
|
-
|
303
|
-
respond_with values:
|
304
|
-
end
|
305
|
-
|
306
|
-
with_metadata do
|
641
|
+
define do
|
307
642
|
name "project-document-resource-template"
|
308
643
|
description "A resource template for retrieving project documents"
|
309
644
|
mime_type "text/plain"
|
310
645
|
uri_template "file:///{name}" do
|
311
|
-
completion :name,
|
646
|
+
completion :name, ["top-secret-plans.txt"]
|
312
647
|
end
|
313
648
|
end
|
649
|
+
|
650
|
+
# You can optionally define a custom completion for an argument and pass it to completions.
|
651
|
+
# Completion = ModelContextProtocol::Server::Completion.define do
|
652
|
+
# hints = {
|
653
|
+
# "name" => ["top-secret-plans.txt"]
|
654
|
+
# }
|
655
|
+
# values = hints[argument_name].grep(/#{argument_value}/)
|
656
|
+
|
657
|
+
# respond_with values:
|
658
|
+
# end
|
659
|
+
|
660
|
+
# define do
|
661
|
+
# name "project-document-resource-template"
|
662
|
+
# description "A resource template for retrieving project documents"
|
663
|
+
# mime_type "text/plain"
|
664
|
+
# uri_template "file:///{name}" do
|
665
|
+
# completion :name, Completion
|
666
|
+
# end
|
667
|
+
# end
|
314
668
|
end
|
315
669
|
```
|
316
670
|
|
317
|
-
|
671
|
+
### Tools
|
672
|
+
|
673
|
+
The `ModelContextProtocol::Server::Tool` base class allows subclasses to define a tool that the MCP client can use.
|
674
|
+
|
675
|
+
Define the tool properties and schemas, then implement the `call` method to build your tool response. Arguments from the MCP client and server context are available, along with logging capabilities.
|
676
|
+
|
677
|
+
#### Tool Definition
|
678
|
+
|
679
|
+
Use the `define` block to set [tool properties](https://spec.modelcontextprotocol.io/specification/2025-06-18/server/tools/) and configure schemas.
|
680
|
+
|
681
|
+
| Property | Description |
|
682
|
+
|----------|-------------|
|
683
|
+
| `name` | The programmatic name of the tool |
|
684
|
+
| `title` | Human-readable display name |
|
685
|
+
| `description` | Short description of what the tool does |
|
686
|
+
| `input_schema` | JSON schema block for validating tool inputs |
|
687
|
+
| `output_schema` | JSON schema block for validating structured content outputs |
|
688
|
+
|
689
|
+
#### Tool Methods
|
690
|
+
|
691
|
+
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.
|
692
|
+
|
693
|
+
| Method | Context | Description |
|
694
|
+
|--------|---------|-------------|
|
695
|
+
| `define` | Class definition | Block for defining tool metadata and schemas |
|
696
|
+
| `call` | Instance method | Main method to implement tool logic and build response |
|
697
|
+
| `cancellable` | Within `call` | Wrap long-running operations to allow client cancellation (e.g., `cancellable { slow_operation }`) |
|
698
|
+
| `progressable` | Within `call` | Wrap long-running operations to send clients progress notifications (e.g., `progressable { slow_operation }`) |
|
699
|
+
| `respond_with` | Within `call` | Return properly formatted response data with various content types |
|
700
|
+
|
701
|
+
#### Content Blocks
|
702
|
+
|
703
|
+
Use content blocks to properly format the content included in tool responses.
|
704
|
+
|
705
|
+
| Method | Context | Description |
|
706
|
+
|--------|---------|-------------|
|
707
|
+
| `text_content` | Within `call` | Create text content block |
|
708
|
+
| `image_content` | Within `call` | Create image content block (requires `data:` and `mime_type:`) |
|
709
|
+
| `audio_content` | Within `call` | Create audio content block (requires `data:` and `mime_type:`) |
|
710
|
+
| `embedded_resource_content` | Within `call` | Create embedded resource content block (requires `resource:`) |
|
711
|
+
| `resource_link` | Within `call` | Create resource link content block (requires `name:` and `uri:`) |
|
318
712
|
|
319
|
-
|
713
|
+
#### Response Types
|
320
714
|
|
321
|
-
|
715
|
+
Tools can return different types of responses using `respond_with`.
|
322
716
|
|
323
|
-
|
717
|
+
| Response Type | Usage | Description |
|
718
|
+
|---------------|-------|-------------|
|
719
|
+
| `structured_content:` | `respond_with structured_content: data` | Return structured data validated against output schema |
|
720
|
+
| `content:` | `respond_with content: content_block` | Return single content block |
|
721
|
+
| `content:` | `respond_with content: [content_blocks]` | Return array of mixed content blocks |
|
722
|
+
| `error:` | `respond_with error: "message"` | Return tool error response |
|
723
|
+
|
724
|
+
#### Available Instance Variables
|
725
|
+
|
726
|
+
Arguments from MCP clients and server context are available, along with logging capabilities.
|
727
|
+
|
728
|
+
| Variable | Context | Description |
|
729
|
+
|----------|---------|-------------|
|
730
|
+
| `arguments` | Within `call` | Hash containing client-provided arguments (symbol keys) |
|
731
|
+
| `context` | Within `call` | Hash containing server configuration context values |
|
732
|
+
| `logger` | Within `call` | Logger instance for logging (e.g., `logger.info("message")`) |
|
733
|
+
|
734
|
+
#### Examples
|
735
|
+
|
736
|
+
This is an example of a tool that returns structured content validated by an output schema:
|
737
|
+
|
738
|
+
```ruby
|
739
|
+
class TestToolWithStructuredContentResponse < ModelContextProtocol::Server::Tool
|
740
|
+
define do
|
741
|
+
# The name of the tool for programmatic use
|
742
|
+
name "get_weather_data"
|
743
|
+
# The human-readable tool name for display in UI
|
744
|
+
title "Weather Data Retriever"
|
745
|
+
# A short description of what the tool does
|
746
|
+
description "Get current weather data for a location"
|
747
|
+
# The JSON schema for validating tool inputs
|
748
|
+
input_schema do
|
749
|
+
{
|
750
|
+
type: "object",
|
751
|
+
properties: {
|
752
|
+
location: {
|
753
|
+
type: "string",
|
754
|
+
description: "City name or zip code"
|
755
|
+
}
|
756
|
+
},
|
757
|
+
required: ["location"]
|
758
|
+
}
|
759
|
+
end
|
760
|
+
# The JSON schema for validating structured content
|
761
|
+
output_schema do
|
762
|
+
{
|
763
|
+
type: "object",
|
764
|
+
properties: {
|
765
|
+
temperature: {
|
766
|
+
type: "number",
|
767
|
+
description: "Temperature in celsius"
|
768
|
+
},
|
769
|
+
conditions: {
|
770
|
+
type: "string",
|
771
|
+
description: "Weather conditions description"
|
772
|
+
},
|
773
|
+
humidity: {
|
774
|
+
type: "number",
|
775
|
+
description: "Humidity percentage"
|
776
|
+
}
|
777
|
+
},
|
778
|
+
required: ["temperature", "conditions", "humidity"]
|
779
|
+
}
|
780
|
+
end
|
781
|
+
end
|
782
|
+
|
783
|
+
def call
|
784
|
+
# Use values provided by the server as context
|
785
|
+
user_id = context[:user_id]
|
786
|
+
logger.info("Initiating request for user #{user_id}...")
|
787
|
+
|
788
|
+
# Use values provided by clients as tool arguments
|
789
|
+
location = arguments[:location]
|
790
|
+
logger.info("Getting weather data for #{location}...")
|
791
|
+
|
792
|
+
# Returns a hash that validates against the output schema
|
793
|
+
weather_data = get_weather_data(location)
|
794
|
+
|
795
|
+
# Respond with structured content
|
796
|
+
respond_with structured_content: weather_data
|
797
|
+
end
|
798
|
+
|
799
|
+
private
|
800
|
+
|
801
|
+
# Simulate calling an external API to get weather data for the provided input
|
802
|
+
def get_weather_data(location)
|
803
|
+
{
|
804
|
+
temperature: 22.5,
|
805
|
+
conditions: "Partly cloudy",
|
806
|
+
humidity: 65
|
807
|
+
}
|
808
|
+
end
|
809
|
+
end
|
810
|
+
```
|
324
811
|
|
325
812
|
This is an example tool that returns a text response:
|
326
813
|
|
327
814
|
```ruby
|
328
815
|
class TestToolWithTextResponse < ModelContextProtocol::Server::Tool
|
329
|
-
|
816
|
+
define do
|
330
817
|
name "double"
|
818
|
+
title "Number Doubler"
|
331
819
|
description "Doubles the provided number"
|
332
820
|
input_schema do
|
333
821
|
{
|
@@ -343,12 +831,15 @@ class TestToolWithTextResponse < ModelContextProtocol::Server::Tool
|
|
343
831
|
end
|
344
832
|
|
345
833
|
def call
|
346
|
-
user_id = context[:user_id]
|
347
|
-
number = arguments[:number].to_i
|
348
834
|
logger.info("Silly user doesn't know how to double a number")
|
835
|
+
number = arguments[:number].to_i
|
349
836
|
calculation = number * 2
|
837
|
+
|
838
|
+
user_id = context[:user_id]
|
350
839
|
salutation = user_id ? "User #{user_id}, " : ""
|
351
|
-
|
840
|
+
text_content = text_content(text: salutation << "#{number} doubled is #{calculation}")
|
841
|
+
|
842
|
+
respond_with content: text_content
|
352
843
|
end
|
353
844
|
end
|
354
845
|
```
|
@@ -357,7 +848,7 @@ This is an example of a tool that returns an image:
|
|
357
848
|
|
358
849
|
```ruby
|
359
850
|
class TestToolWithImageResponse < ModelContextProtocol::Server::Tool
|
360
|
-
|
851
|
+
define do
|
361
852
|
name "custom-chart-generator"
|
362
853
|
description "Generates a chart in various formats"
|
363
854
|
input_schema do
|
@@ -391,68 +882,216 @@ class TestToolWithImageResponse < ModelContextProtocol::Server::Tool
|
|
391
882
|
|
392
883
|
# In a real implementation, we would generate an actual chart
|
393
884
|
# This is a small valid base64 encoded string (represents "test")
|
394
|
-
|
395
|
-
|
885
|
+
data = "dGVzdA=="
|
886
|
+
image_content = image_content(data:, mime_type:)
|
887
|
+
respond_with content: image_content
|
396
888
|
end
|
397
889
|
end
|
398
890
|
```
|
399
891
|
|
400
|
-
|
892
|
+
This is an example of a tool that returns an embedded resource response:
|
401
893
|
|
402
894
|
```ruby
|
403
|
-
class
|
404
|
-
|
405
|
-
name "
|
406
|
-
description "
|
895
|
+
class TestToolWithResourceResponse < ModelContextProtocol::Server::Tool
|
896
|
+
define do
|
897
|
+
name "resource-finder"
|
898
|
+
description "Finds a resource given a name"
|
407
899
|
input_schema do
|
408
900
|
{
|
409
901
|
type: "object",
|
410
902
|
properties: {
|
411
|
-
|
903
|
+
name: {
|
412
904
|
type: "string",
|
413
|
-
description: "
|
905
|
+
description: "The name of the resource"
|
414
906
|
}
|
415
907
|
},
|
416
|
-
required: ["
|
908
|
+
required: ["name"]
|
417
909
|
}
|
418
910
|
end
|
419
911
|
end
|
420
912
|
|
913
|
+
RESOURCE_MAPPINGS = {
|
914
|
+
test_annotated_resource: TestAnnotatedResource,
|
915
|
+
test_binary_resource: TestBinaryResource,
|
916
|
+
test_resource: TestResource
|
917
|
+
}.freeze
|
918
|
+
|
421
919
|
def call
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
920
|
+
name = arguments[:name]
|
921
|
+
resource_klass = RESOURCE_MAPPINGS[name.downcase.to_sym]
|
922
|
+
unless resource_klass
|
923
|
+
return respond_with :error, text: "Resource `#{name}` not found"
|
924
|
+
end
|
925
|
+
|
926
|
+
resource_data = resource_klass.call
|
927
|
+
|
928
|
+
respond_with content: embedded_resource_content(resource: resource_data)
|
426
929
|
end
|
427
930
|
end
|
428
931
|
```
|
429
932
|
|
430
|
-
This is an example of a tool that returns
|
933
|
+
This is an example of a tool that returns mixed content:
|
431
934
|
|
432
935
|
```ruby
|
433
|
-
class
|
434
|
-
|
435
|
-
name "
|
436
|
-
description "
|
936
|
+
class TestToolWithMixedContentResponse < ModelContextProtocol::Server::Tool
|
937
|
+
define do
|
938
|
+
name "get_temperature_history"
|
939
|
+
description "Gets comprehensive temperature history for a zip code"
|
940
|
+
input_schema do
|
941
|
+
{
|
942
|
+
type: "object",
|
943
|
+
properties: {
|
944
|
+
zip: {
|
945
|
+
type: "string"
|
946
|
+
}
|
947
|
+
},
|
948
|
+
required: ["zip"]
|
949
|
+
}
|
950
|
+
end
|
951
|
+
end
|
952
|
+
|
953
|
+
def call
|
954
|
+
logger.info("Getting comprehensive temperature history data")
|
955
|
+
|
956
|
+
zip = arguments[:zip]
|
957
|
+
temperature_history = retrieve_temperature_history(zip:)
|
958
|
+
temperature_history_block = text_content(text: temperature_history.join(", "))
|
959
|
+
|
960
|
+
temperature_chart = generate_weather_history_chart(temperature_history)
|
961
|
+
temperature_chart_block = image_content(
|
962
|
+
data: temperature_chart[:base64_chart_data],
|
963
|
+
mime_type: temperature_chart[:mime_type]
|
964
|
+
)
|
965
|
+
|
966
|
+
respond_with content: [temperature_history_block, temperature_chart_block]
|
967
|
+
end
|
968
|
+
|
969
|
+
private
|
970
|
+
|
971
|
+
def retrieve_temperature_history(zip:)
|
972
|
+
# Simulates a call to an API or DB to retrieve weather history
|
973
|
+
[85.2, 87.4, 89.0, 95.3, 96.0]
|
974
|
+
end
|
975
|
+
|
976
|
+
def generate_weather_history_chart(history)
|
977
|
+
# SImulate a call to generate a chart given the weather history
|
978
|
+
{
|
979
|
+
base64_chart_data: "dGVzdA==",
|
980
|
+
mime_type: "image/png"
|
981
|
+
}
|
982
|
+
end
|
983
|
+
end
|
984
|
+
```
|
985
|
+
|
986
|
+
This is an example of a tool that returns a tool error response:
|
987
|
+
|
988
|
+
```ruby
|
989
|
+
class TestToolWithToolErrorResponse < ModelContextProtocol::Server::Tool
|
990
|
+
define do
|
991
|
+
name "api-caller"
|
992
|
+
description "Makes calls to external APIs"
|
437
993
|
input_schema do
|
438
994
|
{
|
439
995
|
type: "object",
|
440
996
|
properties: {
|
441
|
-
|
997
|
+
api_endpoint: {
|
998
|
+
type: "string",
|
999
|
+
description: "API endpoint URL"
|
1000
|
+
},
|
1001
|
+
method: {
|
442
1002
|
type: "string",
|
443
|
-
description: "
|
1003
|
+
description: "HTTP method (GET, POST, etc)"
|
444
1004
|
}
|
445
1005
|
},
|
446
|
-
required: ["
|
1006
|
+
required: ["api_endpoint", "method"]
|
1007
|
+
}
|
1008
|
+
end
|
1009
|
+
end
|
1010
|
+
|
1011
|
+
def call
|
1012
|
+
# Simulate an API call failure
|
1013
|
+
respond_with error: "Failed to call API at #{arguments[:api_endpoint]}: Connection timed out"
|
1014
|
+
end
|
1015
|
+
end
|
1016
|
+
```
|
1017
|
+
|
1018
|
+
This is an example of a tool that allows a client to cancel a long-running operation:
|
1019
|
+
|
1020
|
+
```ruby
|
1021
|
+
class TestToolWithCancellableSleep < ModelContextProtocol::Server::Tool
|
1022
|
+
define do
|
1023
|
+
name "cancellable_sleep"
|
1024
|
+
title "Cancellable Sleep Tool"
|
1025
|
+
description "Sleep for 3 seconds with cancellation support"
|
1026
|
+
input_schema do
|
1027
|
+
{
|
1028
|
+
type: "object",
|
1029
|
+
properties: {},
|
1030
|
+
additionalProperties: false
|
447
1031
|
}
|
448
1032
|
end
|
449
1033
|
end
|
450
1034
|
|
451
1035
|
def call
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
1036
|
+
logger.info("Starting 3 second sleep operation")
|
1037
|
+
|
1038
|
+
result = cancellable do
|
1039
|
+
sleep 3
|
1040
|
+
"Sleep completed successfully"
|
1041
|
+
end
|
1042
|
+
|
1043
|
+
respond_with content: text_content(text: result)
|
1044
|
+
end
|
1045
|
+
end
|
1046
|
+
```
|
1047
|
+
|
1048
|
+
This is an example of a tool that automatically sends progress notifications to the client and allows the client to cancel the operation:
|
1049
|
+
|
1050
|
+
```ruby
|
1051
|
+
class TestToolWithProgressableAndCancellable < ModelContextProtocol::Server::Tool
|
1052
|
+
define do
|
1053
|
+
name "test_tool_with_progressable_and_cancellable"
|
1054
|
+
description "A test tool that demonstrates combined progressable and cancellable functionality"
|
1055
|
+
|
1056
|
+
input_schema do
|
1057
|
+
{
|
1058
|
+
type: "object",
|
1059
|
+
properties: {
|
1060
|
+
max_duration: {
|
1061
|
+
type: "number",
|
1062
|
+
description: "Expected maximum duration in seconds"
|
1063
|
+
},
|
1064
|
+
work_steps: {
|
1065
|
+
type: "number",
|
1066
|
+
description: "Number of work steps to perform"
|
1067
|
+
}
|
1068
|
+
},
|
1069
|
+
required: ["max_duration"]
|
1070
|
+
}
|
1071
|
+
end
|
1072
|
+
end
|
1073
|
+
|
1074
|
+
def call
|
1075
|
+
max_duration = arguments[:max_duration] || 10
|
1076
|
+
work_steps = arguments[:work_steps] || 10
|
1077
|
+
logger.info("Starting progressable call with max_duration=#{max_duration}, work_steps=#{work_steps}")
|
1078
|
+
|
1079
|
+
result = progressable(max_duration:, message: "Processing #{work_steps} items") do
|
1080
|
+
cancellable do
|
1081
|
+
processed_items = []
|
1082
|
+
|
1083
|
+
work_steps.times do |i|
|
1084
|
+
sleep(max_duration / work_steps.to_f)
|
1085
|
+
processed_items << "item_#{i + 1}"
|
1086
|
+
end
|
1087
|
+
|
1088
|
+
processed_items
|
1089
|
+
end
|
1090
|
+
end
|
1091
|
+
|
1092
|
+
response = text_content(text: "Successfully processed #{result.length} items: #{result.join(", ")}")
|
1093
|
+
|
1094
|
+
respond_with content: response
|
456
1095
|
end
|
457
1096
|
end
|
458
1097
|
```
|
@@ -461,7 +1100,27 @@ end
|
|
461
1100
|
|
462
1101
|
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.
|
463
1102
|
|
464
|
-
|
1103
|
+
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.
|
1104
|
+
|
1105
|
+
#### Completion Methods
|
1106
|
+
|
1107
|
+
Completions only implement the `call` method to provide completion logic.
|
1108
|
+
|
1109
|
+
| Method | Context | Description |
|
1110
|
+
|--------|---------|-------------|
|
1111
|
+
| `call` | Instance method | Main method to implement completion logic and build response |
|
1112
|
+
| `respond_with` | Within `call` | Return properly formatted completion response (e.g., `respond_with values:`) |
|
1113
|
+
|
1114
|
+
#### Available Instance Variables
|
1115
|
+
|
1116
|
+
Completions receive the argument name and current value being completed.
|
1117
|
+
|
1118
|
+
| Variable | Context | Description |
|
1119
|
+
|----------|---------|-------------|
|
1120
|
+
| `argument_name` | Within `call` | String name of the argument being completed |
|
1121
|
+
| `argument_value` | Within `call` | Current partial value being typed by the user |
|
1122
|
+
|
1123
|
+
#### Examples
|
465
1124
|
|
466
1125
|
This is an example completion that returns an array of values in the response:
|
467
1126
|
|
@@ -500,19 +1159,50 @@ gem install model-context-protocol-rb
|
|
500
1159
|
|
501
1160
|
## Development
|
502
1161
|
|
503
|
-
After checking out the repo, run `bin/setup` to install dependencies. Then, run `
|
1162
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rspec` to run the tests.
|
1163
|
+
|
1164
|
+
### Generate Development Servers
|
1165
|
+
|
1166
|
+
Generate executables that you can use for testing:
|
1167
|
+
|
1168
|
+
```bash
|
1169
|
+
# generates bin/dev for STDIO transport
|
1170
|
+
bundle exec rake mcp:generate_stdio_server
|
1171
|
+
|
1172
|
+
# generates bin/dev-http for streamable HTTP transport
|
1173
|
+
bundle exec rake mcp:generate_streamable_http_server
|
1174
|
+
```
|
504
1175
|
|
505
|
-
|
1176
|
+
If you need to test with HTTPS (e.g., for clients that require SSL), generate self-signed certificates:
|
506
1177
|
|
507
1178
|
```bash
|
508
|
-
|
1179
|
+
# Create SSL directory and generate certificates
|
1180
|
+
mkdir -p tmp/ssl
|
1181
|
+
openssl req -x509 -newkey rsa:4096 -keyout tmp/ssl/server.key -out tmp/ssl/server.crt -days 365 -nodes -subj "/C=US/ST=Dev/L=Dev/O=Dev/CN=localhost"
|
509
1182
|
```
|
510
1183
|
|
511
|
-
|
1184
|
+
The HTTP server supports both HTTP and HTTPS:
|
1185
|
+
|
1186
|
+
```bash
|
1187
|
+
# Run HTTP server (default)
|
1188
|
+
bin/dev-http
|
1189
|
+
|
1190
|
+
# Run HTTPS server (requires SSL certificates in tmp/ssl/)
|
1191
|
+
SSL=true bin/dev-http
|
1192
|
+
```
|
1193
|
+
|
1194
|
+
You can also run `bin/console` for an interactive prompt that will allow you to experiment. Execute command `rp` to reload the project.
|
1195
|
+
|
1196
|
+
To install this gem onto your local machine, run `bundle exec rake install`.
|
1197
|
+
|
1198
|
+
### Releases
|
512
1199
|
|
513
|
-
|
1200
|
+
To release a new version, update the version number in `version.rb`, and submit a PR. After the PR has been merged to main, run `bundle exec rake release`, which will:
|
1201
|
+
* create a git tag for the version,
|
1202
|
+
* push the created tag,
|
1203
|
+
* and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
514
1204
|
|
515
|
-
|
1205
|
+
Then, draft and publish release notes in Github.
|
516
1206
|
|
517
1207
|
## Contributing
|
518
1208
|
|