mcp 0.10.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 +577 -355
- data/lib/json_rpc_handler.rb +1 -1
- data/lib/mcp/client/http.rb +4 -1
- data/lib/mcp/client.rb +62 -48
- data/lib/mcp/progress.rb +3 -1
- data/lib/mcp/server/transports/stdio_transport.rb +35 -0
- data/lib/mcp/server/transports/streamable_http_transport.rb +289 -83
- data/lib/mcp/server.rb +181 -23
- data/lib/mcp/server_context.rb +16 -2
- data/lib/mcp/server_session.rb +35 -7
- data/lib/mcp/tool/schema.rb +1 -14
- data/lib/mcp/transport.rb +14 -0
- data/lib/mcp/version.rb +1 -1
- metadata +5 -3
data/README.md
CHANGED
|
@@ -38,6 +38,7 @@ It implements the Model Context Protocol specification, handling model context r
|
|
|
38
38
|
- Supports resource registration and retrieval
|
|
39
39
|
- Supports stdio & Streamable HTTP (including SSE) transports
|
|
40
40
|
- Supports notifications for list changes (tools, prompts, resources)
|
|
41
|
+
- Supports sampling (server-to-client LLM completion requests)
|
|
41
42
|
|
|
42
43
|
### Supported Methods
|
|
43
44
|
|
|
@@ -50,288 +51,10 @@ It implements the Model Context Protocol specification, handling model context r
|
|
|
50
51
|
- `resources/list` - Lists all registered resources and their schemas
|
|
51
52
|
- `resources/read` - Retrieves a specific resource by name
|
|
52
53
|
- `resources/templates/list` - Lists all registered resource templates and their schemas
|
|
54
|
+
- `completion/complete` - Returns autocompletion suggestions for prompt arguments and resource URIs
|
|
55
|
+
- `sampling/createMessage` - Requests LLM completion from the client (server-to-client)
|
|
53
56
|
|
|
54
|
-
###
|
|
55
|
-
|
|
56
|
-
The server allows you to define custom JSON-RPC methods beyond the standard MCP protocol methods using the `define_custom_method` method:
|
|
57
|
-
|
|
58
|
-
```ruby
|
|
59
|
-
server = MCP::Server.new(name: "my_server")
|
|
60
|
-
|
|
61
|
-
# Define a custom method that returns a result
|
|
62
|
-
server.define_custom_method(method_name: "add") do |params|
|
|
63
|
-
params[:a] + params[:b]
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
# Define a custom notification method (returns nil)
|
|
67
|
-
server.define_custom_method(method_name: "notify") do |params|
|
|
68
|
-
# Process notification
|
|
69
|
-
nil
|
|
70
|
-
end
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
**Key Features:**
|
|
74
|
-
|
|
75
|
-
- Accepts any method name as a string
|
|
76
|
-
- Block receives the request parameters as a hash
|
|
77
|
-
- Can handle both regular methods (with responses) and notifications
|
|
78
|
-
- Prevents overriding existing MCP protocol methods
|
|
79
|
-
- Supports instrumentation callbacks for monitoring
|
|
80
|
-
|
|
81
|
-
**Usage Example:**
|
|
82
|
-
|
|
83
|
-
```ruby
|
|
84
|
-
# Client request
|
|
85
|
-
{
|
|
86
|
-
"jsonrpc": "2.0",
|
|
87
|
-
"id": 1,
|
|
88
|
-
"method": "add",
|
|
89
|
-
"params": { "a": 5, "b": 3 }
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
# Server response
|
|
93
|
-
{
|
|
94
|
-
"jsonrpc": "2.0",
|
|
95
|
-
"id": 1,
|
|
96
|
-
"result": 8
|
|
97
|
-
}
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
**Error Handling:**
|
|
101
|
-
|
|
102
|
-
- Raises `MCP::Server::MethodAlreadyDefinedError` if trying to override an existing method
|
|
103
|
-
- Supports the same exception reporting and instrumentation as standard methods
|
|
104
|
-
|
|
105
|
-
### Notifications
|
|
106
|
-
|
|
107
|
-
The server supports sending notifications to clients when lists of tools, prompts, or resources change. This enables real-time updates without polling.
|
|
108
|
-
|
|
109
|
-
#### Notification Methods
|
|
110
|
-
|
|
111
|
-
The server provides the following notification methods:
|
|
112
|
-
|
|
113
|
-
- `notify_tools_list_changed` - Send a notification when the tools list changes
|
|
114
|
-
- `notify_prompts_list_changed` - Send a notification when the prompts list changes
|
|
115
|
-
- `notify_resources_list_changed` - Send a notification when the resources list changes
|
|
116
|
-
- `notify_log_message` - Send a structured logging notification message
|
|
117
|
-
|
|
118
|
-
#### Session Scoping
|
|
119
|
-
|
|
120
|
-
When using Streamable HTTP transport with multiple clients, each client connection gets its own session. Notifications are scoped as follows:
|
|
121
|
-
|
|
122
|
-
- **`report_progress`** and **`notify_log_message`** called via `server_context` inside a tool handler are automatically sent only to the requesting client.
|
|
123
|
-
No extra configuration is needed.
|
|
124
|
-
- **`notify_tools_list_changed`**, **`notify_prompts_list_changed`**, and **`notify_resources_list_changed`** are always broadcast to all connected clients,
|
|
125
|
-
as they represent server-wide state changes. These should be called on the `server` instance directly.
|
|
126
|
-
|
|
127
|
-
#### Notification Format
|
|
128
|
-
|
|
129
|
-
Notifications follow the JSON-RPC 2.0 specification and use these method names:
|
|
130
|
-
|
|
131
|
-
- `notifications/tools/list_changed`
|
|
132
|
-
- `notifications/prompts/list_changed`
|
|
133
|
-
- `notifications/resources/list_changed`
|
|
134
|
-
- `notifications/progress`
|
|
135
|
-
- `notifications/message`
|
|
136
|
-
|
|
137
|
-
### Progress
|
|
138
|
-
|
|
139
|
-
The MCP Ruby SDK supports progress tracking for long-running tool operations,
|
|
140
|
-
following the [MCP Progress specification](https://modelcontextprotocol.io/specification/latest/server/utilities/progress).
|
|
141
|
-
|
|
142
|
-
#### How Progress Works
|
|
143
|
-
|
|
144
|
-
1. **Client Request**: The client sends a `progressToken` in the `_meta` field when calling a tool
|
|
145
|
-
2. **Server Notification**: The server sends `notifications/progress` messages back to the client during tool execution
|
|
146
|
-
3. **Tool Integration**: Tools call `server_context.report_progress` to report incremental progress
|
|
147
|
-
|
|
148
|
-
#### Server-Side: Tool with Progress
|
|
149
|
-
|
|
150
|
-
Tools that accept a `server_context:` parameter can call `report_progress` on it.
|
|
151
|
-
The server automatically wraps the context in an `MCP::ServerContext` instance that provides this method:
|
|
152
|
-
|
|
153
|
-
```ruby
|
|
154
|
-
class LongRunningTool < MCP::Tool
|
|
155
|
-
description "A tool that reports progress during execution"
|
|
156
|
-
input_schema(
|
|
157
|
-
properties: {
|
|
158
|
-
count: { type: "integer" },
|
|
159
|
-
},
|
|
160
|
-
required: ["count"]
|
|
161
|
-
)
|
|
162
|
-
|
|
163
|
-
def self.call(count:, server_context:)
|
|
164
|
-
count.times do |i|
|
|
165
|
-
# Do work here.
|
|
166
|
-
server_context.report_progress(i + 1, total: count, message: "Processing item #{i + 1}")
|
|
167
|
-
end
|
|
168
|
-
|
|
169
|
-
MCP::Tool::Response.new([{ type: "text", text: "Done" }])
|
|
170
|
-
end
|
|
171
|
-
end
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
The `server_context.report_progress` method accepts:
|
|
175
|
-
|
|
176
|
-
- `progress` (required) — current progress value (numeric)
|
|
177
|
-
- `total:` (optional) — total expected value, so clients can display a percentage
|
|
178
|
-
- `message:` (optional) — human-readable status message
|
|
179
|
-
|
|
180
|
-
**Key Features:**
|
|
181
|
-
|
|
182
|
-
- Tools report progress via `server_context.report_progress`
|
|
183
|
-
- `report_progress` is a no-op when no `progressToken` was provided by the client
|
|
184
|
-
- Supports both numeric and string progress tokens
|
|
185
|
-
|
|
186
|
-
### Logging
|
|
187
|
-
|
|
188
|
-
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).
|
|
189
|
-
|
|
190
|
-
The `notifications/message` notification is used for structured logging between client and server.
|
|
191
|
-
|
|
192
|
-
#### Log Levels
|
|
193
|
-
|
|
194
|
-
The SDK supports 8 log levels with increasing severity:
|
|
195
|
-
|
|
196
|
-
- `debug` - Detailed debugging information
|
|
197
|
-
- `info` - General informational messages
|
|
198
|
-
- `notice` - Normal but significant events
|
|
199
|
-
- `warning` - Warning conditions
|
|
200
|
-
- `error` - Error conditions
|
|
201
|
-
- `critical` - Critical conditions
|
|
202
|
-
- `alert` - Action must be taken immediately
|
|
203
|
-
- `emergency` - System is unusable
|
|
204
|
-
|
|
205
|
-
#### How Logging Works
|
|
206
|
-
|
|
207
|
-
1. **Client Configuration**: The client sends a `logging/setLevel` request to configure the minimum log level
|
|
208
|
-
2. **Server Filtering**: The server only sends log messages at the configured level or higher severity
|
|
209
|
-
3. **Notification Delivery**: Log messages are sent as `notifications/message` to the client
|
|
210
|
-
|
|
211
|
-
For example, if the client sets the level to `"error"` (severity 4), the server will send messages with levels: `error`, `critical`, `alert`, and `emergency`.
|
|
212
|
-
|
|
213
|
-
For more details, see the [MCP Logging specification](https://modelcontextprotocol.io/specification/latest/server/utilities/logging).
|
|
214
|
-
|
|
215
|
-
**Usage Example:**
|
|
216
|
-
|
|
217
|
-
```ruby
|
|
218
|
-
server = MCP::Server.new(name: "my_server")
|
|
219
|
-
transport = MCP::Server::Transports::StdioTransport.new(server)
|
|
220
|
-
server.transport = transport
|
|
221
|
-
|
|
222
|
-
# The client first configures the logging level (on the client side):
|
|
223
|
-
transport.send_request(
|
|
224
|
-
request: {
|
|
225
|
-
jsonrpc: "2.0",
|
|
226
|
-
method: "logging/setLevel",
|
|
227
|
-
params: { level: "info" },
|
|
228
|
-
id: session_id # Unique request ID within the session
|
|
229
|
-
}
|
|
230
|
-
)
|
|
231
|
-
|
|
232
|
-
# Send log messages at different severity levels
|
|
233
|
-
server.notify_log_message(
|
|
234
|
-
data: { message: "Application started successfully" },
|
|
235
|
-
level: "info"
|
|
236
|
-
)
|
|
237
|
-
|
|
238
|
-
server.notify_log_message(
|
|
239
|
-
data: { message: "Configuration file not found, using defaults" },
|
|
240
|
-
level: "warning"
|
|
241
|
-
)
|
|
242
|
-
|
|
243
|
-
server.notify_log_message(
|
|
244
|
-
data: {
|
|
245
|
-
error: "Database connection failed",
|
|
246
|
-
details: { host: "localhost", port: 5432 }
|
|
247
|
-
},
|
|
248
|
-
level: "error",
|
|
249
|
-
logger: "DatabaseLogger" # Optional logger name
|
|
250
|
-
)
|
|
251
|
-
```
|
|
252
|
-
|
|
253
|
-
**Key Features:**
|
|
254
|
-
|
|
255
|
-
- 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
|
|
256
|
-
- Server has capability `logging` to send log messages
|
|
257
|
-
- Messages are only sent if a transport is configured
|
|
258
|
-
- Messages are filtered based on the client's configured log level
|
|
259
|
-
- If the log level hasn't been set by the client, no messages will be sent
|
|
260
|
-
|
|
261
|
-
#### Transport Support
|
|
262
|
-
|
|
263
|
-
- **stdio**: Notifications are sent as JSON-RPC 2.0 messages to stdout
|
|
264
|
-
- **Streamable HTTP**: Notifications are sent as JSON-RPC 2.0 messages over HTTP with streaming (chunked transfer or SSE)
|
|
265
|
-
|
|
266
|
-
#### Usage Example
|
|
267
|
-
|
|
268
|
-
```ruby
|
|
269
|
-
server = MCP::Server.new(name: "my_server")
|
|
270
|
-
|
|
271
|
-
# Default Streamable HTTP - session oriented
|
|
272
|
-
transport = MCP::Server::Transports::StreamableHTTPTransport.new(server)
|
|
273
|
-
|
|
274
|
-
server.transport = transport
|
|
275
|
-
|
|
276
|
-
# When tools change, notify clients
|
|
277
|
-
server.define_tool(name: "new_tool") { |**args| { result: "ok" } }
|
|
278
|
-
server.notify_tools_list_changed
|
|
279
|
-
```
|
|
280
|
-
|
|
281
|
-
You can use Stateless Streamable HTTP, where notifications are not supported and all calls are request/response interactions.
|
|
282
|
-
This mode allows for easy multi-node deployment.
|
|
283
|
-
Set `stateless: true` in `MCP::Server::Transports::StreamableHTTPTransport.new` (`stateless` defaults to `false`):
|
|
284
|
-
|
|
285
|
-
```ruby
|
|
286
|
-
# Stateless Streamable HTTP - session-less
|
|
287
|
-
transport = MCP::Server::Transports::StreamableHTTPTransport.new(server, stateless: true)
|
|
288
|
-
```
|
|
289
|
-
|
|
290
|
-
By default, sessions do not expire. To mitigate session hijacking risks, you can set a `session_idle_timeout` (in seconds).
|
|
291
|
-
When configured, sessions that receive no HTTP requests for this duration are automatically expired and cleaned up:
|
|
292
|
-
|
|
293
|
-
```ruby
|
|
294
|
-
# Session timeout of 30 minutes
|
|
295
|
-
transport = MCP::Server::Transports::StreamableHTTPTransport.new(server, session_idle_timeout: 1800)
|
|
296
|
-
```
|
|
297
|
-
|
|
298
|
-
### Unsupported Features (to be implemented in future versions)
|
|
299
|
-
|
|
300
|
-
- Resource subscriptions
|
|
301
|
-
- Completions
|
|
302
|
-
- Elicitation
|
|
303
|
-
|
|
304
|
-
### Usage
|
|
305
|
-
|
|
306
|
-
#### Rails Controller
|
|
307
|
-
|
|
308
|
-
When added to a Rails controller on a route that handles POST requests, your server will be compliant with non-streaming
|
|
309
|
-
[Streamable HTTP](https://modelcontextprotocol.io/specification/latest/basic/transports#streamable-http) transport
|
|
310
|
-
requests.
|
|
311
|
-
|
|
312
|
-
You can use `StreamableHTTPTransport#handle_request` to handle requests with proper HTTP
|
|
313
|
-
status codes (e.g., 202 Accepted for notifications).
|
|
314
|
-
|
|
315
|
-
```ruby
|
|
316
|
-
class McpController < ActionController::Base
|
|
317
|
-
def create
|
|
318
|
-
server = MCP::Server.new(
|
|
319
|
-
name: "my_server",
|
|
320
|
-
title: "Example Server Display Name",
|
|
321
|
-
version: "1.0.0",
|
|
322
|
-
instructions: "Use the tools of this server as a last resort",
|
|
323
|
-
tools: [SomeTool, AnotherTool],
|
|
324
|
-
prompts: [MyPrompt],
|
|
325
|
-
server_context: { user_id: current_user.id },
|
|
326
|
-
)
|
|
327
|
-
transport = MCP::Server::Transports::StreamableHTTPTransport.new(server)
|
|
328
|
-
server.transport = transport
|
|
329
|
-
status, headers, body = transport.handle_request(request)
|
|
330
|
-
|
|
331
|
-
render(json: body.first, status: status, headers: headers)
|
|
332
|
-
end
|
|
333
|
-
end
|
|
334
|
-
```
|
|
57
|
+
### Usage
|
|
335
58
|
|
|
336
59
|
#### Stdio Transport
|
|
337
60
|
|
|
@@ -380,6 +103,48 @@ $ ruby examples/stdio_server.rb
|
|
|
380
103
|
{"jsonrpc":"2.0","id":"3","method":"tools/call","params":{"name":"example_tool","arguments":{"message":"Hello"}}}
|
|
381
104
|
```
|
|
382
105
|
|
|
106
|
+
#### Rails Controller
|
|
107
|
+
|
|
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.
|
|
111
|
+
|
|
112
|
+
You can use `StreamableHTTPTransport#handle_request` to handle requests with proper HTTP
|
|
113
|
+
status codes (e.g., 202 Accepted for notifications).
|
|
114
|
+
|
|
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)
|
|
130
|
+
|
|
131
|
+
render(json: body.first, status: status, headers: headers)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
```
|
|
135
|
+
|
|
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.
|
|
147
|
+
|
|
383
148
|
### Configuration
|
|
384
149
|
|
|
385
150
|
The gem can be configured using the `MCP.configure` block:
|
|
@@ -930,119 +695,563 @@ end
|
|
|
930
695
|
3. Using the `MCP::Server#define_prompt` method:
|
|
931
696
|
|
|
932
697
|
```ruby
|
|
933
|
-
server = MCP::Server.new
|
|
934
|
-
server.define_prompt(
|
|
935
|
-
name: "my_prompt",
|
|
936
|
-
description: "This prompt performs specific functionality...",
|
|
937
|
-
arguments: [
|
|
938
|
-
Prompt::Argument.new(
|
|
939
|
-
name: "message",
|
|
940
|
-
title: "Message Title",
|
|
941
|
-
description: "Input message",
|
|
942
|
-
required: true
|
|
943
|
-
)
|
|
944
|
-
],
|
|
945
|
-
meta: { version: "1.0", category: "example" }
|
|
946
|
-
) do |args, server_context:|
|
|
947
|
-
Prompt::Result.new(
|
|
948
|
-
description: "Response description",
|
|
949
|
-
messages: [
|
|
950
|
-
Prompt::Message.new(
|
|
951
|
-
role: "user",
|
|
952
|
-
content: Content::Text.new("User message")
|
|
953
|
-
),
|
|
954
|
-
Prompt::Message.new(
|
|
955
|
-
role: "assistant",
|
|
956
|
-
content: Content::Text.new(args["message"])
|
|
957
|
-
)
|
|
958
|
-
]
|
|
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
|
+
```
|
|
727
|
+
|
|
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
|
|
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
|
|
743
|
+
server = MCP::Server.new(
|
|
744
|
+
name: "my_server",
|
|
745
|
+
prompts: [MyPrompt],
|
|
746
|
+
server_context: { user_id: current_user.id },
|
|
747
|
+
)
|
|
748
|
+
```
|
|
749
|
+
|
|
750
|
+
The server will handle prompt listing and execution through the MCP protocol methods:
|
|
751
|
+
|
|
752
|
+
- `prompts/list` - Lists all registered prompts and their schemas
|
|
753
|
+
- `prompts/get` - Retrieves and executes a specific prompt with arguments
|
|
754
|
+
|
|
755
|
+
### Resources
|
|
756
|
+
|
|
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.
|
|
762
|
+
|
|
763
|
+
```ruby
|
|
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
|
+
)
|
|
776
|
+
```
|
|
777
|
+
|
|
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.
|
|
795
|
+
|
|
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
|
+
|
|
805
|
+
server = MCP::Server.new(
|
|
806
|
+
name: "my_server",
|
|
807
|
+
resource_templates: [resource_template],
|
|
808
|
+
)
|
|
809
|
+
```
|
|
810
|
+
|
|
811
|
+
### Sampling
|
|
812
|
+
|
|
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.
|
|
815
|
+
|
|
816
|
+
**Key Concepts:**
|
|
817
|
+
|
|
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
|
|
822
|
+
|
|
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:
|
|
828
|
+
|
|
829
|
+
```ruby
|
|
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
|
+
)
|
|
838
|
+
|
|
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
|
+
)
|
|
846
|
+
|
|
847
|
+
MCP::Tool::Response.new([{
|
|
848
|
+
type: "text",
|
|
849
|
+
text: result[:content][:text]
|
|
850
|
+
}])
|
|
851
|
+
end
|
|
852
|
+
end
|
|
853
|
+
|
|
854
|
+
server = MCP::Server.new(name: "my_server", tools: [SummarizeTool])
|
|
855
|
+
server.server_context = server
|
|
856
|
+
```
|
|
857
|
+
|
|
858
|
+
**Parameters:**
|
|
859
|
+
|
|
860
|
+
Required:
|
|
861
|
+
|
|
862
|
+
- `messages:` (Array) - Array of message objects with `role` and `content`
|
|
863
|
+
- `max_tokens:` (Integer) - Maximum tokens in the response
|
|
864
|
+
|
|
865
|
+
Optional:
|
|
866
|
+
|
|
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" }`)
|
|
875
|
+
|
|
876
|
+
**Direct Usage:**
|
|
877
|
+
|
|
878
|
+
`Server#create_sampling_message` can also be called directly outside of tools:
|
|
879
|
+
|
|
880
|
+
```ruby
|
|
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
|
+
)
|
|
889
|
+
```
|
|
890
|
+
|
|
891
|
+
Result contains the LLM response:
|
|
892
|
+
|
|
893
|
+
```ruby
|
|
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
|
+
}
|
|
900
|
+
```
|
|
901
|
+
|
|
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.
|
|
904
|
+
|
|
905
|
+
**Tool Use in Sampling:**
|
|
906
|
+
|
|
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:
|
|
909
|
+
|
|
910
|
+
```ruby
|
|
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
|
+
)
|
|
929
|
+
|
|
930
|
+
if result[:stopReason] == "toolUse"
|
|
931
|
+
tool_results = result[:content].map do |tool_use|
|
|
932
|
+
weather_data = get_weather(tool_use[:input][:city])
|
|
933
|
+
|
|
934
|
+
{
|
|
935
|
+
type: "tool_result",
|
|
936
|
+
toolUseId: tool_use[:id],
|
|
937
|
+
content: [{ type: "text", text: weather_data.to_json }]
|
|
938
|
+
}
|
|
939
|
+
end
|
|
940
|
+
|
|
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
|
|
951
|
+
```
|
|
952
|
+
|
|
953
|
+
**Error Handling:**
|
|
954
|
+
|
|
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
|
|
959
|
+
|
|
960
|
+
### Notifications
|
|
961
|
+
|
|
962
|
+
The server supports sending notifications to clients when lists of tools, prompts, or resources change. This enables real-time updates without polling.
|
|
963
|
+
|
|
964
|
+
#### Notification Methods
|
|
965
|
+
|
|
966
|
+
The server provides the following notification methods:
|
|
967
|
+
|
|
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
|
|
972
|
+
|
|
973
|
+
#### Session Scoping
|
|
974
|
+
|
|
975
|
+
When using Streamable HTTP transport with multiple clients, each client connection gets its own session. Notifications are scoped as follows:
|
|
976
|
+
|
|
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.
|
|
981
|
+
|
|
982
|
+
#### Notification Format
|
|
983
|
+
|
|
984
|
+
Notifications follow the JSON-RPC 2.0 specification and use these method names:
|
|
985
|
+
|
|
986
|
+
- `notifications/tools/list_changed`
|
|
987
|
+
- `notifications/prompts/list_changed`
|
|
988
|
+
- `notifications/resources/list_changed`
|
|
989
|
+
- `notifications/progress`
|
|
990
|
+
- `notifications/message`
|
|
991
|
+
|
|
992
|
+
### Progress
|
|
993
|
+
|
|
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).
|
|
996
|
+
|
|
997
|
+
#### How Progress Works
|
|
998
|
+
|
|
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
|
|
1002
|
+
|
|
1003
|
+
#### Server-Side: Tool with Progress
|
|
1004
|
+
|
|
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:
|
|
1007
|
+
|
|
1008
|
+
```ruby
|
|
1009
|
+
class LongRunningTool < MCP::Tool
|
|
1010
|
+
description "A tool that reports progress during execution"
|
|
1011
|
+
input_schema(
|
|
1012
|
+
properties: {
|
|
1013
|
+
count: { type: "integer" },
|
|
1014
|
+
},
|
|
1015
|
+
required: ["count"]
|
|
959
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
|
|
960
1026
|
end
|
|
961
1027
|
```
|
|
962
1028
|
|
|
963
|
-
The server_context
|
|
964
|
-
e.g. around authentication state or user preferences.
|
|
1029
|
+
The `server_context.report_progress` method accepts:
|
|
965
1030
|
|
|
966
|
-
|
|
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
|
|
967
1034
|
|
|
968
|
-
|
|
969
|
-
- `MCP::Prompt::Message` - Represents a message in the conversation with a role and content
|
|
970
|
-
- `MCP::Prompt::Result` - The output of a prompt template containing description and messages
|
|
971
|
-
- `MCP::Content::Text` - Text content for messages
|
|
1035
|
+
**Key Features:**
|
|
972
1036
|
|
|
973
|
-
|
|
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
|
|
974
1040
|
|
|
975
|
-
|
|
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:
|
|
976
1047
|
|
|
977
1048
|
```ruby
|
|
978
1049
|
server = MCP::Server.new(
|
|
979
1050
|
name: "my_server",
|
|
980
|
-
prompts: [
|
|
981
|
-
|
|
1051
|
+
prompts: [CodeReviewPrompt],
|
|
1052
|
+
resource_templates: [FileTemplate],
|
|
1053
|
+
capabilities: { completions: {} },
|
|
982
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
|
|
983
1074
|
```
|
|
984
1075
|
|
|
985
|
-
The
|
|
1076
|
+
The handler receives a `params` hash with:
|
|
986
1077
|
|
|
987
|
-
- `
|
|
988
|
-
- `
|
|
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: { ... } }`)
|
|
989
1081
|
|
|
990
|
-
|
|
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.
|
|
991
1084
|
|
|
992
|
-
|
|
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.
|
|
993
1087
|
|
|
994
|
-
###
|
|
1088
|
+
### Logging
|
|
995
1089
|
|
|
996
|
-
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:**
|
|
997
1118
|
|
|
998
1119
|
```ruby
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
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
|
+
}
|
|
1005
1131
|
)
|
|
1006
1132
|
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
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
|
|
1010
1151
|
)
|
|
1011
1152
|
```
|
|
1012
1153
|
|
|
1013
|
-
|
|
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
|
|
1014
1168
|
|
|
1015
1169
|
```ruby
|
|
1016
|
-
server.
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
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
|
|
1022
1215
|
end
|
|
1023
1216
|
```
|
|
1024
1217
|
|
|
1025
|
-
|
|
1218
|
+
**Key Features:**
|
|
1026
1219
|
|
|
1027
|
-
|
|
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
|
|
1028
1225
|
|
|
1029
|
-
|
|
1226
|
+
**Usage Example:**
|
|
1030
1227
|
|
|
1031
1228
|
```ruby
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1229
|
+
# Client request
|
|
1230
|
+
{
|
|
1231
|
+
"jsonrpc": "2.0",
|
|
1232
|
+
"id": 1,
|
|
1233
|
+
"method": "add",
|
|
1234
|
+
"params": { "a": 5, "b": 3 }
|
|
1235
|
+
}
|
|
1039
1236
|
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1237
|
+
# Server response
|
|
1238
|
+
{
|
|
1239
|
+
"jsonrpc": "2.0",
|
|
1240
|
+
"id": 1,
|
|
1241
|
+
"result": 8
|
|
1242
|
+
}
|
|
1044
1243
|
```
|
|
1045
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
|
+
|
|
1046
1255
|
## Building an MCP Client
|
|
1047
1256
|
|
|
1048
1257
|
The `MCP::Client` class provides an interface for interacting with MCP servers.
|
|
@@ -1056,6 +1265,7 @@ This class supports:
|
|
|
1056
1265
|
- Resource reading via the `resources/read` method (`MCP::Client#read_resources`)
|
|
1057
1266
|
- Prompt listing via the `prompts/list` method (`MCP::Client#prompts`)
|
|
1058
1267
|
- Prompt retrieval via the `prompts/get` method (`MCP::Client#get_prompt`)
|
|
1268
|
+
- Completion requests via the `completion/complete` method (`MCP::Client#complete`)
|
|
1059
1269
|
- Automatic JSON-RPC 2.0 message formatting
|
|
1060
1270
|
- UUID request ID generation
|
|
1061
1271
|
|
|
@@ -1191,6 +1401,18 @@ client.tools # will make the call using Bearer auth
|
|
|
1191
1401
|
|
|
1192
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.
|
|
1193
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
|
+
|
|
1194
1416
|
### Tool Objects
|
|
1195
1417
|
|
|
1196
1418
|
The client provides a wrapper class for tools returned by the server:
|