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