model_context_protocol_riccardo 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.cursor/rules/release-changelogs.mdc +32 -0
- data/.gitattributes +4 -0
- data/.github/workflows/ci.yml +22 -0
- data/.gitignore +8 -0
- data/.rubocop.yml +5 -0
- data/.ruby-version +1 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +15 -0
- data/Gemfile.lock +117 -0
- data/LICENSE.txt +21 -0
- data/README.md +473 -0
- data/Rakefile +17 -0
- data/bin/console +15 -0
- data/bin/rake +31 -0
- data/bin/setup +8 -0
- data/dev.yml +31 -0
- data/examples/stdio_server.rb +94 -0
- data/lib/mcp-ruby.rb +3 -0
- data/lib/model_context_protocol/configuration.rb +75 -0
- data/lib/model_context_protocol/content.rb +33 -0
- data/lib/model_context_protocol/instrumentation.rb +26 -0
- data/lib/model_context_protocol/json_rpc.rb +11 -0
- data/lib/model_context_protocol/methods.rb +86 -0
- data/lib/model_context_protocol/prompt/argument.rb +21 -0
- data/lib/model_context_protocol/prompt/message.rb +19 -0
- data/lib/model_context_protocol/prompt/result.rb +19 -0
- data/lib/model_context_protocol/prompt.rb +82 -0
- data/lib/model_context_protocol/resource/contents.rb +45 -0
- data/lib/model_context_protocol/resource/embedded.rb +18 -0
- data/lib/model_context_protocol/resource.rb +24 -0
- data/lib/model_context_protocol/resource_template.rb +24 -0
- data/lib/model_context_protocol/server.rb +258 -0
- data/lib/model_context_protocol/string_utils.rb +26 -0
- data/lib/model_context_protocol/tool/annotations.rb +27 -0
- data/lib/model_context_protocol/tool/input_schema.rb +26 -0
- data/lib/model_context_protocol/tool/response.rb +18 -0
- data/lib/model_context_protocol/tool.rb +85 -0
- data/lib/model_context_protocol/transport.rb +33 -0
- data/lib/model_context_protocol/transports/stdio.rb +33 -0
- data/lib/model_context_protocol/version.rb +5 -0
- data/lib/model_context_protocol.rb +44 -0
- data/model_context_protocol.gemspec +32 -0
- metadata +116 -0
data/README.md
ADDED
@@ -0,0 +1,473 @@
|
|
1
|
+
# Model Context Protocol
|
2
|
+
|
3
|
+
A Ruby gem for implementing Model Context Protocol servers
|
4
|
+
|
5
|
+
## MCP Server
|
6
|
+
|
7
|
+
The `ModelContextProtocol::Server` class is the core component that handles JSON-RPC requests and responses.
|
8
|
+
It implements the Model Context Protocol specification, handling model context requests and responses.
|
9
|
+
|
10
|
+
### Key Features
|
11
|
+
- Implements JSON-RPC 2.0 message handling
|
12
|
+
- Supports protocol initialization and capability negotiation
|
13
|
+
- Manages tool registration and invocation
|
14
|
+
- Supports prompt registration and execution
|
15
|
+
- Supports resource registration and retrieval
|
16
|
+
|
17
|
+
### Supported Methods
|
18
|
+
- `initialize` - Initializes the protocol and returns server capabilities
|
19
|
+
- `ping` - Simple health check
|
20
|
+
- `tools/list` - Lists all registered tools and their schemas
|
21
|
+
- `tools/call` - Invokes a specific tool with provided arguments
|
22
|
+
- `prompts/list` - Lists all registered prompts and their schemas
|
23
|
+
- `prompts/get` - Retrieves a specific prompt by name
|
24
|
+
- `resources/list` - Lists all registered resources and their schemas
|
25
|
+
- `resources/read` - Retrieves a specific resource by name
|
26
|
+
- `resources/templates/list` - Lists all registered resource templates and their schemas
|
27
|
+
|
28
|
+
### Unsupported Features ( to be implemented in future versions )
|
29
|
+
|
30
|
+
- Notifications
|
31
|
+
- Log Level
|
32
|
+
- Resource subscriptions
|
33
|
+
- Completions
|
34
|
+
- Complete StreamableHTTP implementation with streaming responses
|
35
|
+
|
36
|
+
### Usage
|
37
|
+
|
38
|
+
#### Rails Controller
|
39
|
+
|
40
|
+
When added to a Rails controller on a route that handles POST requests, your server will be compliant with non-streaming
|
41
|
+
[StreamableHTTP](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http) transport
|
42
|
+
requests.
|
43
|
+
|
44
|
+
You can use the `Server#handle_json` method to handle requests.
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
module ModelContextProtocol
|
48
|
+
class ApplicationController < ActionController::Base
|
49
|
+
|
50
|
+
def index
|
51
|
+
server = ModelContextProtocol::Server.new(
|
52
|
+
name: "my_server",
|
53
|
+
tools: [SomeTool, AnotherTool],
|
54
|
+
prompts: [MyPrompt],
|
55
|
+
server_context: { user_id: current_user.id },
|
56
|
+
)
|
57
|
+
render(json: server.handle_json(request.body.read).to_h)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
```
|
62
|
+
|
63
|
+
#### Stdio Transport
|
64
|
+
|
65
|
+
If you want to build a local command-line application, you can use the stdio transport:
|
66
|
+
|
67
|
+
```ruby
|
68
|
+
#!/usr/bin/env ruby
|
69
|
+
require "model_context_protocol"
|
70
|
+
require "model_context_protocol/transports/stdio"
|
71
|
+
|
72
|
+
# Create a simple tool
|
73
|
+
class ExampleTool < ModelContextProtocol::Tool
|
74
|
+
description "A simple example tool that echoes back its arguments"
|
75
|
+
input_schema(
|
76
|
+
properties: {
|
77
|
+
message: { type: "string" },
|
78
|
+
},
|
79
|
+
required: ["message"]
|
80
|
+
)
|
81
|
+
|
82
|
+
class << self
|
83
|
+
def call(message:, server_context:)
|
84
|
+
ModelContextProtocol::Tool::Response.new([{
|
85
|
+
type: "text",
|
86
|
+
text: "Hello from example tool! Message: #{message}",
|
87
|
+
}])
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Set up the server
|
93
|
+
server = ModelContextProtocol::Server.new(
|
94
|
+
name: "example_server",
|
95
|
+
tools: [ExampleTool],
|
96
|
+
)
|
97
|
+
|
98
|
+
# Create and start the transport
|
99
|
+
transport = ModelContextProtocol::Transports::StdioTransport.new(server)
|
100
|
+
transport.open
|
101
|
+
```
|
102
|
+
|
103
|
+
You can run this script and then type in requests to the server at the command line.
|
104
|
+
|
105
|
+
```
|
106
|
+
$ ./stdio_server.rb
|
107
|
+
{"jsonrpc":"2.0","id":"1","result":"pong"}
|
108
|
+
{"jsonrpc":"2.0","id":"2","result":["ExampleTool"]}
|
109
|
+
{"jsonrpc":"2.0","id":"3","result":["ExampleTool"]}
|
110
|
+
```
|
111
|
+
|
112
|
+
## Configuration
|
113
|
+
|
114
|
+
The gem can be configured using the `ModelContextProtocol.configure` block:
|
115
|
+
|
116
|
+
```ruby
|
117
|
+
ModelContextProtocol.configure do |config|
|
118
|
+
config.exception_reporter = do |exception, server_context|
|
119
|
+
# Your exception reporting logic here
|
120
|
+
# For example with Bugsnag:
|
121
|
+
Bugsnag.notify(exception) do |report|
|
122
|
+
report.add_metadata(:model_context_protocol, server_context)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
config.instrumentation_callback = do |data|
|
127
|
+
puts "Got instrumentation data #{data.inspect}"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
```
|
131
|
+
|
132
|
+
or by creating an explicit configuration and passing it into the server.
|
133
|
+
This is useful for systems where an application hosts more than one MCP server but
|
134
|
+
they might require different instrumentation callbacks.
|
135
|
+
|
136
|
+
```ruby
|
137
|
+
configuration = ModelContextProtocol::Configuration.new
|
138
|
+
configuration.exception_reporter = do |exception, server_context|
|
139
|
+
# Your exception reporting logic here
|
140
|
+
# For example with Bugsnag:
|
141
|
+
Bugsnag.notify(exception) do |report|
|
142
|
+
report.add_metadata(:model_context_protocol, server_context)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
configuration.instrumentation_callback = do |data|
|
147
|
+
puts "Got instrumentation data #{data.inspect}"
|
148
|
+
end
|
149
|
+
|
150
|
+
server = ModelContextProtocol::Server.new(
|
151
|
+
# ... all other options
|
152
|
+
configuration:,
|
153
|
+
)
|
154
|
+
```
|
155
|
+
|
156
|
+
### Server Context and Configuration Block Data
|
157
|
+
|
158
|
+
#### `server_context`
|
159
|
+
|
160
|
+
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.
|
161
|
+
|
162
|
+
**Type:**
|
163
|
+
```ruby
|
164
|
+
server_context: { [String, Symbol] => Any }
|
165
|
+
```
|
166
|
+
|
167
|
+
**Example:**
|
168
|
+
```ruby
|
169
|
+
server = ModelContextProtocol::Server.new(
|
170
|
+
name: "my_server",
|
171
|
+
server_context: { user_id: current_user.id, request_id: request.uuid }
|
172
|
+
)
|
173
|
+
```
|
174
|
+
|
175
|
+
This hash is then passed as the `server_context` argument to tool and prompt calls, and is included in exception and instrumentation callbacks.
|
176
|
+
|
177
|
+
#### Configuration Block Data
|
178
|
+
|
179
|
+
##### Exception Reporter
|
180
|
+
|
181
|
+
The exception reporter receives:
|
182
|
+
|
183
|
+
- `exception`: The Ruby exception object that was raised
|
184
|
+
- `server_context`: The context hash provided to the server
|
185
|
+
|
186
|
+
**Signature:**
|
187
|
+
```ruby
|
188
|
+
exception_reporter = ->(exception, server_context) { ... }
|
189
|
+
```
|
190
|
+
|
191
|
+
##### Instrumentation Callback
|
192
|
+
|
193
|
+
The instrumentation callback receives a hash with the following possible keys:
|
194
|
+
|
195
|
+
- `method`: (String) The protocol method called (e.g., "ping", "tools/list")
|
196
|
+
- `tool_name`: (String, optional) The name of the tool called
|
197
|
+
- `prompt_name`: (String, optional) The name of the prompt called
|
198
|
+
- `resource_uri`: (String, optional) The URI of the resource called
|
199
|
+
- `error`: (String, optional) Error code if a lookup failed
|
200
|
+
- `duration`: (Float) Duration of the call in seconds
|
201
|
+
|
202
|
+
**Type:**
|
203
|
+
```ruby
|
204
|
+
instrumentation_callback = ->(data) { ... }
|
205
|
+
# where data is a Hash with keys as described above
|
206
|
+
```
|
207
|
+
|
208
|
+
**Example:**
|
209
|
+
```ruby
|
210
|
+
config.instrumentation_callback = ->(data) do
|
211
|
+
puts "Instrumentation: #{data.inspect}"
|
212
|
+
end
|
213
|
+
```
|
214
|
+
|
215
|
+
### Server Protocol Version
|
216
|
+
|
217
|
+
The server's protocol version can be overridden using the `protocol_version` class method:
|
218
|
+
|
219
|
+
```ruby
|
220
|
+
ModelContextProtocol::Server.protocol_version = "2024-11-05"
|
221
|
+
```
|
222
|
+
|
223
|
+
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`:
|
224
|
+
|
225
|
+
```ruby
|
226
|
+
ModelContextProtocol::Server.protocol_version = nil
|
227
|
+
```
|
228
|
+
|
229
|
+
Be sure to check the [MCP spec](https://spec.modelcontextprotocol.io/specification/2024-11-05/) for the protocol version to understand the supported features for the version being set.
|
230
|
+
|
231
|
+
### Exception Reporting
|
232
|
+
|
233
|
+
The exception reporter receives two arguments:
|
234
|
+
|
235
|
+
- `exception`: The Ruby exception object that was raised
|
236
|
+
- `server_context`: A hash containing contextual information about where the error occurred
|
237
|
+
|
238
|
+
The server_context hash includes:
|
239
|
+
|
240
|
+
- For tool calls: `{ tool_name: "name", arguments: { ... } }`
|
241
|
+
- For general request handling: `{ request: { ... } }`
|
242
|
+
|
243
|
+
When an exception occurs:
|
244
|
+
|
245
|
+
1. The exception is reported via the configured reporter
|
246
|
+
2. For tool calls, a generic error response is returned to the client: `{ error: "Internal error occurred", isError: true }`
|
247
|
+
3. For other requests, the exception is re-raised after reporting
|
248
|
+
|
249
|
+
If no exception reporter is configured, a default no-op reporter is used that silently ignores exceptions.
|
250
|
+
|
251
|
+
## Tools
|
252
|
+
|
253
|
+
MCP spec includes [Tools](https://modelcontextprotocol.io/docs/concepts/tools) which provide functionality to LLM apps.
|
254
|
+
|
255
|
+
This gem provides a `ModelContextProtocol::Tool` class that can be used to create tools in two ways:
|
256
|
+
|
257
|
+
1. As a class definition:
|
258
|
+
|
259
|
+
```ruby
|
260
|
+
class MyTool < ModelContextProtocol::Tool
|
261
|
+
description "This tool performs specific functionality..."
|
262
|
+
input_schema(
|
263
|
+
properties: {
|
264
|
+
message: { type: "string" },
|
265
|
+
},
|
266
|
+
required: ["message"]
|
267
|
+
)
|
268
|
+
annotations(
|
269
|
+
title: "My Tool",
|
270
|
+
read_only_hint: true,
|
271
|
+
destructive_hint: false,
|
272
|
+
idempotent_hint: true,
|
273
|
+
open_world_hint: false
|
274
|
+
)
|
275
|
+
|
276
|
+
def self.call(message:, server_context:)
|
277
|
+
Tool::Response.new([{ type: "text", text: "OK" }])
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
tool = MyTool
|
282
|
+
```
|
283
|
+
|
284
|
+
2. By using the `ModelContextProtocol::Tool.define` method with a block:
|
285
|
+
|
286
|
+
```ruby
|
287
|
+
tool = ModelContextProtocol::Tool.define(
|
288
|
+
name: "my_tool",
|
289
|
+
description: "This tool performs specific functionality...",
|
290
|
+
annotations: {
|
291
|
+
title: "My Tool",
|
292
|
+
read_only_hint: true
|
293
|
+
}
|
294
|
+
) do |args, server_context|
|
295
|
+
Tool::Response.new([{ type: "text", text: "OK" }])
|
296
|
+
end
|
297
|
+
```
|
298
|
+
|
299
|
+
The server_context parameter is the server_context passed into the server and can be used to pass per request information,
|
300
|
+
e.g. around authentication state.
|
301
|
+
|
302
|
+
### Tool Annotations
|
303
|
+
|
304
|
+
Tools can include annotations that provide additional metadata about their behavior. The following annotations are supported:
|
305
|
+
|
306
|
+
- `title`: A human-readable title for the tool
|
307
|
+
- `read_only_hint`: Indicates if the tool only reads data (doesn't modify state)
|
308
|
+
- `destructive_hint`: Indicates if the tool performs destructive operations
|
309
|
+
- `idempotent_hint`: Indicates if the tool's operations are idempotent
|
310
|
+
- `open_world_hint`: Indicates if the tool operates in an open world context
|
311
|
+
|
312
|
+
Annotations can be set either through the class definition using the `annotations` class method or when defining a tool using the `define` method.
|
313
|
+
|
314
|
+
## Prompts
|
315
|
+
|
316
|
+
MCP spec includes [Prompts](https://modelcontextprotocol.io/docs/concepts/prompts), which enable servers to define reusable prompt templates and workflows that clients can easily surface to users and LLMs.
|
317
|
+
|
318
|
+
The `ModelContextProtocol::Prompt` class provides two ways to create prompts:
|
319
|
+
|
320
|
+
1. As a class definition with metadata:
|
321
|
+
|
322
|
+
```ruby
|
323
|
+
class MyPrompt < ModelContextProtocol::Prompt
|
324
|
+
prompt_name "my_prompt" # Optional - defaults to underscored class name
|
325
|
+
description "This prompt performs specific functionality..."
|
326
|
+
arguments [
|
327
|
+
Prompt::Argument.new(
|
328
|
+
name: "message",
|
329
|
+
description: "Input message",
|
330
|
+
required: true
|
331
|
+
)
|
332
|
+
]
|
333
|
+
|
334
|
+
class << self
|
335
|
+
def template(args, server_context:)
|
336
|
+
Prompt::Result.new(
|
337
|
+
description: "Response description",
|
338
|
+
messages: [
|
339
|
+
Prompt::Message.new(
|
340
|
+
role: "user",
|
341
|
+
content: Content::Text.new("User message")
|
342
|
+
),
|
343
|
+
Prompt::Message.new(
|
344
|
+
role: "assistant",
|
345
|
+
content: Content::Text.new(args["message"])
|
346
|
+
)
|
347
|
+
]
|
348
|
+
)
|
349
|
+
end
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
prompt = MyPrompt
|
354
|
+
```
|
355
|
+
|
356
|
+
2. Using the `ModelContextProtocol::Prompt.define` method:
|
357
|
+
|
358
|
+
```ruby
|
359
|
+
prompt = ModelContextProtocol::Prompt.define(
|
360
|
+
name: "my_prompt",
|
361
|
+
description: "This prompt performs specific functionality...",
|
362
|
+
arguments: [
|
363
|
+
Prompt::Argument.new(
|
364
|
+
name: "message",
|
365
|
+
description: "Input message",
|
366
|
+
required: true
|
367
|
+
)
|
368
|
+
]
|
369
|
+
) do |args, server_context:|
|
370
|
+
Prompt::Result.new(
|
371
|
+
description: "Response description",
|
372
|
+
messages: [
|
373
|
+
Prompt::Message.new(
|
374
|
+
role: "user",
|
375
|
+
content: Content::Text.new("User message")
|
376
|
+
),
|
377
|
+
Prompt::Message.new(
|
378
|
+
role: "assistant",
|
379
|
+
content: Content::Text.new(args["message"])
|
380
|
+
)
|
381
|
+
]
|
382
|
+
)
|
383
|
+
end
|
384
|
+
```
|
385
|
+
|
386
|
+
The server_context parameter is the server_context passed into the server and can be used to pass per request information,
|
387
|
+
e.g. around authentication state or user preferences.
|
388
|
+
|
389
|
+
### Key Components
|
390
|
+
|
391
|
+
- `Prompt::Argument` - Defines input parameters for the prompt template
|
392
|
+
- `Prompt::Message` - Represents a message in the conversation with a role and content
|
393
|
+
- `Prompt::Result` - The output of a prompt template containing description and messages
|
394
|
+
- `Content::Text` - Text content for messages
|
395
|
+
|
396
|
+
### Usage
|
397
|
+
|
398
|
+
Register prompts with the MCP server:
|
399
|
+
|
400
|
+
```ruby
|
401
|
+
server = ModelContextProtocol::Server.new(
|
402
|
+
name: "my_server",
|
403
|
+
prompts: [MyPrompt],
|
404
|
+
server_context: { user_id: current_user.id },
|
405
|
+
)
|
406
|
+
```
|
407
|
+
|
408
|
+
The server will handle prompt listing and execution through the MCP protocol methods:
|
409
|
+
|
410
|
+
- `prompts/list` - Lists all registered prompts and their schemas
|
411
|
+
- `prompts/get` - Retrieves and executes a specific prompt with arguments
|
412
|
+
|
413
|
+
### Instrumentation
|
414
|
+
|
415
|
+
The server allows registering a callback to receive information about instrumentation.
|
416
|
+
To register a handler pass a proc/lambda to as `instrumentation_callback` into the server constructor.
|
417
|
+
|
418
|
+
```ruby
|
419
|
+
ModelContextProtocol.configure do |config|
|
420
|
+
config.instrumentation_callback = do |data|
|
421
|
+
puts "Got instrumentation data #{data.inspect}"
|
422
|
+
end
|
423
|
+
end
|
424
|
+
```
|
425
|
+
|
426
|
+
The data contains the following keys:
|
427
|
+
`method`: the metod called, e.g. `ping`, `tools/list`, `tools/call` etc
|
428
|
+
`tool_name`: the name of the tool called
|
429
|
+
`prompt_name`: the name of the prompt called
|
430
|
+
`resource_uri`: the uri of the resource called
|
431
|
+
`error`: if looking up tools/prompts etc failed, e.g. `tool_not_found`
|
432
|
+
`duration`: the duration of the call in seconds
|
433
|
+
|
434
|
+
`tool_name`, `prompt_name` and `resource_uri` are only populated if a matching handler is registered.
|
435
|
+
This is to avoid potential issues with metric cardinality
|
436
|
+
|
437
|
+
## Resources
|
438
|
+
|
439
|
+
MCP spec includes [Resources](https://modelcontextprotocol.io/docs/concepts/resources)
|
440
|
+
|
441
|
+
The `ModelContextProtocol::Resource` class provides a way to register resources with the server.
|
442
|
+
|
443
|
+
```ruby
|
444
|
+
resource = ModelContextProtocol::Resource.new(
|
445
|
+
uri: "example.com/my_resource",
|
446
|
+
mime_type: "text/plain",
|
447
|
+
text: "Lorem ipsum dolor sit amet"
|
448
|
+
)
|
449
|
+
|
450
|
+
server = ModelContextProtocol::Server.new(
|
451
|
+
name: "my_server",
|
452
|
+
resources: [resource],
|
453
|
+
)
|
454
|
+
```
|
455
|
+
|
456
|
+
The server must register a handler for the `resources/read` method to retrieve a resource dynamically.
|
457
|
+
|
458
|
+
```ruby
|
459
|
+
server.resources_read_handler do |params|
|
460
|
+
[{
|
461
|
+
uri: params[:uri],
|
462
|
+
mimeType: "text/plain",
|
463
|
+
text: "Hello, world!",
|
464
|
+
}]
|
465
|
+
end
|
466
|
+
|
467
|
+
```
|
468
|
+
|
469
|
+
otherwise 'resources/read' requests will be a no-op.
|
470
|
+
|
471
|
+
## Releases
|
472
|
+
|
473
|
+
TODO
|
data/Rakefile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bundler/gem_tasks"
|
4
|
+
require "rake/testtask"
|
5
|
+
|
6
|
+
Rake::TestTask.new(:test) do |t|
|
7
|
+
t.ruby_opts = ["-W0", "-W:deprecated"]
|
8
|
+
t.libs << "test"
|
9
|
+
t.libs << "lib"
|
10
|
+
t.test_files = FileList["test/**/*_test.rb"]
|
11
|
+
end
|
12
|
+
|
13
|
+
require "rubocop/rake_task"
|
14
|
+
|
15
|
+
RuboCop::RakeTask.new
|
16
|
+
|
17
|
+
task default: [:test, :rubocop]
|
data/bin/console
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "bundler/setup"
|
5
|
+
require "model_context_protocol"
|
6
|
+
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
9
|
+
|
10
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
11
|
+
# require "pry"
|
12
|
+
# Pry.start
|
13
|
+
|
14
|
+
require "irb"
|
15
|
+
IRB.start(__FILE__)
|
data/bin/rake
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'rake' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
require "pathname"
|
12
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path(
|
13
|
+
"../../Gemfile",
|
14
|
+
Pathname.new(__FILE__).realpath,
|
15
|
+
)
|
16
|
+
|
17
|
+
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
18
|
+
|
19
|
+
if File.file?(bundle_binstub)
|
20
|
+
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
21
|
+
load(bundle_binstub)
|
22
|
+
else
|
23
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
24
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
require "rubygems"
|
29
|
+
require "bundler/setup"
|
30
|
+
|
31
|
+
load Gem.bin_path("rake", "rake")
|
data/bin/setup
ADDED
data/dev.yml
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
name: mcp-ruby
|
2
|
+
|
3
|
+
type: ruby
|
4
|
+
|
5
|
+
up:
|
6
|
+
- ruby
|
7
|
+
- bundler
|
8
|
+
|
9
|
+
commands:
|
10
|
+
console:
|
11
|
+
desc: Open console with the gem loaded
|
12
|
+
run: bin/console
|
13
|
+
build:
|
14
|
+
desc: Build the gem using rake build
|
15
|
+
run: bin/rake build
|
16
|
+
test:
|
17
|
+
desc: Run tests
|
18
|
+
syntax:
|
19
|
+
argument: file
|
20
|
+
optional: args...
|
21
|
+
run: |
|
22
|
+
if [[ $# -eq 0 ]]; then
|
23
|
+
bin/rake test
|
24
|
+
else
|
25
|
+
bin/rake -I test "$@"
|
26
|
+
fi
|
27
|
+
style:
|
28
|
+
desc: Run rubocop
|
29
|
+
aliases: [rubocop, lint]
|
30
|
+
run: bin/rubocop
|
31
|
+
|
@@ -0,0 +1,94 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
$LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
|
5
|
+
require "model_context_protocol"
|
6
|
+
require "model_context_protocol/transports/stdio"
|
7
|
+
|
8
|
+
# Create a simple tool
|
9
|
+
class ExampleTool < MCP::Tool
|
10
|
+
description "A simple example tool that adds two numbers"
|
11
|
+
input_schema(
|
12
|
+
properties: {
|
13
|
+
a: { type: "number" },
|
14
|
+
b: { type: "number" },
|
15
|
+
},
|
16
|
+
required: ["a", "b"],
|
17
|
+
)
|
18
|
+
|
19
|
+
class << self
|
20
|
+
def call(a:, b:)
|
21
|
+
MCP::Tool::Response.new([{
|
22
|
+
type: "text",
|
23
|
+
text: "The sum of #{a} and #{b} is #{a + b}",
|
24
|
+
}])
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Create a simple prompt
|
30
|
+
class ExamplePrompt < MCP::Prompt
|
31
|
+
description "A simple example prompt that echoes back its arguments"
|
32
|
+
arguments [
|
33
|
+
MCP::Prompt::Argument.new(
|
34
|
+
name: "message",
|
35
|
+
description: "The message to echo back",
|
36
|
+
required: true,
|
37
|
+
),
|
38
|
+
]
|
39
|
+
|
40
|
+
class << self
|
41
|
+
def template(args, server_context:)
|
42
|
+
MCP::Prompt::Result.new(
|
43
|
+
messages: [
|
44
|
+
MCP::Prompt::Message.new(
|
45
|
+
role: "user",
|
46
|
+
content: MCP::Content::Text.new(args[:message]),
|
47
|
+
),
|
48
|
+
],
|
49
|
+
)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Set up the server
|
55
|
+
server = MCP::Server.new(
|
56
|
+
name: "example_server",
|
57
|
+
tools: [ExampleTool],
|
58
|
+
prompts: [ExamplePrompt],
|
59
|
+
resources: [
|
60
|
+
MCP::Resource.new(
|
61
|
+
uri: "test_resource",
|
62
|
+
name: "Test resource",
|
63
|
+
description: "Test resource that echoes back the uri as its content",
|
64
|
+
mime_type: "text/plain",
|
65
|
+
),
|
66
|
+
],
|
67
|
+
)
|
68
|
+
|
69
|
+
server.define_tool(
|
70
|
+
name: "echo",
|
71
|
+
description: "A simple example tool that echoes back its arguments",
|
72
|
+
input_schema: { properties: { message: { type: "string" } }, required: ["message"] },
|
73
|
+
) do |message:|
|
74
|
+
MCP::Tool::Response.new(
|
75
|
+
[
|
76
|
+
{
|
77
|
+
type: "text",
|
78
|
+
text: "Hello from echo tool! Message: #{message}",
|
79
|
+
},
|
80
|
+
],
|
81
|
+
)
|
82
|
+
end
|
83
|
+
|
84
|
+
server.resources_read_handler do |params|
|
85
|
+
[{
|
86
|
+
uri: params[:uri],
|
87
|
+
mimeType: "text/plain",
|
88
|
+
text: "Hello, world!",
|
89
|
+
}]
|
90
|
+
end
|
91
|
+
|
92
|
+
# Create and start the transport
|
93
|
+
transport = MCP::Transports::StdioTransport.new(server)
|
94
|
+
transport.open
|
data/lib/mcp-ruby.rb
ADDED