mcp 0.11.0 → 0.12.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/README.md +935 -927
- data/lib/mcp/client/http.rb +4 -1
- data/lib/mcp/server/transports/streamable_http_transport.rb +30 -15
- data/lib/mcp/transport.rb +1 -0
- data/lib/mcp/version.rb +1 -1
- metadata +2 -2
data/README.md
CHANGED
|
@@ -54,1208 +54,1204 @@ It implements the Model Context Protocol specification, handling model context r
|
|
|
54
54
|
- `completion/complete` - Returns autocompletion suggestions for prompt arguments and resource URIs
|
|
55
55
|
- `sampling/createMessage` - Requests LLM completion from the client (server-to-client)
|
|
56
56
|
|
|
57
|
-
###
|
|
57
|
+
### Usage
|
|
58
58
|
|
|
59
|
-
|
|
59
|
+
#### Stdio Transport
|
|
60
|
+
|
|
61
|
+
If you want to build a local command-line application, you can use the stdio transport:
|
|
60
62
|
|
|
61
63
|
```ruby
|
|
62
|
-
|
|
64
|
+
require "mcp"
|
|
63
65
|
|
|
64
|
-
#
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
# Create a simple tool
|
|
67
|
+
class ExampleTool < MCP::Tool
|
|
68
|
+
description "A simple example tool that echoes back its arguments"
|
|
69
|
+
input_schema(
|
|
70
|
+
properties: {
|
|
71
|
+
message: { type: "string" },
|
|
72
|
+
},
|
|
73
|
+
required: ["message"]
|
|
74
|
+
)
|
|
68
75
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
76
|
+
class << self
|
|
77
|
+
def call(message:, server_context:)
|
|
78
|
+
MCP::Tool::Response.new([{
|
|
79
|
+
type: "text",
|
|
80
|
+
text: "Hello from example tool! Message: #{message}",
|
|
81
|
+
}])
|
|
82
|
+
end
|
|
83
|
+
end
|
|
73
84
|
end
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
**Key Features:**
|
|
77
85
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
86
|
+
# Set up the server
|
|
87
|
+
server = MCP::Server.new(
|
|
88
|
+
name: "example_server",
|
|
89
|
+
tools: [ExampleTool],
|
|
90
|
+
)
|
|
83
91
|
|
|
84
|
-
|
|
92
|
+
# Create and start the transport
|
|
93
|
+
transport = MCP::Server::Transports::StdioTransport.new(server)
|
|
94
|
+
transport.open
|
|
95
|
+
```
|
|
85
96
|
|
|
86
|
-
|
|
87
|
-
# Client request
|
|
88
|
-
{
|
|
89
|
-
"jsonrpc": "2.0",
|
|
90
|
-
"id": 1,
|
|
91
|
-
"method": "add",
|
|
92
|
-
"params": { "a": 5, "b": 3 }
|
|
93
|
-
}
|
|
97
|
+
You can run this script and then type in requests to the server at the command line.
|
|
94
98
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
99
|
+
```console
|
|
100
|
+
$ ruby examples/stdio_server.rb
|
|
101
|
+
{"jsonrpc":"2.0","id":"1","method":"ping"}
|
|
102
|
+
{"jsonrpc":"2.0","id":"2","method":"tools/list"}
|
|
103
|
+
{"jsonrpc":"2.0","id":"3","method":"tools/call","params":{"name":"example_tool","arguments":{"message":"Hello"}}}
|
|
101
104
|
```
|
|
102
105
|
|
|
103
|
-
|
|
106
|
+
#### Rails Controller
|
|
104
107
|
|
|
105
|
-
|
|
106
|
-
-
|
|
108
|
+
When added to a Rails controller on a route that handles POST requests, your server will be compliant with non-streaming
|
|
109
|
+
[Streamable HTTP](https://modelcontextprotocol.io/specification/latest/basic/transports#streamable-http) transport
|
|
110
|
+
requests.
|
|
107
111
|
|
|
108
|
-
|
|
112
|
+
You can use `StreamableHTTPTransport#handle_request` to handle requests with proper HTTP
|
|
113
|
+
status codes (e.g., 202 Accepted for notifications).
|
|
109
114
|
|
|
110
|
-
|
|
111
|
-
|
|
115
|
+
```ruby
|
|
116
|
+
class McpController < ActionController::API
|
|
117
|
+
def create
|
|
118
|
+
server = MCP::Server.new(
|
|
119
|
+
name: "my_server",
|
|
120
|
+
title: "Example Server Display Name",
|
|
121
|
+
version: "1.0.0",
|
|
122
|
+
instructions: "Use the tools of this server as a last resort",
|
|
123
|
+
tools: [SomeTool, AnotherTool],
|
|
124
|
+
prompts: [MyPrompt],
|
|
125
|
+
server_context: { user_id: current_user.id },
|
|
126
|
+
)
|
|
127
|
+
# Since the `MCP-Session-Id` is not shared across requests, `stateless: true` is set.
|
|
128
|
+
transport = MCP::Server::Transports::StreamableHTTPTransport.new(server, stateless: true)
|
|
129
|
+
status, headers, body = transport.handle_request(request)
|
|
112
130
|
|
|
113
|
-
|
|
131
|
+
render(json: body.first, status: status, headers: headers)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
```
|
|
114
135
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
136
|
+
> [!IMPORTANT]
|
|
137
|
+
> `MCP::Server::Transports::StreamableHTTPTransport` stores session and SSE stream state in memory,
|
|
138
|
+
> so it must run in a single process. Use a single-process server (e.g., Puma with `workers 0`).
|
|
139
|
+
> Multi-process configurations (Unicorn, or Puma with `workers > 0`) fork separate processes that
|
|
140
|
+
> do not share memory, which breaks session management and SSE connections.
|
|
141
|
+
>
|
|
142
|
+
> When running multiple server instances behind a load balancer, configure your load balancer to use
|
|
143
|
+
> sticky sessions (session affinity) so that requests with the same `Mcp-Session-Id` header are always
|
|
144
|
+
> routed to the same instance.
|
|
145
|
+
>
|
|
146
|
+
> Stateless mode (`stateless: true`) does not use sessions and works with any server configuration.
|
|
119
147
|
|
|
120
|
-
|
|
148
|
+
### Configuration
|
|
121
149
|
|
|
122
|
-
|
|
123
|
-
For multi-client transports (e.g., `StreamableHTTPTransport`), use `server_context.create_sampling_message` inside tools instead,
|
|
124
|
-
which routes the request to the correct client session.
|
|
150
|
+
The gem can be configured using the `MCP.configure` block:
|
|
125
151
|
|
|
126
152
|
```ruby
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
153
|
+
MCP.configure do |config|
|
|
154
|
+
config.exception_reporter = ->(exception, server_context) {
|
|
155
|
+
# Your exception reporting logic here
|
|
156
|
+
# For example with Bugsnag:
|
|
157
|
+
Bugsnag.notify(exception) do |report|
|
|
158
|
+
report.add_metadata(:model_context_protocol, server_context)
|
|
159
|
+
end
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
config.instrumentation_callback = ->(data) {
|
|
163
|
+
puts "Got instrumentation data #{data.inspect}"
|
|
164
|
+
}
|
|
165
|
+
end
|
|
130
166
|
```
|
|
131
167
|
|
|
132
|
-
|
|
133
|
-
This
|
|
168
|
+
or by creating an explicit configuration and passing it into the server.
|
|
169
|
+
This is useful for systems where an application hosts more than one MCP server but
|
|
170
|
+
they might require different instrumentation callbacks.
|
|
134
171
|
|
|
135
172
|
```ruby
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
173
|
+
configuration = MCP::Configuration.new
|
|
174
|
+
configuration.exception_reporter = ->(exception, server_context) {
|
|
175
|
+
# Your exception reporting logic here
|
|
176
|
+
# For example with Bugsnag:
|
|
177
|
+
Bugsnag.notify(exception) do |report|
|
|
178
|
+
report.add_metadata(:model_context_protocol, server_context)
|
|
179
|
+
end
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
configuration.instrumentation_callback = ->(data) {
|
|
183
|
+
puts "Got instrumentation data #{data.inspect}"
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
server = MCP::Server.new(
|
|
187
|
+
# ... all other options
|
|
188
|
+
configuration:,
|
|
143
189
|
)
|
|
144
190
|
```
|
|
145
191
|
|
|
146
|
-
|
|
192
|
+
### Server Context and Configuration Block Data
|
|
193
|
+
|
|
194
|
+
#### `server_context`
|
|
195
|
+
|
|
196
|
+
The `server_context` is a user-defined hash that is passed into the server instance and made available to tools, prompts, and exception/instrumentation callbacks. It can be used to provide contextual information such as authentication state, user IDs, or request-specific data.
|
|
197
|
+
|
|
198
|
+
**Type:**
|
|
147
199
|
|
|
148
200
|
```ruby
|
|
149
|
-
{
|
|
150
|
-
role: "assistant",
|
|
151
|
-
content: { type: "text", text: "The capital of France is Paris." },
|
|
152
|
-
model: "claude-3-sonnet-20240307",
|
|
153
|
-
stopReason: "endTurn"
|
|
154
|
-
}
|
|
201
|
+
server_context: { [String, Symbol] => Any }
|
|
155
202
|
```
|
|
156
203
|
|
|
157
|
-
**
|
|
204
|
+
**Example:**
|
|
158
205
|
|
|
159
|
-
|
|
206
|
+
```ruby
|
|
207
|
+
server = MCP::Server.new(
|
|
208
|
+
name: "my_server",
|
|
209
|
+
server_context: { user_id: current_user.id, request_id: request.uuid }
|
|
210
|
+
)
|
|
211
|
+
```
|
|
160
212
|
|
|
161
|
-
|
|
162
|
-
- `max_tokens:` (Integer) - Maximum tokens in the response
|
|
213
|
+
This hash is then passed as the `server_context` argument to tool and prompt calls, and is included in exception and instrumentation callbacks.
|
|
163
214
|
|
|
164
|
-
|
|
215
|
+
#### Request-specific `_meta` Parameter
|
|
165
216
|
|
|
166
|
-
|
|
167
|
-
- `model_preferences:` (Hash) - Model selection preferences (e.g., `{ intelligencePriority: 0.8 }`)
|
|
168
|
-
- `include_context:` (String) - Context inclusion: `"none"`, `"thisServer"`, or `"allServers"` (soft-deprecated)
|
|
169
|
-
- `temperature:` (Float) - Sampling temperature
|
|
170
|
-
- `stop_sequences:` (Array) - Sequences that stop generation
|
|
171
|
-
- `metadata:` (Hash) - Additional metadata
|
|
172
|
-
- `tools:` (Array) - Tools available to the LLM (requires `sampling.tools` capability)
|
|
173
|
-
- `tool_choice:` (Hash) - Tool selection mode (e.g., `{ mode: "auto" }`)
|
|
217
|
+
The MCP protocol supports a special [`_meta` parameter](https://modelcontextprotocol.io/specification/2025-06-18/basic#general-fields) in requests that allows clients to pass request-specific metadata. The server automatically extracts this parameter and makes it available to tools and prompts as a nested field within the `server_context`.
|
|
174
218
|
|
|
175
|
-
**
|
|
219
|
+
**Access Pattern:**
|
|
176
220
|
|
|
177
|
-
|
|
178
|
-
The request is automatically routed to the correct client session.
|
|
179
|
-
Set `server.server_context = server` so that `server_context.create_sampling_message` delegates to the server:
|
|
221
|
+
When a client includes `_meta` in the request params, it becomes available as `server_context[:_meta]`:
|
|
180
222
|
|
|
181
223
|
```ruby
|
|
182
|
-
class
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
},
|
|
188
|
-
required: ["text"]
|
|
189
|
-
)
|
|
224
|
+
class MyTool < MCP::Tool
|
|
225
|
+
def self.call(message:, server_context:)
|
|
226
|
+
# Access provider-specific metadata
|
|
227
|
+
session_id = server_context.dig(:_meta, :session_id)
|
|
228
|
+
request_id = server_context.dig(:_meta, :request_id)
|
|
190
229
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
messages: [
|
|
194
|
-
{ role: "user", content: { type: "text", text: "Please summarize: #{text}" } }
|
|
195
|
-
],
|
|
196
|
-
max_tokens: 500
|
|
197
|
-
)
|
|
230
|
+
# Access server's original context
|
|
231
|
+
user_id = server_context.dig(:user_id)
|
|
198
232
|
|
|
199
233
|
MCP::Tool::Response.new([{
|
|
200
234
|
type: "text",
|
|
201
|
-
text:
|
|
235
|
+
text: "Processing for user #{user_id} in session #{session_id}"
|
|
202
236
|
}])
|
|
203
237
|
end
|
|
204
238
|
end
|
|
239
|
+
```
|
|
205
240
|
|
|
206
|
-
|
|
207
|
-
|
|
241
|
+
**Client Request Example:**
|
|
242
|
+
|
|
243
|
+
```json
|
|
244
|
+
{
|
|
245
|
+
"jsonrpc": "2.0",
|
|
246
|
+
"id": 1,
|
|
247
|
+
"method": "tools/call",
|
|
248
|
+
"params": {
|
|
249
|
+
"name": "my_tool",
|
|
250
|
+
"arguments": { "message": "Hello" },
|
|
251
|
+
"_meta": {
|
|
252
|
+
"session_id": "abc123",
|
|
253
|
+
"request_id": "req_456"
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
208
257
|
```
|
|
209
258
|
|
|
210
|
-
|
|
259
|
+
#### Configuration Block Data
|
|
211
260
|
|
|
212
|
-
|
|
213
|
-
|
|
261
|
+
##### Exception Reporter
|
|
262
|
+
|
|
263
|
+
The exception reporter receives:
|
|
264
|
+
|
|
265
|
+
- `exception`: The Ruby exception object that was raised
|
|
266
|
+
- `server_context`: The context hash provided to the server
|
|
267
|
+
|
|
268
|
+
**Signature:**
|
|
214
269
|
|
|
215
270
|
```ruby
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
{ role: "user", content: { type: "text", text: "What's the weather in Paris?" } }
|
|
219
|
-
],
|
|
220
|
-
max_tokens: 1000,
|
|
221
|
-
tools: [
|
|
222
|
-
{
|
|
223
|
-
name: "get_weather",
|
|
224
|
-
description: "Get weather for a city",
|
|
225
|
-
inputSchema: {
|
|
226
|
-
type: "object",
|
|
227
|
-
properties: { city: { type: "string" } },
|
|
228
|
-
required: ["city"]
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
],
|
|
232
|
-
tool_choice: { mode: "auto" }
|
|
233
|
-
)
|
|
271
|
+
exception_reporter = ->(exception, server_context) { ... }
|
|
272
|
+
```
|
|
234
273
|
|
|
235
|
-
|
|
236
|
-
tool_results = result[:content].map do |tool_use|
|
|
237
|
-
weather_data = get_weather(tool_use[:input][:city])
|
|
274
|
+
##### Instrumentation Callback
|
|
238
275
|
|
|
239
|
-
|
|
240
|
-
type: "tool_result",
|
|
241
|
-
toolUseId: tool_use[:id],
|
|
242
|
-
content: [{ type: "text", text: weather_data.to_json }]
|
|
243
|
-
}
|
|
244
|
-
end
|
|
276
|
+
The instrumentation callback receives a hash with the following possible keys:
|
|
245
277
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
278
|
+
- `method`: (String) The protocol method called (e.g., "ping", "tools/list")
|
|
279
|
+
- `tool_name`: (String, optional) The name of the tool called
|
|
280
|
+
- `tool_arguments`: (Hash, optional) The arguments passed to the tool
|
|
281
|
+
- `prompt_name`: (String, optional) The name of the prompt called
|
|
282
|
+
- `resource_uri`: (String, optional) The URI of the resource called
|
|
283
|
+
- `error`: (String, optional) Error code if a lookup failed
|
|
284
|
+
- `duration`: (Float) Duration of the call in seconds
|
|
285
|
+
- `client`: (Hash, optional) Client information with `name` and `version` keys, from the initialize request
|
|
286
|
+
|
|
287
|
+
> [!NOTE]
|
|
288
|
+
> `tool_name`, `prompt_name` and `resource_uri` are only populated if a matching handler is registered.
|
|
289
|
+
> This is to avoid potential issues with metric cardinality.
|
|
290
|
+
|
|
291
|
+
**Type:**
|
|
292
|
+
|
|
293
|
+
```ruby
|
|
294
|
+
instrumentation_callback = ->(data) { ... }
|
|
295
|
+
# where data is a Hash with keys as described above
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
**Example:**
|
|
299
|
+
|
|
300
|
+
```ruby
|
|
301
|
+
MCP.configure do |config|
|
|
302
|
+
config.instrumentation_callback = ->(data) {
|
|
303
|
+
puts "Instrumentation: #{data.inspect}"
|
|
304
|
+
}
|
|
255
305
|
end
|
|
256
306
|
```
|
|
257
307
|
|
|
258
|
-
|
|
308
|
+
### Server Protocol Version
|
|
259
309
|
|
|
260
|
-
|
|
261
|
-
- Raises `RuntimeError` if client does not support `sampling` capability
|
|
262
|
-
- Raises `RuntimeError` if `tools` are used but client lacks `sampling.tools` capability
|
|
263
|
-
- Raises `StandardError` if client returns an error response
|
|
310
|
+
The server's protocol version can be overridden using the `protocol_version` keyword argument:
|
|
264
311
|
|
|
265
|
-
|
|
312
|
+
```ruby
|
|
313
|
+
configuration = MCP::Configuration.new(protocol_version: "2024-11-05")
|
|
314
|
+
MCP::Server.new(name: "test_server", configuration: configuration)
|
|
315
|
+
```
|
|
266
316
|
|
|
267
|
-
|
|
317
|
+
If no protocol version is specified, the latest stable version will be applied by default.
|
|
318
|
+
The latest stable version includes new features from the [draft version](https://modelcontextprotocol.io/specification/draft).
|
|
268
319
|
|
|
269
|
-
|
|
320
|
+
This will make all new server instances use the specified protocol version instead of the default version. The protocol version can be reset to the default by setting it to `nil`:
|
|
270
321
|
|
|
271
|
-
|
|
322
|
+
```ruby
|
|
323
|
+
MCP::Configuration.new(protocol_version: nil)
|
|
324
|
+
```
|
|
272
325
|
|
|
273
|
-
|
|
274
|
-
- `notify_prompts_list_changed` - Send a notification when the prompts list changes
|
|
275
|
-
- `notify_resources_list_changed` - Send a notification when the resources list changes
|
|
276
|
-
- `notify_log_message` - Send a structured logging notification message
|
|
326
|
+
If an invalid `protocol_version` value is set, an `ArgumentError` is raised.
|
|
277
327
|
|
|
278
|
-
|
|
328
|
+
Be sure to check the [MCP spec](https://modelcontextprotocol.io/specification/versioning) for the protocol version to understand the supported features for the version being set.
|
|
279
329
|
|
|
280
|
-
|
|
330
|
+
### Exception Reporting
|
|
281
331
|
|
|
282
|
-
|
|
283
|
-
No extra configuration is needed.
|
|
284
|
-
- **`notify_tools_list_changed`**, **`notify_prompts_list_changed`**, and **`notify_resources_list_changed`** are always broadcast to all connected clients,
|
|
285
|
-
as they represent server-wide state changes. These should be called on the `server` instance directly.
|
|
332
|
+
The exception reporter receives two arguments:
|
|
286
333
|
|
|
287
|
-
|
|
334
|
+
- `exception`: The Ruby exception object that was raised
|
|
335
|
+
- `server_context`: A hash containing contextual information about where the error occurred
|
|
288
336
|
|
|
289
|
-
|
|
337
|
+
The server_context hash includes:
|
|
290
338
|
|
|
291
|
-
- `
|
|
292
|
-
- `
|
|
293
|
-
- `notifications/resources/list_changed`
|
|
294
|
-
- `notifications/progress`
|
|
295
|
-
- `notifications/message`
|
|
339
|
+
- For tool calls: `{ tool_name: "name", arguments: { ... } }`
|
|
340
|
+
- For general request handling: `{ request: { ... } }`
|
|
296
341
|
|
|
297
|
-
|
|
342
|
+
When an exception occurs:
|
|
298
343
|
|
|
299
|
-
The
|
|
300
|
-
|
|
344
|
+
1. The exception is reported via the configured reporter
|
|
345
|
+
2. For tool calls, a generic error response is returned to the client: `{ error: "Internal error occurred", isError: true }`
|
|
346
|
+
3. For other requests, the exception is re-raised after reporting
|
|
301
347
|
|
|
302
|
-
|
|
348
|
+
If no exception reporter is configured, a default no-op reporter is used that silently ignores exceptions.
|
|
303
349
|
|
|
304
|
-
|
|
305
|
-
2. **Server Notification**: The server sends `notifications/progress` messages back to the client during tool execution
|
|
306
|
-
3. **Tool Integration**: Tools call `server_context.report_progress` to report incremental progress
|
|
350
|
+
### Tools
|
|
307
351
|
|
|
308
|
-
|
|
352
|
+
MCP spec includes [Tools](https://modelcontextprotocol.io/specification/latest/server/tools) which provide functionality to LLM apps.
|
|
309
353
|
|
|
310
|
-
|
|
311
|
-
|
|
354
|
+
This gem provides a `MCP::Tool` class that can be used to create tools in three ways:
|
|
355
|
+
|
|
356
|
+
1. As a class definition:
|
|
312
357
|
|
|
313
358
|
```ruby
|
|
314
|
-
class
|
|
315
|
-
|
|
359
|
+
class MyTool < MCP::Tool
|
|
360
|
+
title "My Tool"
|
|
361
|
+
description "This tool performs specific functionality..."
|
|
316
362
|
input_schema(
|
|
317
363
|
properties: {
|
|
318
|
-
|
|
364
|
+
message: { type: "string" },
|
|
319
365
|
},
|
|
320
|
-
required: ["
|
|
366
|
+
required: ["message"]
|
|
367
|
+
)
|
|
368
|
+
output_schema(
|
|
369
|
+
properties: {
|
|
370
|
+
result: { type: "string" },
|
|
371
|
+
success: { type: "boolean" },
|
|
372
|
+
timestamp: { type: "string", format: "date-time" }
|
|
373
|
+
},
|
|
374
|
+
required: ["result", "success", "timestamp"]
|
|
375
|
+
)
|
|
376
|
+
annotations(
|
|
377
|
+
read_only_hint: true,
|
|
378
|
+
destructive_hint: false,
|
|
379
|
+
idempotent_hint: true,
|
|
380
|
+
open_world_hint: false,
|
|
381
|
+
title: "My Tool"
|
|
321
382
|
)
|
|
322
383
|
|
|
323
|
-
def self.call(
|
|
324
|
-
|
|
325
|
-
# Do work here.
|
|
326
|
-
server_context.report_progress(i + 1, total: count, message: "Processing item #{i + 1}")
|
|
327
|
-
end
|
|
328
|
-
|
|
329
|
-
MCP::Tool::Response.new([{ type: "text", text: "Done" }])
|
|
384
|
+
def self.call(message:, server_context:)
|
|
385
|
+
MCP::Tool::Response.new([{ type: "text", text: "OK" }])
|
|
330
386
|
end
|
|
331
387
|
end
|
|
332
|
-
```
|
|
333
|
-
|
|
334
|
-
The `server_context.report_progress` method accepts:
|
|
335
|
-
|
|
336
|
-
- `progress` (required) — current progress value (numeric)
|
|
337
|
-
- `total:` (optional) — total expected value, so clients can display a percentage
|
|
338
|
-
- `message:` (optional) — human-readable status message
|
|
339
|
-
|
|
340
|
-
**Key Features:**
|
|
341
|
-
|
|
342
|
-
- Tools report progress via `server_context.report_progress`
|
|
343
|
-
- `report_progress` is a no-op when no `progressToken` was provided by the client
|
|
344
|
-
- Supports both numeric and string progress tokens
|
|
345
|
-
|
|
346
|
-
### Completions
|
|
347
388
|
|
|
348
|
-
|
|
349
|
-
|
|
389
|
+
tool = MyTool
|
|
390
|
+
```
|
|
350
391
|
|
|
351
|
-
|
|
392
|
+
2. By using the `MCP::Tool.define` method with a block:
|
|
352
393
|
|
|
353
394
|
```ruby
|
|
354
|
-
|
|
355
|
-
name: "
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
395
|
+
tool = MCP::Tool.define(
|
|
396
|
+
name: "my_tool",
|
|
397
|
+
title: "My Tool",
|
|
398
|
+
description: "This tool performs specific functionality...",
|
|
399
|
+
annotations: {
|
|
400
|
+
read_only_hint: true,
|
|
401
|
+
title: "My Tool"
|
|
402
|
+
}
|
|
403
|
+
) do |args, server_context:|
|
|
404
|
+
MCP::Tool::Response.new([{ type: "text", text: "OK" }])
|
|
405
|
+
end
|
|
406
|
+
```
|
|
360
407
|
|
|
361
|
-
|
|
362
|
-
ref = params[:ref]
|
|
363
|
-
argument = params[:argument]
|
|
364
|
-
value = argument[:value]
|
|
408
|
+
3. By using the `MCP::Server#define_tool` method with a block:
|
|
365
409
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
end
|
|
410
|
+
```ruby
|
|
411
|
+
server = MCP::Server.new
|
|
412
|
+
server.define_tool(
|
|
413
|
+
name: "my_tool",
|
|
414
|
+
description: "This tool performs specific functionality...",
|
|
415
|
+
annotations: {
|
|
416
|
+
title: "My Tool",
|
|
417
|
+
read_only_hint: true
|
|
418
|
+
}
|
|
419
|
+
) do |args, server_context:|
|
|
420
|
+
Tool::Response.new([{ type: "text", text: "OK" }])
|
|
378
421
|
end
|
|
379
422
|
```
|
|
380
423
|
|
|
381
|
-
The
|
|
424
|
+
The server_context parameter is the server_context passed into the server and can be used to pass per request information,
|
|
425
|
+
e.g. around authentication state.
|
|
382
426
|
|
|
383
|
-
|
|
384
|
-
- `argument` - The argument being completed (`{ name: "...", value: "..." }`)
|
|
385
|
-
- `context` (optional) - Previously resolved arguments (`{ arguments: { ... } }`)
|
|
427
|
+
### Tool Annotations
|
|
386
428
|
|
|
387
|
-
|
|
388
|
-
The SDK automatically enforces the 100-item limit per the MCP specification.
|
|
429
|
+
Tools can include annotations that provide additional metadata about their behavior. The following annotations are supported:
|
|
389
430
|
|
|
390
|
-
|
|
391
|
-
|
|
431
|
+
- `destructive_hint`: Indicates if the tool performs destructive operations. Defaults to true
|
|
432
|
+
- `idempotent_hint`: Indicates if the tool's operations are idempotent. Defaults to false
|
|
433
|
+
- `open_world_hint`: Indicates if the tool operates in an open world context. Defaults to true
|
|
434
|
+
- `read_only_hint`: Indicates if the tool only reads data (doesn't modify state). Defaults to false
|
|
435
|
+
- `title`: A human-readable title for the tool
|
|
392
436
|
|
|
393
|
-
|
|
437
|
+
Annotations can be set either through the class definition using the `annotations` class method or when defining a tool using the `define` method.
|
|
394
438
|
|
|
395
|
-
|
|
439
|
+
> [!NOTE]
|
|
440
|
+
> This **Tool Annotations** feature is supported starting from `protocol_version: '2025-03-26'`.
|
|
396
441
|
|
|
397
|
-
|
|
442
|
+
### Tool Output Schemas
|
|
398
443
|
|
|
399
|
-
|
|
444
|
+
Tools can optionally define an `output_schema` to specify the expected structure of their results. This works similarly to how `input_schema` is defined and can be used in three ways:
|
|
400
445
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
- `debug` - Detailed debugging information
|
|
404
|
-
- `info` - General informational messages
|
|
405
|
-
- `notice` - Normal but significant events
|
|
406
|
-
- `warning` - Warning conditions
|
|
407
|
-
- `error` - Error conditions
|
|
408
|
-
- `critical` - Critical conditions
|
|
409
|
-
- `alert` - Action must be taken immediately
|
|
410
|
-
- `emergency` - System is unusable
|
|
411
|
-
|
|
412
|
-
#### How Logging Works
|
|
413
|
-
|
|
414
|
-
1. **Client Configuration**: The client sends a `logging/setLevel` request to configure the minimum log level
|
|
415
|
-
2. **Server Filtering**: The server only sends log messages at the configured level or higher severity
|
|
416
|
-
3. **Notification Delivery**: Log messages are sent as `notifications/message` to the client
|
|
417
|
-
|
|
418
|
-
For example, if the client sets the level to `"error"` (severity 4), the server will send messages with levels: `error`, `critical`, `alert`, and `emergency`.
|
|
419
|
-
|
|
420
|
-
For more details, see the [MCP Logging specification](https://modelcontextprotocol.io/specification/latest/server/utilities/logging).
|
|
421
|
-
|
|
422
|
-
**Usage Example:**
|
|
446
|
+
1. **Class definition with output_schema:**
|
|
423
447
|
|
|
424
448
|
```ruby
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
# The client first configures the logging level (on the client side):
|
|
430
|
-
transport.send_request(
|
|
431
|
-
request: {
|
|
432
|
-
jsonrpc: "2.0",
|
|
433
|
-
method: "logging/setLevel",
|
|
434
|
-
params: { level: "info" },
|
|
435
|
-
id: session_id # Unique request ID within the session
|
|
436
|
-
}
|
|
437
|
-
)
|
|
438
|
-
|
|
439
|
-
# Send log messages at different severity levels
|
|
440
|
-
server.notify_log_message(
|
|
441
|
-
data: { message: "Application started successfully" },
|
|
442
|
-
level: "info"
|
|
443
|
-
)
|
|
444
|
-
|
|
445
|
-
server.notify_log_message(
|
|
446
|
-
data: { message: "Configuration file not found, using defaults" },
|
|
447
|
-
level: "warning"
|
|
448
|
-
)
|
|
449
|
+
class WeatherTool < MCP::Tool
|
|
450
|
+
tool_name "get_weather"
|
|
451
|
+
description "Get current weather for a location"
|
|
449
452
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
)
|
|
458
|
-
```
|
|
453
|
+
input_schema(
|
|
454
|
+
properties: {
|
|
455
|
+
location: { type: "string" },
|
|
456
|
+
units: { type: "string", enum: ["celsius", "fahrenheit"] }
|
|
457
|
+
},
|
|
458
|
+
required: ["location"]
|
|
459
|
+
)
|
|
459
460
|
|
|
460
|
-
|
|
461
|
+
output_schema(
|
|
462
|
+
properties: {
|
|
463
|
+
temperature: { type: "number" },
|
|
464
|
+
condition: { type: "string" },
|
|
465
|
+
humidity: { type: "integer" }
|
|
466
|
+
},
|
|
467
|
+
required: ["temperature", "condition", "humidity"]
|
|
468
|
+
)
|
|
461
469
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
470
|
+
def self.call(location:, units: "celsius", server_context:)
|
|
471
|
+
# Call weather API and structure the response
|
|
472
|
+
api_response = WeatherAPI.fetch(location, units)
|
|
473
|
+
weather_data = {
|
|
474
|
+
temperature: api_response.temp,
|
|
475
|
+
condition: api_response.description,
|
|
476
|
+
humidity: api_response.humidity_percent
|
|
477
|
+
}
|
|
467
478
|
|
|
468
|
-
|
|
479
|
+
output_schema.validate_result(weather_data)
|
|
469
480
|
|
|
470
|
-
|
|
471
|
-
|
|
481
|
+
MCP::Tool::Response.new([{
|
|
482
|
+
type: "text",
|
|
483
|
+
text: weather_data.to_json
|
|
484
|
+
}])
|
|
485
|
+
end
|
|
486
|
+
end
|
|
487
|
+
```
|
|
472
488
|
|
|
473
|
-
|
|
489
|
+
2. **Using Tool.define with output_schema:**
|
|
474
490
|
|
|
475
491
|
```ruby
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
492
|
+
tool = MCP::Tool.define(
|
|
493
|
+
name: "calculate_stats",
|
|
494
|
+
description: "Calculate statistics for a dataset",
|
|
495
|
+
input_schema: {
|
|
496
|
+
properties: {
|
|
497
|
+
numbers: { type: "array", items: { type: "number" } }
|
|
498
|
+
},
|
|
499
|
+
required: ["numbers"]
|
|
500
|
+
},
|
|
501
|
+
output_schema: {
|
|
502
|
+
properties: {
|
|
503
|
+
mean: { type: "number" },
|
|
504
|
+
median: { type: "number" },
|
|
505
|
+
count: { type: "integer" }
|
|
506
|
+
},
|
|
507
|
+
required: ["mean", "median", "count"]
|
|
508
|
+
}
|
|
509
|
+
) do |args, server_context:|
|
|
510
|
+
# Calculate statistics and validate against schema
|
|
511
|
+
MCP::Tool::Response.new([{ type: "text", text: "Statistics calculated" }])
|
|
512
|
+
end
|
|
486
513
|
```
|
|
487
514
|
|
|
488
|
-
|
|
489
|
-
This mode allows for easy multi-node deployment.
|
|
490
|
-
Set `stateless: true` in `MCP::Server::Transports::StreamableHTTPTransport.new` (`stateless` defaults to `false`):
|
|
515
|
+
3. **Using OutputSchema objects:**
|
|
491
516
|
|
|
492
517
|
```ruby
|
|
493
|
-
|
|
494
|
-
|
|
518
|
+
class DataTool < MCP::Tool
|
|
519
|
+
output_schema MCP::Tool::OutputSchema.new(
|
|
520
|
+
properties: {
|
|
521
|
+
success: { type: "boolean" },
|
|
522
|
+
data: { type: "object" }
|
|
523
|
+
},
|
|
524
|
+
required: ["success"]
|
|
525
|
+
)
|
|
526
|
+
end
|
|
495
527
|
```
|
|
496
528
|
|
|
497
|
-
|
|
498
|
-
When configured, sessions that receive no HTTP requests for this duration are automatically expired and cleaned up:
|
|
529
|
+
Output schema may also describe an array of objects:
|
|
499
530
|
|
|
500
531
|
```ruby
|
|
501
|
-
|
|
502
|
-
|
|
532
|
+
class WeatherTool < MCP::Tool
|
|
533
|
+
output_schema(
|
|
534
|
+
type: "array",
|
|
535
|
+
items: {
|
|
536
|
+
properties: {
|
|
537
|
+
temperature: { type: "number" },
|
|
538
|
+
condition: { type: "string" },
|
|
539
|
+
humidity: { type: "integer" }
|
|
540
|
+
},
|
|
541
|
+
required: ["temperature", "condition", "humidity"]
|
|
542
|
+
}
|
|
543
|
+
)
|
|
544
|
+
end
|
|
503
545
|
```
|
|
504
546
|
|
|
505
|
-
|
|
547
|
+
Please note: in this case, you must provide `type: "array"`. The default type
|
|
548
|
+
for output schemas is `object`.
|
|
506
549
|
|
|
507
|
-
-
|
|
508
|
-
- Elicitation
|
|
550
|
+
MCP spec for the [Output Schema](https://modelcontextprotocol.io/specification/latest/server/tools#output-schema) specifies that:
|
|
509
551
|
|
|
510
|
-
|
|
552
|
+
- **Server Validation**: Servers MUST provide structured results that conform to the output schema
|
|
553
|
+
- **Client Validation**: Clients SHOULD validate structured results against the output schema
|
|
554
|
+
- **Better Integration**: Enables strict schema validation, type information, and improved developer experience
|
|
555
|
+
- **Backward Compatibility**: Tools returning structured content SHOULD also include serialized JSON in a TextContent block
|
|
511
556
|
|
|
512
|
-
|
|
513
|
-
> `MCP::Server::Transports::StreamableHTTPTransport` stores session and SSE stream state in memory,
|
|
514
|
-
> so it must run in a single process. Use a single-process server (e.g., Puma with `workers 0`).
|
|
515
|
-
> Multi-process configurations (Unicorn, or Puma with `workers > 0`) fork separate processes that
|
|
516
|
-
> do not share memory, which breaks session management and SSE connections.
|
|
517
|
-
> Stateless mode (`stateless: true`) does not use sessions and works with any server configuration.
|
|
557
|
+
The output schema follows standard JSON Schema format and helps ensure consistent data exchange between MCP servers and clients.
|
|
518
558
|
|
|
519
|
-
|
|
559
|
+
### Tool Responses with Structured Content
|
|
520
560
|
|
|
521
|
-
|
|
522
|
-
[Streamable HTTP](https://modelcontextprotocol.io/specification/latest/basic/transports#streamable-http) transport
|
|
523
|
-
requests.
|
|
561
|
+
Tools can return structured data alongside text content using the `structured_content` parameter.
|
|
524
562
|
|
|
525
|
-
|
|
526
|
-
status codes (e.g., 202 Accepted for notifications).
|
|
563
|
+
The structured content will be included in the JSON-RPC response as the `structuredContent` field.
|
|
527
564
|
|
|
528
565
|
```ruby
|
|
529
|
-
class
|
|
530
|
-
|
|
531
|
-
server = MCP::Server.new(
|
|
532
|
-
name: "my_server",
|
|
533
|
-
title: "Example Server Display Name",
|
|
534
|
-
version: "1.0.0",
|
|
535
|
-
instructions: "Use the tools of this server as a last resort",
|
|
536
|
-
tools: [SomeTool, AnotherTool],
|
|
537
|
-
prompts: [MyPrompt],
|
|
538
|
-
server_context: { user_id: current_user.id },
|
|
539
|
-
)
|
|
540
|
-
transport = MCP::Server::Transports::StreamableHTTPTransport.new(server)
|
|
541
|
-
server.transport = transport
|
|
542
|
-
status, headers, body = transport.handle_request(request)
|
|
566
|
+
class WeatherTool < MCP::Tool
|
|
567
|
+
description "Get current weather and return structured data"
|
|
543
568
|
|
|
544
|
-
|
|
569
|
+
def self.call(location:, units: "celsius", server_context:)
|
|
570
|
+
# Call weather API and structure the response
|
|
571
|
+
api_response = WeatherAPI.fetch(location, units)
|
|
572
|
+
weather_data = {
|
|
573
|
+
temperature: api_response.temp,
|
|
574
|
+
condition: api_response.description,
|
|
575
|
+
humidity: api_response.humidity_percent
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
output_schema.validate_result(weather_data)
|
|
579
|
+
|
|
580
|
+
MCP::Tool::Response.new(
|
|
581
|
+
[{
|
|
582
|
+
type: "text",
|
|
583
|
+
text: weather_data.to_json
|
|
584
|
+
}],
|
|
585
|
+
structured_content: weather_data
|
|
586
|
+
)
|
|
545
587
|
end
|
|
546
588
|
end
|
|
547
589
|
```
|
|
548
590
|
|
|
549
|
-
|
|
591
|
+
### Tool Responses with Errors
|
|
550
592
|
|
|
551
|
-
|
|
593
|
+
Tools can return error information alongside text content using the `error` parameter.
|
|
594
|
+
|
|
595
|
+
The error will be included in the JSON-RPC response as the `isError` field.
|
|
552
596
|
|
|
553
597
|
```ruby
|
|
554
|
-
|
|
598
|
+
class WeatherTool < MCP::Tool
|
|
599
|
+
description "Get current weather and return structured data"
|
|
555
600
|
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
input_schema(
|
|
560
|
-
properties: {
|
|
561
|
-
message: { type: "string" },
|
|
562
|
-
},
|
|
563
|
-
required: ["message"]
|
|
564
|
-
)
|
|
601
|
+
def self.call(server_context:)
|
|
602
|
+
# Do something here
|
|
603
|
+
content = {}
|
|
565
604
|
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
MCP::Tool::Response.new([{
|
|
605
|
+
MCP::Tool::Response.new(
|
|
606
|
+
[{
|
|
569
607
|
type: "text",
|
|
570
|
-
text:
|
|
571
|
-
}]
|
|
572
|
-
|
|
608
|
+
text: content.to_json
|
|
609
|
+
}],
|
|
610
|
+
structured_content: content,
|
|
611
|
+
error: true
|
|
612
|
+
)
|
|
573
613
|
end
|
|
574
614
|
end
|
|
615
|
+
```
|
|
575
616
|
|
|
576
|
-
|
|
577
|
-
server = MCP::Server.new(
|
|
578
|
-
name: "example_server",
|
|
579
|
-
tools: [ExampleTool],
|
|
580
|
-
)
|
|
581
|
-
|
|
582
|
-
# Create and start the transport
|
|
583
|
-
transport = MCP::Server::Transports::StdioTransport.new(server)
|
|
584
|
-
transport.open
|
|
585
|
-
```
|
|
586
|
-
|
|
587
|
-
You can run this script and then type in requests to the server at the command line.
|
|
617
|
+
### Prompts
|
|
588
618
|
|
|
589
|
-
|
|
590
|
-
$ ruby examples/stdio_server.rb
|
|
591
|
-
{"jsonrpc":"2.0","id":"1","method":"ping"}
|
|
592
|
-
{"jsonrpc":"2.0","id":"2","method":"tools/list"}
|
|
593
|
-
{"jsonrpc":"2.0","id":"3","method":"tools/call","params":{"name":"example_tool","arguments":{"message":"Hello"}}}
|
|
594
|
-
```
|
|
619
|
+
MCP spec includes [Prompts](https://modelcontextprotocol.io/specification/latest/server/prompts), which enable servers to define reusable prompt templates and workflows that clients can easily surface to users and LLMs.
|
|
595
620
|
|
|
596
|
-
|
|
621
|
+
The `MCP::Prompt` class provides three ways to create prompts:
|
|
597
622
|
|
|
598
|
-
|
|
623
|
+
1. As a class definition with metadata:
|
|
599
624
|
|
|
600
625
|
```ruby
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
626
|
+
class MyPrompt < MCP::Prompt
|
|
627
|
+
prompt_name "my_prompt" # Optional - defaults to underscored class name
|
|
628
|
+
title "My Prompt"
|
|
629
|
+
description "This prompt performs specific functionality..."
|
|
630
|
+
arguments [
|
|
631
|
+
MCP::Prompt::Argument.new(
|
|
632
|
+
name: "message",
|
|
633
|
+
title: "Message Title",
|
|
634
|
+
description: "Input message",
|
|
635
|
+
required: true
|
|
636
|
+
)
|
|
637
|
+
]
|
|
638
|
+
meta({ version: "1.0", category: "example" })
|
|
639
|
+
|
|
640
|
+
class << self
|
|
641
|
+
def template(args, server_context:)
|
|
642
|
+
MCP::Prompt::Result.new(
|
|
643
|
+
description: "Response description",
|
|
644
|
+
messages: [
|
|
645
|
+
MCP::Prompt::Message.new(
|
|
646
|
+
role: "user",
|
|
647
|
+
content: MCP::Content::Text.new("User message")
|
|
648
|
+
),
|
|
649
|
+
MCP::Prompt::Message.new(
|
|
650
|
+
role: "assistant",
|
|
651
|
+
content: MCP::Content::Text.new(args["message"])
|
|
652
|
+
)
|
|
653
|
+
]
|
|
654
|
+
)
|
|
607
655
|
end
|
|
608
|
-
|
|
656
|
+
end
|
|
657
|
+
end
|
|
609
658
|
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
659
|
+
prompt = MyPrompt
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
2. Using the `MCP::Prompt.define` method:
|
|
663
|
+
|
|
664
|
+
```ruby
|
|
665
|
+
prompt = MCP::Prompt.define(
|
|
666
|
+
name: "my_prompt",
|
|
667
|
+
title: "My Prompt",
|
|
668
|
+
description: "This prompt performs specific functionality...",
|
|
669
|
+
arguments: [
|
|
670
|
+
MCP::Prompt::Argument.new(
|
|
671
|
+
name: "message",
|
|
672
|
+
title: "Message Title",
|
|
673
|
+
description: "Input message",
|
|
674
|
+
required: true
|
|
675
|
+
)
|
|
676
|
+
],
|
|
677
|
+
meta: { version: "1.0", category: "example" }
|
|
678
|
+
) do |args, server_context:|
|
|
679
|
+
MCP::Prompt::Result.new(
|
|
680
|
+
description: "Response description",
|
|
681
|
+
messages: [
|
|
682
|
+
MCP::Prompt::Message.new(
|
|
683
|
+
role: "user",
|
|
684
|
+
content: MCP::Content::Text.new("User message")
|
|
685
|
+
),
|
|
686
|
+
MCP::Prompt::Message.new(
|
|
687
|
+
role: "assistant",
|
|
688
|
+
content: MCP::Content::Text.new(args["message"])
|
|
689
|
+
)
|
|
690
|
+
]
|
|
691
|
+
)
|
|
613
692
|
end
|
|
614
693
|
```
|
|
615
694
|
|
|
616
|
-
|
|
617
|
-
This is useful for systems where an application hosts more than one MCP server but
|
|
618
|
-
they might require different instrumentation callbacks.
|
|
695
|
+
3. Using the `MCP::Server#define_prompt` method:
|
|
619
696
|
|
|
620
697
|
```ruby
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
698
|
+
server = MCP::Server.new
|
|
699
|
+
server.define_prompt(
|
|
700
|
+
name: "my_prompt",
|
|
701
|
+
description: "This prompt performs specific functionality...",
|
|
702
|
+
arguments: [
|
|
703
|
+
Prompt::Argument.new(
|
|
704
|
+
name: "message",
|
|
705
|
+
title: "Message Title",
|
|
706
|
+
description: "Input message",
|
|
707
|
+
required: true
|
|
708
|
+
)
|
|
709
|
+
],
|
|
710
|
+
meta: { version: "1.0", category: "example" }
|
|
711
|
+
) do |args, server_context:|
|
|
712
|
+
Prompt::Result.new(
|
|
713
|
+
description: "Response description",
|
|
714
|
+
messages: [
|
|
715
|
+
Prompt::Message.new(
|
|
716
|
+
role: "user",
|
|
717
|
+
content: Content::Text.new("User message")
|
|
718
|
+
),
|
|
719
|
+
Prompt::Message.new(
|
|
720
|
+
role: "assistant",
|
|
721
|
+
content: Content::Text.new(args["message"])
|
|
722
|
+
)
|
|
723
|
+
]
|
|
724
|
+
)
|
|
725
|
+
end
|
|
726
|
+
```
|
|
629
727
|
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
728
|
+
The server_context parameter is the server_context passed into the server and can be used to pass per request information,
|
|
729
|
+
e.g. around authentication state or user preferences.
|
|
730
|
+
|
|
731
|
+
### Key Components
|
|
633
732
|
|
|
733
|
+
- `MCP::Prompt::Argument` - Defines input parameters for the prompt template with name, title, description, and required flag
|
|
734
|
+
- `MCP::Prompt::Message` - Represents a message in the conversation with a role and content
|
|
735
|
+
- `MCP::Prompt::Result` - The output of a prompt template containing description and messages
|
|
736
|
+
- `MCP::Content::Text` - Text content for messages
|
|
737
|
+
|
|
738
|
+
### Usage
|
|
739
|
+
|
|
740
|
+
Register prompts with the MCP server:
|
|
741
|
+
|
|
742
|
+
```ruby
|
|
634
743
|
server = MCP::Server.new(
|
|
635
|
-
|
|
636
|
-
|
|
744
|
+
name: "my_server",
|
|
745
|
+
prompts: [MyPrompt],
|
|
746
|
+
server_context: { user_id: current_user.id },
|
|
637
747
|
)
|
|
638
748
|
```
|
|
639
749
|
|
|
640
|
-
|
|
750
|
+
The server will handle prompt listing and execution through the MCP protocol methods:
|
|
641
751
|
|
|
642
|
-
|
|
752
|
+
- `prompts/list` - Lists all registered prompts and their schemas
|
|
753
|
+
- `prompts/get` - Retrieves and executes a specific prompt with arguments
|
|
643
754
|
|
|
644
|
-
|
|
755
|
+
### Resources
|
|
645
756
|
|
|
646
|
-
|
|
757
|
+
MCP spec includes [Resources](https://modelcontextprotocol.io/specification/latest/server/resources).
|
|
758
|
+
|
|
759
|
+
### Reading Resources
|
|
760
|
+
|
|
761
|
+
The `MCP::Resource` class provides a way to register resources with the server.
|
|
647
762
|
|
|
648
763
|
```ruby
|
|
649
|
-
|
|
764
|
+
resource = MCP::Resource.new(
|
|
765
|
+
uri: "https://example.com/my_resource",
|
|
766
|
+
name: "my-resource",
|
|
767
|
+
title: "My Resource",
|
|
768
|
+
description: "Lorem ipsum dolor sit amet",
|
|
769
|
+
mime_type: "text/html",
|
|
770
|
+
)
|
|
771
|
+
|
|
772
|
+
server = MCP::Server.new(
|
|
773
|
+
name: "my_server",
|
|
774
|
+
resources: [resource],
|
|
775
|
+
)
|
|
650
776
|
```
|
|
651
777
|
|
|
652
|
-
|
|
778
|
+
The server must register a handler for the `resources/read` method to retrieve a resource dynamically.
|
|
779
|
+
|
|
780
|
+
```ruby
|
|
781
|
+
server.resources_read_handler do |params|
|
|
782
|
+
[{
|
|
783
|
+
uri: params[:uri],
|
|
784
|
+
mimeType: "text/plain",
|
|
785
|
+
text: "Hello from example resource! URI: #{params[:uri]}"
|
|
786
|
+
}]
|
|
787
|
+
end
|
|
788
|
+
```
|
|
789
|
+
|
|
790
|
+
otherwise `resources/read` requests will be a no-op.
|
|
791
|
+
|
|
792
|
+
### Resource Templates
|
|
793
|
+
|
|
794
|
+
The `MCP::ResourceTemplate` class provides a way to register resource templates with the server.
|
|
653
795
|
|
|
654
796
|
```ruby
|
|
797
|
+
resource_template = MCP::ResourceTemplate.new(
|
|
798
|
+
uri_template: "https://example.com/my_resource_template",
|
|
799
|
+
name: "my-resource-template",
|
|
800
|
+
title: "My Resource Template",
|
|
801
|
+
description: "Lorem ipsum dolor sit amet",
|
|
802
|
+
mime_type: "text/html",
|
|
803
|
+
)
|
|
804
|
+
|
|
655
805
|
server = MCP::Server.new(
|
|
656
806
|
name: "my_server",
|
|
657
|
-
|
|
807
|
+
resource_templates: [resource_template],
|
|
658
808
|
)
|
|
659
809
|
```
|
|
660
810
|
|
|
661
|
-
|
|
811
|
+
### Sampling
|
|
662
812
|
|
|
663
|
-
|
|
813
|
+
The Model Context Protocol allows servers to request LLM completions from clients through the `sampling/createMessage` method.
|
|
814
|
+
This enables servers to leverage the client's LLM capabilities without needing direct access to AI models.
|
|
664
815
|
|
|
665
|
-
|
|
816
|
+
**Key Concepts:**
|
|
666
817
|
|
|
667
|
-
**
|
|
818
|
+
- **Server-to-Client Request**: Unlike typical MCP methods (client to server), sampling is initiated by the server
|
|
819
|
+
- **Client Capability**: Clients must declare `sampling` capability during initialization
|
|
820
|
+
- **Tool Support**: When using tools in sampling requests, clients must declare `sampling.tools` capability
|
|
821
|
+
- **Human-in-the-Loop**: Clients can implement user approval before forwarding requests to LLMs
|
|
668
822
|
|
|
669
|
-
|
|
823
|
+
**Using Sampling in Tools:**
|
|
824
|
+
|
|
825
|
+
Tools that accept a `server_context:` parameter can call `create_sampling_message` on it.
|
|
826
|
+
The request is automatically routed to the correct client session.
|
|
827
|
+
Set `server.server_context = server` so that `server_context.create_sampling_message` delegates to the server:
|
|
670
828
|
|
|
671
829
|
```ruby
|
|
672
|
-
class
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
830
|
+
class SummarizeTool < MCP::Tool
|
|
831
|
+
description "Summarize text using LLM"
|
|
832
|
+
input_schema(
|
|
833
|
+
properties: {
|
|
834
|
+
text: { type: "string" }
|
|
835
|
+
},
|
|
836
|
+
required: ["text"]
|
|
837
|
+
)
|
|
677
838
|
|
|
678
|
-
|
|
679
|
-
|
|
839
|
+
def self.call(text:, server_context:)
|
|
840
|
+
result = server_context.create_sampling_message(
|
|
841
|
+
messages: [
|
|
842
|
+
{ role: "user", content: { type: "text", text: "Please summarize: #{text}" } }
|
|
843
|
+
],
|
|
844
|
+
max_tokens: 500
|
|
845
|
+
)
|
|
680
846
|
|
|
681
847
|
MCP::Tool::Response.new([{
|
|
682
848
|
type: "text",
|
|
683
|
-
text:
|
|
849
|
+
text: result[:content][:text]
|
|
684
850
|
}])
|
|
685
851
|
end
|
|
686
852
|
end
|
|
853
|
+
|
|
854
|
+
server = MCP::Server.new(name: "my_server", tools: [SummarizeTool])
|
|
855
|
+
server.server_context = server
|
|
687
856
|
```
|
|
688
857
|
|
|
689
|
-
**
|
|
858
|
+
**Parameters:**
|
|
690
859
|
|
|
691
|
-
|
|
692
|
-
{
|
|
693
|
-
"jsonrpc": "2.0",
|
|
694
|
-
"id": 1,
|
|
695
|
-
"method": "tools/call",
|
|
696
|
-
"params": {
|
|
697
|
-
"name": "my_tool",
|
|
698
|
-
"arguments": { "message": "Hello" },
|
|
699
|
-
"_meta": {
|
|
700
|
-
"session_id": "abc123",
|
|
701
|
-
"request_id": "req_456"
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
```
|
|
860
|
+
Required:
|
|
706
861
|
|
|
707
|
-
|
|
862
|
+
- `messages:` (Array) - Array of message objects with `role` and `content`
|
|
863
|
+
- `max_tokens:` (Integer) - Maximum tokens in the response
|
|
708
864
|
|
|
709
|
-
|
|
865
|
+
Optional:
|
|
710
866
|
|
|
711
|
-
|
|
867
|
+
- `system_prompt:` (String) - System prompt for the LLM
|
|
868
|
+
- `model_preferences:` (Hash) - Model selection preferences (e.g., `{ intelligencePriority: 0.8 }`)
|
|
869
|
+
- `include_context:` (String) - Context inclusion: `"none"`, `"thisServer"`, or `"allServers"` (soft-deprecated)
|
|
870
|
+
- `temperature:` (Float) - Sampling temperature
|
|
871
|
+
- `stop_sequences:` (Array) - Sequences that stop generation
|
|
872
|
+
- `metadata:` (Hash) - Additional metadata
|
|
873
|
+
- `tools:` (Array) - Tools available to the LLM (requires `sampling.tools` capability)
|
|
874
|
+
- `tool_choice:` (Hash) - Tool selection mode (e.g., `{ mode: "auto" }`)
|
|
712
875
|
|
|
713
|
-
|
|
714
|
-
- `server_context`: The context hash provided to the server
|
|
876
|
+
**Direct Usage:**
|
|
715
877
|
|
|
716
|
-
|
|
878
|
+
`Server#create_sampling_message` can also be called directly outside of tools:
|
|
717
879
|
|
|
718
880
|
```ruby
|
|
719
|
-
|
|
881
|
+
result = server.create_sampling_message(
|
|
882
|
+
messages: [
|
|
883
|
+
{ role: "user", content: { type: "text", text: "What is the capital of France?" } }
|
|
884
|
+
],
|
|
885
|
+
max_tokens: 100,
|
|
886
|
+
system_prompt: "You are a helpful assistant.",
|
|
887
|
+
temperature: 0.7
|
|
888
|
+
)
|
|
720
889
|
```
|
|
721
890
|
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
The instrumentation callback receives a hash with the following possible keys:
|
|
725
|
-
|
|
726
|
-
- `method`: (String) The protocol method called (e.g., "ping", "tools/list")
|
|
727
|
-
- `tool_name`: (String, optional) The name of the tool called
|
|
728
|
-
- `tool_arguments`: (Hash, optional) The arguments passed to the tool
|
|
729
|
-
- `prompt_name`: (String, optional) The name of the prompt called
|
|
730
|
-
- `resource_uri`: (String, optional) The URI of the resource called
|
|
731
|
-
- `error`: (String, optional) Error code if a lookup failed
|
|
732
|
-
- `duration`: (Float) Duration of the call in seconds
|
|
733
|
-
- `client`: (Hash, optional) Client information with `name` and `version` keys, from the initialize request
|
|
734
|
-
|
|
735
|
-
> [!NOTE]
|
|
736
|
-
> `tool_name`, `prompt_name` and `resource_uri` are only populated if a matching handler is registered.
|
|
737
|
-
> This is to avoid potential issues with metric cardinality.
|
|
738
|
-
|
|
739
|
-
**Type:**
|
|
891
|
+
Result contains the LLM response:
|
|
740
892
|
|
|
741
893
|
```ruby
|
|
742
|
-
|
|
743
|
-
|
|
894
|
+
{
|
|
895
|
+
role: "assistant",
|
|
896
|
+
content: { type: "text", text: "The capital of France is Paris." },
|
|
897
|
+
model: "claude-3-sonnet-20240307",
|
|
898
|
+
stopReason: "endTurn"
|
|
899
|
+
}
|
|
744
900
|
```
|
|
745
901
|
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
```ruby
|
|
749
|
-
MCP.configure do |config|
|
|
750
|
-
config.instrumentation_callback = ->(data) {
|
|
751
|
-
puts "Instrumentation: #{data.inspect}"
|
|
752
|
-
}
|
|
753
|
-
end
|
|
754
|
-
```
|
|
902
|
+
For multi-client transports (e.g., `StreamableHTTPTransport`), use `server_context.create_sampling_message` inside tools
|
|
903
|
+
to route the request to the correct client session.
|
|
755
904
|
|
|
756
|
-
|
|
905
|
+
**Tool Use in Sampling:**
|
|
757
906
|
|
|
758
|
-
|
|
907
|
+
When tools are provided in a sampling request, the LLM can call them during generation.
|
|
908
|
+
The server must handle tool calls and continue the conversation with tool results:
|
|
759
909
|
|
|
760
910
|
```ruby
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
911
|
+
result = server.create_sampling_message(
|
|
912
|
+
messages: [
|
|
913
|
+
{ role: "user", content: { type: "text", text: "What's the weather in Paris?" } }
|
|
914
|
+
],
|
|
915
|
+
max_tokens: 1000,
|
|
916
|
+
tools: [
|
|
917
|
+
{
|
|
918
|
+
name: "get_weather",
|
|
919
|
+
description: "Get weather for a city",
|
|
920
|
+
inputSchema: {
|
|
921
|
+
type: "object",
|
|
922
|
+
properties: { city: { type: "string" } },
|
|
923
|
+
required: ["city"]
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
],
|
|
927
|
+
tool_choice: { mode: "auto" }
|
|
928
|
+
)
|
|
764
929
|
|
|
765
|
-
|
|
766
|
-
|
|
930
|
+
if result[:stopReason] == "toolUse"
|
|
931
|
+
tool_results = result[:content].map do |tool_use|
|
|
932
|
+
weather_data = get_weather(tool_use[:input][:city])
|
|
767
933
|
|
|
768
|
-
|
|
934
|
+
{
|
|
935
|
+
type: "tool_result",
|
|
936
|
+
toolUseId: tool_use[:id],
|
|
937
|
+
content: [{ type: "text", text: weather_data.to_json }]
|
|
938
|
+
}
|
|
939
|
+
end
|
|
769
940
|
|
|
770
|
-
|
|
771
|
-
|
|
941
|
+
final_result = server.create_sampling_message(
|
|
942
|
+
messages: [
|
|
943
|
+
{ role: "user", content: { type: "text", text: "What's the weather in Paris?" } },
|
|
944
|
+
{ role: "assistant", content: result[:content] },
|
|
945
|
+
{ role: "user", content: tool_results }
|
|
946
|
+
],
|
|
947
|
+
max_tokens: 1000,
|
|
948
|
+
tools: [...]
|
|
949
|
+
)
|
|
950
|
+
end
|
|
772
951
|
```
|
|
773
952
|
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
Be sure to check the [MCP spec](https://modelcontextprotocol.io/specification/versioning) for the protocol version to understand the supported features for the version being set.
|
|
953
|
+
**Error Handling:**
|
|
777
954
|
|
|
778
|
-
|
|
955
|
+
- Raises `RuntimeError` if transport is not set
|
|
956
|
+
- Raises `RuntimeError` if client does not support `sampling` capability
|
|
957
|
+
- Raises `RuntimeError` if `tools` are used but client lacks `sampling.tools` capability
|
|
958
|
+
- Raises `StandardError` if client returns an error response
|
|
779
959
|
|
|
780
|
-
|
|
960
|
+
### Notifications
|
|
781
961
|
|
|
782
|
-
|
|
783
|
-
- `server_context`: A hash containing contextual information about where the error occurred
|
|
962
|
+
The server supports sending notifications to clients when lists of tools, prompts, or resources change. This enables real-time updates without polling.
|
|
784
963
|
|
|
785
|
-
|
|
964
|
+
#### Notification Methods
|
|
786
965
|
|
|
787
|
-
|
|
788
|
-
- For general request handling: `{ request: { ... } }`
|
|
966
|
+
The server provides the following notification methods:
|
|
789
967
|
|
|
790
|
-
|
|
968
|
+
- `notify_tools_list_changed` - Send a notification when the tools list changes
|
|
969
|
+
- `notify_prompts_list_changed` - Send a notification when the prompts list changes
|
|
970
|
+
- `notify_resources_list_changed` - Send a notification when the resources list changes
|
|
971
|
+
- `notify_log_message` - Send a structured logging notification message
|
|
791
972
|
|
|
792
|
-
|
|
793
|
-
2. For tool calls, a generic error response is returned to the client: `{ error: "Internal error occurred", isError: true }`
|
|
794
|
-
3. For other requests, the exception is re-raised after reporting
|
|
973
|
+
#### Session Scoping
|
|
795
974
|
|
|
796
|
-
|
|
975
|
+
When using Streamable HTTP transport with multiple clients, each client connection gets its own session. Notifications are scoped as follows:
|
|
797
976
|
|
|
798
|
-
|
|
977
|
+
- **`report_progress`** and **`notify_log_message`** called via `server_context` inside a tool handler are automatically sent only to the requesting client.
|
|
978
|
+
No extra configuration is needed.
|
|
979
|
+
- **`notify_tools_list_changed`**, **`notify_prompts_list_changed`**, and **`notify_resources_list_changed`** are always broadcast to all connected clients,
|
|
980
|
+
as they represent server-wide state changes. These should be called on the `server` instance directly.
|
|
799
981
|
|
|
800
|
-
|
|
982
|
+
#### Notification Format
|
|
801
983
|
|
|
802
|
-
|
|
984
|
+
Notifications follow the JSON-RPC 2.0 specification and use these method names:
|
|
803
985
|
|
|
804
|
-
|
|
986
|
+
- `notifications/tools/list_changed`
|
|
987
|
+
- `notifications/prompts/list_changed`
|
|
988
|
+
- `notifications/resources/list_changed`
|
|
989
|
+
- `notifications/progress`
|
|
990
|
+
- `notifications/message`
|
|
805
991
|
|
|
806
|
-
|
|
807
|
-
class MyTool < MCP::Tool
|
|
808
|
-
title "My Tool"
|
|
809
|
-
description "This tool performs specific functionality..."
|
|
810
|
-
input_schema(
|
|
811
|
-
properties: {
|
|
812
|
-
message: { type: "string" },
|
|
813
|
-
},
|
|
814
|
-
required: ["message"]
|
|
815
|
-
)
|
|
816
|
-
output_schema(
|
|
817
|
-
properties: {
|
|
818
|
-
result: { type: "string" },
|
|
819
|
-
success: { type: "boolean" },
|
|
820
|
-
timestamp: { type: "string", format: "date-time" }
|
|
821
|
-
},
|
|
822
|
-
required: ["result", "success", "timestamp"]
|
|
823
|
-
)
|
|
824
|
-
annotations(
|
|
825
|
-
read_only_hint: true,
|
|
826
|
-
destructive_hint: false,
|
|
827
|
-
idempotent_hint: true,
|
|
828
|
-
open_world_hint: false,
|
|
829
|
-
title: "My Tool"
|
|
830
|
-
)
|
|
992
|
+
### Progress
|
|
831
993
|
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
end
|
|
835
|
-
end
|
|
994
|
+
The MCP Ruby SDK supports progress tracking for long-running tool operations,
|
|
995
|
+
following the [MCP Progress specification](https://modelcontextprotocol.io/specification/latest/server/utilities/progress).
|
|
836
996
|
|
|
837
|
-
|
|
838
|
-
```
|
|
997
|
+
#### How Progress Works
|
|
839
998
|
|
|
840
|
-
|
|
999
|
+
1. **Client Request**: The client sends a `progressToken` in the `_meta` field when calling a tool
|
|
1000
|
+
2. **Server Notification**: The server sends `notifications/progress` messages back to the client during tool execution
|
|
1001
|
+
3. **Tool Integration**: Tools call `server_context.report_progress` to report incremental progress
|
|
841
1002
|
|
|
842
|
-
|
|
843
|
-
tool = MCP::Tool.define(
|
|
844
|
-
name: "my_tool",
|
|
845
|
-
title: "My Tool",
|
|
846
|
-
description: "This tool performs specific functionality...",
|
|
847
|
-
annotations: {
|
|
848
|
-
read_only_hint: true,
|
|
849
|
-
title: "My Tool"
|
|
850
|
-
}
|
|
851
|
-
) do |args, server_context:|
|
|
852
|
-
MCP::Tool::Response.new([{ type: "text", text: "OK" }])
|
|
853
|
-
end
|
|
854
|
-
```
|
|
1003
|
+
#### Server-Side: Tool with Progress
|
|
855
1004
|
|
|
856
|
-
|
|
1005
|
+
Tools that accept a `server_context:` parameter can call `report_progress` on it.
|
|
1006
|
+
The server automatically wraps the context in an `MCP::ServerContext` instance that provides this method:
|
|
857
1007
|
|
|
858
1008
|
```ruby
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
name: "my_tool",
|
|
862
|
-
description: "This tool performs specific functionality...",
|
|
863
|
-
annotations: {
|
|
864
|
-
title: "My Tool",
|
|
865
|
-
read_only_hint: true
|
|
866
|
-
}
|
|
867
|
-
) do |args, server_context:|
|
|
868
|
-
Tool::Response.new([{ type: "text", text: "OK" }])
|
|
869
|
-
end
|
|
870
|
-
```
|
|
871
|
-
|
|
872
|
-
The server_context parameter is the server_context passed into the server and can be used to pass per request information,
|
|
873
|
-
e.g. around authentication state.
|
|
874
|
-
|
|
875
|
-
### Tool Annotations
|
|
876
|
-
|
|
877
|
-
Tools can include annotations that provide additional metadata about their behavior. The following annotations are supported:
|
|
878
|
-
|
|
879
|
-
- `destructive_hint`: Indicates if the tool performs destructive operations. Defaults to true
|
|
880
|
-
- `idempotent_hint`: Indicates if the tool's operations are idempotent. Defaults to false
|
|
881
|
-
- `open_world_hint`: Indicates if the tool operates in an open world context. Defaults to true
|
|
882
|
-
- `read_only_hint`: Indicates if the tool only reads data (doesn't modify state). Defaults to false
|
|
883
|
-
- `title`: A human-readable title for the tool
|
|
884
|
-
|
|
885
|
-
Annotations can be set either through the class definition using the `annotations` class method or when defining a tool using the `define` method.
|
|
886
|
-
|
|
887
|
-
> [!NOTE]
|
|
888
|
-
> This **Tool Annotations** feature is supported starting from `protocol_version: '2025-03-26'`.
|
|
889
|
-
|
|
890
|
-
### Tool Output Schemas
|
|
891
|
-
|
|
892
|
-
Tools can optionally define an `output_schema` to specify the expected structure of their results. This works similarly to how `input_schema` is defined and can be used in three ways:
|
|
893
|
-
|
|
894
|
-
1. **Class definition with output_schema:**
|
|
895
|
-
|
|
896
|
-
```ruby
|
|
897
|
-
class WeatherTool < MCP::Tool
|
|
898
|
-
tool_name "get_weather"
|
|
899
|
-
description "Get current weather for a location"
|
|
900
|
-
|
|
1009
|
+
class LongRunningTool < MCP::Tool
|
|
1010
|
+
description "A tool that reports progress during execution"
|
|
901
1011
|
input_schema(
|
|
902
|
-
properties: {
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
required: ["location"]
|
|
907
|
-
)
|
|
908
|
-
|
|
909
|
-
output_schema(
|
|
910
|
-
properties: {
|
|
911
|
-
temperature: { type: "number" },
|
|
912
|
-
condition: { type: "string" },
|
|
913
|
-
humidity: { type: "integer" }
|
|
914
|
-
},
|
|
915
|
-
required: ["temperature", "condition", "humidity"]
|
|
916
|
-
)
|
|
917
|
-
|
|
918
|
-
def self.call(location:, units: "celsius", server_context:)
|
|
919
|
-
# Call weather API and structure the response
|
|
920
|
-
api_response = WeatherAPI.fetch(location, units)
|
|
921
|
-
weather_data = {
|
|
922
|
-
temperature: api_response.temp,
|
|
923
|
-
condition: api_response.description,
|
|
924
|
-
humidity: api_response.humidity_percent
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
output_schema.validate_result(weather_data)
|
|
928
|
-
|
|
929
|
-
MCP::Tool::Response.new([{
|
|
930
|
-
type: "text",
|
|
931
|
-
text: weather_data.to_json
|
|
932
|
-
}])
|
|
933
|
-
end
|
|
934
|
-
end
|
|
935
|
-
```
|
|
936
|
-
|
|
937
|
-
2. **Using Tool.define with output_schema:**
|
|
938
|
-
|
|
939
|
-
```ruby
|
|
940
|
-
tool = MCP::Tool.define(
|
|
941
|
-
name: "calculate_stats",
|
|
942
|
-
description: "Calculate statistics for a dataset",
|
|
943
|
-
input_schema: {
|
|
944
|
-
properties: {
|
|
945
|
-
numbers: { type: "array", items: { type: "number" } }
|
|
946
|
-
},
|
|
947
|
-
required: ["numbers"]
|
|
948
|
-
},
|
|
949
|
-
output_schema: {
|
|
950
|
-
properties: {
|
|
951
|
-
mean: { type: "number" },
|
|
952
|
-
median: { type: "number" },
|
|
953
|
-
count: { type: "integer" }
|
|
954
|
-
},
|
|
955
|
-
required: ["mean", "median", "count"]
|
|
956
|
-
}
|
|
957
|
-
) do |args, server_context:|
|
|
958
|
-
# Calculate statistics and validate against schema
|
|
959
|
-
MCP::Tool::Response.new([{ type: "text", text: "Statistics calculated" }])
|
|
960
|
-
end
|
|
961
|
-
```
|
|
962
|
-
|
|
963
|
-
3. **Using OutputSchema objects:**
|
|
964
|
-
|
|
965
|
-
```ruby
|
|
966
|
-
class DataTool < MCP::Tool
|
|
967
|
-
output_schema MCP::Tool::OutputSchema.new(
|
|
968
|
-
properties: {
|
|
969
|
-
success: { type: "boolean" },
|
|
970
|
-
data: { type: "object" }
|
|
971
|
-
},
|
|
972
|
-
required: ["success"]
|
|
973
|
-
)
|
|
974
|
-
end
|
|
975
|
-
```
|
|
976
|
-
|
|
977
|
-
Output schema may also describe an array of objects:
|
|
978
|
-
|
|
979
|
-
```ruby
|
|
980
|
-
class WeatherTool < MCP::Tool
|
|
981
|
-
output_schema(
|
|
982
|
-
type: "array",
|
|
983
|
-
items: {
|
|
984
|
-
properties: {
|
|
985
|
-
temperature: { type: "number" },
|
|
986
|
-
condition: { type: "string" },
|
|
987
|
-
humidity: { type: "integer" }
|
|
988
|
-
},
|
|
989
|
-
required: ["temperature", "condition", "humidity"]
|
|
990
|
-
}
|
|
991
|
-
)
|
|
992
|
-
end
|
|
993
|
-
```
|
|
994
|
-
|
|
995
|
-
Please note: in this case, you must provide `type: "array"`. The default type
|
|
996
|
-
for output schemas is `object`.
|
|
997
|
-
|
|
998
|
-
MCP spec for the [Output Schema](https://modelcontextprotocol.io/specification/latest/server/tools#output-schema) specifies that:
|
|
999
|
-
|
|
1000
|
-
- **Server Validation**: Servers MUST provide structured results that conform to the output schema
|
|
1001
|
-
- **Client Validation**: Clients SHOULD validate structured results against the output schema
|
|
1002
|
-
- **Better Integration**: Enables strict schema validation, type information, and improved developer experience
|
|
1003
|
-
- **Backward Compatibility**: Tools returning structured content SHOULD also include serialized JSON in a TextContent block
|
|
1004
|
-
|
|
1005
|
-
The output schema follows standard JSON Schema format and helps ensure consistent data exchange between MCP servers and clients.
|
|
1006
|
-
|
|
1007
|
-
### Tool Responses with Structured Content
|
|
1008
|
-
|
|
1009
|
-
Tools can return structured data alongside text content using the `structured_content` parameter.
|
|
1010
|
-
|
|
1011
|
-
The structured content will be included in the JSON-RPC response as the `structuredContent` field.
|
|
1012
|
-
|
|
1013
|
-
```ruby
|
|
1014
|
-
class WeatherTool < MCP::Tool
|
|
1015
|
-
description "Get current weather and return structured data"
|
|
1016
|
-
|
|
1017
|
-
def self.call(location:, units: "celsius", server_context:)
|
|
1018
|
-
# Call weather API and structure the response
|
|
1019
|
-
api_response = WeatherAPI.fetch(location, units)
|
|
1020
|
-
weather_data = {
|
|
1021
|
-
temperature: api_response.temp,
|
|
1022
|
-
condition: api_response.description,
|
|
1023
|
-
humidity: api_response.humidity_percent
|
|
1024
|
-
}
|
|
1025
|
-
|
|
1026
|
-
output_schema.validate_result(weather_data)
|
|
1027
|
-
|
|
1028
|
-
MCP::Tool::Response.new(
|
|
1029
|
-
[{
|
|
1030
|
-
type: "text",
|
|
1031
|
-
text: weather_data.to_json
|
|
1032
|
-
}],
|
|
1033
|
-
structured_content: weather_data
|
|
1034
|
-
)
|
|
1035
|
-
end
|
|
1036
|
-
end
|
|
1037
|
-
```
|
|
1038
|
-
|
|
1039
|
-
### Tool Responses with Errors
|
|
1040
|
-
|
|
1041
|
-
Tools can return error information alongside text content using the `error` parameter.
|
|
1042
|
-
|
|
1043
|
-
The error will be included in the JSON-RPC response as the `isError` field.
|
|
1044
|
-
|
|
1045
|
-
```ruby
|
|
1046
|
-
class WeatherTool < MCP::Tool
|
|
1047
|
-
description "Get current weather and return structured data"
|
|
1048
|
-
|
|
1049
|
-
def self.call(server_context:)
|
|
1050
|
-
# Do something here
|
|
1051
|
-
content = {}
|
|
1052
|
-
|
|
1053
|
-
MCP::Tool::Response.new(
|
|
1054
|
-
[{
|
|
1055
|
-
type: "text",
|
|
1056
|
-
text: content.to_json
|
|
1057
|
-
}],
|
|
1058
|
-
structured_content: content,
|
|
1059
|
-
error: true
|
|
1060
|
-
)
|
|
1061
|
-
end
|
|
1062
|
-
end
|
|
1063
|
-
```
|
|
1064
|
-
|
|
1065
|
-
### Prompts
|
|
1066
|
-
|
|
1067
|
-
MCP spec includes [Prompts](https://modelcontextprotocol.io/specification/latest/server/prompts), which enable servers to define reusable prompt templates and workflows that clients can easily surface to users and LLMs.
|
|
1068
|
-
|
|
1069
|
-
The `MCP::Prompt` class provides three ways to create prompts:
|
|
1070
|
-
|
|
1071
|
-
1. As a class definition with metadata:
|
|
1072
|
-
|
|
1073
|
-
```ruby
|
|
1074
|
-
class MyPrompt < MCP::Prompt
|
|
1075
|
-
prompt_name "my_prompt" # Optional - defaults to underscored class name
|
|
1076
|
-
title "My Prompt"
|
|
1077
|
-
description "This prompt performs specific functionality..."
|
|
1078
|
-
arguments [
|
|
1079
|
-
MCP::Prompt::Argument.new(
|
|
1080
|
-
name: "message",
|
|
1081
|
-
title: "Message Title",
|
|
1082
|
-
description: "Input message",
|
|
1083
|
-
required: true
|
|
1084
|
-
)
|
|
1085
|
-
]
|
|
1086
|
-
meta({ version: "1.0", category: "example" })
|
|
1087
|
-
|
|
1088
|
-
class << self
|
|
1089
|
-
def template(args, server_context:)
|
|
1090
|
-
MCP::Prompt::Result.new(
|
|
1091
|
-
description: "Response description",
|
|
1092
|
-
messages: [
|
|
1093
|
-
MCP::Prompt::Message.new(
|
|
1094
|
-
role: "user",
|
|
1095
|
-
content: MCP::Content::Text.new("User message")
|
|
1096
|
-
),
|
|
1097
|
-
MCP::Prompt::Message.new(
|
|
1098
|
-
role: "assistant",
|
|
1099
|
-
content: MCP::Content::Text.new(args["message"])
|
|
1100
|
-
)
|
|
1101
|
-
]
|
|
1102
|
-
)
|
|
1103
|
-
end
|
|
1104
|
-
end
|
|
1105
|
-
end
|
|
1106
|
-
|
|
1107
|
-
prompt = MyPrompt
|
|
1108
|
-
```
|
|
1109
|
-
|
|
1110
|
-
2. Using the `MCP::Prompt.define` method:
|
|
1111
|
-
|
|
1112
|
-
```ruby
|
|
1113
|
-
prompt = MCP::Prompt.define(
|
|
1114
|
-
name: "my_prompt",
|
|
1115
|
-
title: "My Prompt",
|
|
1116
|
-
description: "This prompt performs specific functionality...",
|
|
1117
|
-
arguments: [
|
|
1118
|
-
MCP::Prompt::Argument.new(
|
|
1119
|
-
name: "message",
|
|
1120
|
-
title: "Message Title",
|
|
1121
|
-
description: "Input message",
|
|
1122
|
-
required: true
|
|
1123
|
-
)
|
|
1124
|
-
],
|
|
1125
|
-
meta: { version: "1.0", category: "example" }
|
|
1126
|
-
) do |args, server_context:|
|
|
1127
|
-
MCP::Prompt::Result.new(
|
|
1128
|
-
description: "Response description",
|
|
1129
|
-
messages: [
|
|
1130
|
-
MCP::Prompt::Message.new(
|
|
1131
|
-
role: "user",
|
|
1132
|
-
content: MCP::Content::Text.new("User message")
|
|
1133
|
-
),
|
|
1134
|
-
MCP::Prompt::Message.new(
|
|
1135
|
-
role: "assistant",
|
|
1136
|
-
content: MCP::Content::Text.new(args["message"])
|
|
1137
|
-
)
|
|
1138
|
-
]
|
|
1139
|
-
)
|
|
1140
|
-
end
|
|
1141
|
-
```
|
|
1142
|
-
|
|
1143
|
-
3. Using the `MCP::Server#define_prompt` method:
|
|
1144
|
-
|
|
1145
|
-
```ruby
|
|
1146
|
-
server = MCP::Server.new
|
|
1147
|
-
server.define_prompt(
|
|
1148
|
-
name: "my_prompt",
|
|
1149
|
-
description: "This prompt performs specific functionality...",
|
|
1150
|
-
arguments: [
|
|
1151
|
-
Prompt::Argument.new(
|
|
1152
|
-
name: "message",
|
|
1153
|
-
title: "Message Title",
|
|
1154
|
-
description: "Input message",
|
|
1155
|
-
required: true
|
|
1156
|
-
)
|
|
1157
|
-
],
|
|
1158
|
-
meta: { version: "1.0", category: "example" }
|
|
1159
|
-
) do |args, server_context:|
|
|
1160
|
-
Prompt::Result.new(
|
|
1161
|
-
description: "Response description",
|
|
1162
|
-
messages: [
|
|
1163
|
-
Prompt::Message.new(
|
|
1164
|
-
role: "user",
|
|
1165
|
-
content: Content::Text.new("User message")
|
|
1166
|
-
),
|
|
1167
|
-
Prompt::Message.new(
|
|
1168
|
-
role: "assistant",
|
|
1169
|
-
content: Content::Text.new(args["message"])
|
|
1170
|
-
)
|
|
1171
|
-
]
|
|
1012
|
+
properties: {
|
|
1013
|
+
count: { type: "integer" },
|
|
1014
|
+
},
|
|
1015
|
+
required: ["count"]
|
|
1172
1016
|
)
|
|
1017
|
+
|
|
1018
|
+
def self.call(count:, server_context:)
|
|
1019
|
+
count.times do |i|
|
|
1020
|
+
# Do work here.
|
|
1021
|
+
server_context.report_progress(i + 1, total: count, message: "Processing item #{i + 1}")
|
|
1022
|
+
end
|
|
1023
|
+
|
|
1024
|
+
MCP::Tool::Response.new([{ type: "text", text: "Done" }])
|
|
1025
|
+
end
|
|
1173
1026
|
end
|
|
1174
1027
|
```
|
|
1175
1028
|
|
|
1176
|
-
The server_context
|
|
1177
|
-
e.g. around authentication state or user preferences.
|
|
1029
|
+
The `server_context.report_progress` method accepts:
|
|
1178
1030
|
|
|
1179
|
-
|
|
1031
|
+
- `progress` (required) — current progress value (numeric)
|
|
1032
|
+
- `total:` (optional) — total expected value, so clients can display a percentage
|
|
1033
|
+
- `message:` (optional) — human-readable status message
|
|
1180
1034
|
|
|
1181
|
-
|
|
1182
|
-
- `MCP::Prompt::Message` - Represents a message in the conversation with a role and content
|
|
1183
|
-
- `MCP::Prompt::Result` - The output of a prompt template containing description and messages
|
|
1184
|
-
- `MCP::Content::Text` - Text content for messages
|
|
1035
|
+
**Key Features:**
|
|
1185
1036
|
|
|
1186
|
-
|
|
1037
|
+
- Tools report progress via `server_context.report_progress`
|
|
1038
|
+
- `report_progress` is a no-op when no `progressToken` was provided by the client
|
|
1039
|
+
- Supports both numeric and string progress tokens
|
|
1187
1040
|
|
|
1188
|
-
|
|
1041
|
+
### Completions
|
|
1042
|
+
|
|
1043
|
+
MCP spec includes [Completions](https://modelcontextprotocol.io/specification/latest/server/utilities/completion),
|
|
1044
|
+
which enable servers to provide autocompletion suggestions for prompt arguments and resource URIs.
|
|
1045
|
+
|
|
1046
|
+
To enable completions, declare the `completions` capability and register a handler:
|
|
1189
1047
|
|
|
1190
1048
|
```ruby
|
|
1191
1049
|
server = MCP::Server.new(
|
|
1192
1050
|
name: "my_server",
|
|
1193
|
-
prompts: [
|
|
1194
|
-
|
|
1051
|
+
prompts: [CodeReviewPrompt],
|
|
1052
|
+
resource_templates: [FileTemplate],
|
|
1053
|
+
capabilities: { completions: {} },
|
|
1195
1054
|
)
|
|
1055
|
+
|
|
1056
|
+
server.completion_handler do |params|
|
|
1057
|
+
ref = params[:ref]
|
|
1058
|
+
argument = params[:argument]
|
|
1059
|
+
value = argument[:value]
|
|
1060
|
+
|
|
1061
|
+
case ref[:type]
|
|
1062
|
+
when "ref/prompt"
|
|
1063
|
+
values = case argument[:name]
|
|
1064
|
+
when "language"
|
|
1065
|
+
["python", "pytorch", "pyside"].select { |v| v.start_with?(value) }
|
|
1066
|
+
else
|
|
1067
|
+
[]
|
|
1068
|
+
end
|
|
1069
|
+
{ completion: { values: values, hasMore: false } }
|
|
1070
|
+
when "ref/resource"
|
|
1071
|
+
{ completion: { values: [], hasMore: false } }
|
|
1072
|
+
end
|
|
1073
|
+
end
|
|
1196
1074
|
```
|
|
1197
1075
|
|
|
1198
|
-
The
|
|
1076
|
+
The handler receives a `params` hash with:
|
|
1199
1077
|
|
|
1200
|
-
- `
|
|
1201
|
-
- `
|
|
1078
|
+
- `ref` - The reference (`{ type: "ref/prompt", name: "..." }` or `{ type: "ref/resource", uri: "..." }`)
|
|
1079
|
+
- `argument` - The argument being completed (`{ name: "...", value: "..." }`)
|
|
1080
|
+
- `context` (optional) - Previously resolved arguments (`{ arguments: { ... } }`)
|
|
1202
1081
|
|
|
1203
|
-
|
|
1082
|
+
The handler must return a hash with a `completion` key containing `values` (array of strings), and optionally `total` and `hasMore`.
|
|
1083
|
+
The SDK automatically enforces the 100-item limit per the MCP specification.
|
|
1204
1084
|
|
|
1205
|
-
|
|
1085
|
+
The server validates that the referenced prompt, resource, or resource template is registered before calling the handler.
|
|
1086
|
+
Requests for unknown references return an error.
|
|
1206
1087
|
|
|
1207
|
-
###
|
|
1088
|
+
### Logging
|
|
1208
1089
|
|
|
1209
|
-
The
|
|
1090
|
+
The MCP Ruby SDK supports structured logging through the `notify_log_message` method, following the [MCP Logging specification](https://modelcontextprotocol.io/specification/latest/server/utilities/logging).
|
|
1091
|
+
|
|
1092
|
+
The `notifications/message` notification is used for structured logging between client and server.
|
|
1093
|
+
|
|
1094
|
+
#### Log Levels
|
|
1095
|
+
|
|
1096
|
+
The SDK supports 8 log levels with increasing severity:
|
|
1097
|
+
|
|
1098
|
+
- `debug` - Detailed debugging information
|
|
1099
|
+
- `info` - General informational messages
|
|
1100
|
+
- `notice` - Normal but significant events
|
|
1101
|
+
- `warning` - Warning conditions
|
|
1102
|
+
- `error` - Error conditions
|
|
1103
|
+
- `critical` - Critical conditions
|
|
1104
|
+
- `alert` - Action must be taken immediately
|
|
1105
|
+
- `emergency` - System is unusable
|
|
1106
|
+
|
|
1107
|
+
#### How Logging Works
|
|
1108
|
+
|
|
1109
|
+
1. **Client Configuration**: The client sends a `logging/setLevel` request to configure the minimum log level
|
|
1110
|
+
2. **Server Filtering**: The server only sends log messages at the configured level or higher severity
|
|
1111
|
+
3. **Notification Delivery**: Log messages are sent as `notifications/message` to the client
|
|
1112
|
+
|
|
1113
|
+
For example, if the client sets the level to `"error"` (severity 4), the server will send messages with levels: `error`, `critical`, `alert`, and `emergency`.
|
|
1114
|
+
|
|
1115
|
+
For more details, see the [MCP Logging specification](https://modelcontextprotocol.io/specification/latest/server/utilities/logging).
|
|
1116
|
+
|
|
1117
|
+
**Usage Example:**
|
|
1210
1118
|
|
|
1211
1119
|
```ruby
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1120
|
+
server = MCP::Server.new(name: "my_server")
|
|
1121
|
+
transport = MCP::Server::Transports::StdioTransport.new(server)
|
|
1122
|
+
|
|
1123
|
+
# The client first configures the logging level (on the client side):
|
|
1124
|
+
transport.send_request(
|
|
1125
|
+
request: {
|
|
1126
|
+
jsonrpc: "2.0",
|
|
1127
|
+
method: "logging/setLevel",
|
|
1128
|
+
params: { level: "info" },
|
|
1129
|
+
id: session_id # Unique request ID within the session
|
|
1130
|
+
}
|
|
1218
1131
|
)
|
|
1219
1132
|
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1133
|
+
# Send log messages at different severity levels
|
|
1134
|
+
server.notify_log_message(
|
|
1135
|
+
data: { message: "Application started successfully" },
|
|
1136
|
+
level: "info"
|
|
1137
|
+
)
|
|
1138
|
+
|
|
1139
|
+
server.notify_log_message(
|
|
1140
|
+
data: { message: "Configuration file not found, using defaults" },
|
|
1141
|
+
level: "warning"
|
|
1142
|
+
)
|
|
1143
|
+
|
|
1144
|
+
server.notify_log_message(
|
|
1145
|
+
data: {
|
|
1146
|
+
error: "Database connection failed",
|
|
1147
|
+
details: { host: "localhost", port: 5432 }
|
|
1148
|
+
},
|
|
1149
|
+
level: "error",
|
|
1150
|
+
logger: "DatabaseLogger" # Optional logger name
|
|
1223
1151
|
)
|
|
1224
1152
|
```
|
|
1225
1153
|
|
|
1226
|
-
|
|
1154
|
+
**Key Features:**
|
|
1155
|
+
|
|
1156
|
+
- Supports 8 log levels (debug, info, notice, warning, error, critical, alert, emergency) based on https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/logging#log-levels
|
|
1157
|
+
- Server has capability `logging` to send log messages
|
|
1158
|
+
- Messages are only sent if a transport is configured
|
|
1159
|
+
- Messages are filtered based on the client's configured log level
|
|
1160
|
+
- If the log level hasn't been set by the client, no messages will be sent
|
|
1161
|
+
|
|
1162
|
+
#### Transport Support
|
|
1163
|
+
|
|
1164
|
+
- **stdio**: Notifications are sent as JSON-RPC 2.0 messages to stdout
|
|
1165
|
+
- **Streamable HTTP**: Notifications are sent as JSON-RPC 2.0 messages over HTTP with streaming (chunked transfer or SSE)
|
|
1166
|
+
|
|
1167
|
+
#### Usage Example
|
|
1227
1168
|
|
|
1228
1169
|
```ruby
|
|
1229
|
-
server.
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1170
|
+
server = MCP::Server.new(name: "my_server")
|
|
1171
|
+
|
|
1172
|
+
# Default Streamable HTTP - session oriented
|
|
1173
|
+
transport = MCP::Server::Transports::StreamableHTTPTransport.new(server)
|
|
1174
|
+
|
|
1175
|
+
# When tools change, notify clients
|
|
1176
|
+
server.define_tool(name: "new_tool") { |**args| { result: "ok" } }
|
|
1177
|
+
server.notify_tools_list_changed
|
|
1178
|
+
```
|
|
1179
|
+
|
|
1180
|
+
You can use Stateless Streamable HTTP, where notifications are not supported and all calls are request/response interactions.
|
|
1181
|
+
This mode allows for easy multi-node deployment.
|
|
1182
|
+
Set `stateless: true` in `MCP::Server::Transports::StreamableHTTPTransport.new` (`stateless` defaults to `false`):
|
|
1183
|
+
|
|
1184
|
+
```ruby
|
|
1185
|
+
# Stateless Streamable HTTP - session-less
|
|
1186
|
+
transport = MCP::Server::Transports::StreamableHTTPTransport.new(server, stateless: true)
|
|
1187
|
+
```
|
|
1188
|
+
|
|
1189
|
+
By default, sessions do not expire. To mitigate session hijacking risks, you can set a `session_idle_timeout` (in seconds).
|
|
1190
|
+
When configured, sessions that receive no HTTP requests for this duration are automatically expired and cleaned up:
|
|
1191
|
+
|
|
1192
|
+
```ruby
|
|
1193
|
+
# Session timeout of 30 minutes
|
|
1194
|
+
transport = MCP::Server::Transports::StreamableHTTPTransport.new(server, session_idle_timeout: 1800)
|
|
1195
|
+
```
|
|
1196
|
+
|
|
1197
|
+
### Advanced
|
|
1198
|
+
|
|
1199
|
+
#### Custom Methods
|
|
1200
|
+
|
|
1201
|
+
The server allows you to define custom JSON-RPC methods beyond the standard MCP protocol methods using the `define_custom_method` method:
|
|
1202
|
+
|
|
1203
|
+
```ruby
|
|
1204
|
+
server = MCP::Server.new(name: "my_server")
|
|
1205
|
+
|
|
1206
|
+
# Define a custom method that returns a result
|
|
1207
|
+
server.define_custom_method(method_name: "add") do |params|
|
|
1208
|
+
params[:a] + params[:b]
|
|
1209
|
+
end
|
|
1210
|
+
|
|
1211
|
+
# Define a custom notification method (returns nil)
|
|
1212
|
+
server.define_custom_method(method_name: "notify") do |params|
|
|
1213
|
+
# Process notification
|
|
1214
|
+
nil
|
|
1235
1215
|
end
|
|
1236
1216
|
```
|
|
1237
1217
|
|
|
1238
|
-
|
|
1218
|
+
**Key Features:**
|
|
1239
1219
|
|
|
1240
|
-
|
|
1220
|
+
- Accepts any method name as a string
|
|
1221
|
+
- Block receives the request parameters as a hash
|
|
1222
|
+
- Can handle both regular methods (with responses) and notifications
|
|
1223
|
+
- Prevents overriding existing MCP protocol methods
|
|
1224
|
+
- Supports instrumentation callbacks for monitoring
|
|
1241
1225
|
|
|
1242
|
-
|
|
1226
|
+
**Usage Example:**
|
|
1243
1227
|
|
|
1244
1228
|
```ruby
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1229
|
+
# Client request
|
|
1230
|
+
{
|
|
1231
|
+
"jsonrpc": "2.0",
|
|
1232
|
+
"id": 1,
|
|
1233
|
+
"method": "add",
|
|
1234
|
+
"params": { "a": 5, "b": 3 }
|
|
1235
|
+
}
|
|
1252
1236
|
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1237
|
+
# Server response
|
|
1238
|
+
{
|
|
1239
|
+
"jsonrpc": "2.0",
|
|
1240
|
+
"id": 1,
|
|
1241
|
+
"result": 8
|
|
1242
|
+
}
|
|
1257
1243
|
```
|
|
1258
1244
|
|
|
1245
|
+
**Error Handling:**
|
|
1246
|
+
|
|
1247
|
+
- Raises `MCP::Server::MethodAlreadyDefinedError` if trying to override an existing method
|
|
1248
|
+
- Supports the same exception reporting and instrumentation as standard methods
|
|
1249
|
+
|
|
1250
|
+
### Unsupported Features (to be implemented in future versions)
|
|
1251
|
+
|
|
1252
|
+
- Resource subscriptions
|
|
1253
|
+
- Elicitation
|
|
1254
|
+
|
|
1259
1255
|
## Building an MCP Client
|
|
1260
1256
|
|
|
1261
1257
|
The `MCP::Client` class provides an interface for interacting with MCP servers.
|
|
@@ -1405,6 +1401,18 @@ client.tools # will make the call using Bearer auth
|
|
|
1405
1401
|
|
|
1406
1402
|
You can add any custom headers needed for your authentication scheme, or for any other purpose. The client will include these headers on every request.
|
|
1407
1403
|
|
|
1404
|
+
#### Customizing the Faraday Connection
|
|
1405
|
+
|
|
1406
|
+
You can pass a block to `MCP::Client::HTTP.new` to customize the underlying Faraday connection.
|
|
1407
|
+
The block is called after the default middleware is configured, so you can add middleware or swap the HTTP adapter:
|
|
1408
|
+
|
|
1409
|
+
```ruby
|
|
1410
|
+
http_transport = MCP::Client::HTTP.new(url: "https://api.example.com/mcp") do |faraday|
|
|
1411
|
+
faraday.use MyApp::Middleware::HttpRecorder
|
|
1412
|
+
faraday.adapter :typhoeus
|
|
1413
|
+
end
|
|
1414
|
+
```
|
|
1415
|
+
|
|
1408
1416
|
### Tool Objects
|
|
1409
1417
|
|
|
1410
1418
|
The client provides a wrapper class for tools returned by the server:
|