model-context-protocol-rb 0.3.1 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -1
- data/README.md +105 -56
- data/lib/model_context_protocol/server/completion.rb +45 -0
- data/lib/model_context_protocol/server/prompt.rb +65 -10
- data/lib/model_context_protocol/server/registry.rb +22 -0
- data/lib/model_context_protocol/server/resource.rb +29 -6
- data/lib/model_context_protocol/server/resource_template.rb +93 -0
- data/lib/model_context_protocol/server/tool.rb +22 -4
- data/lib/model_context_protocol/server.rb +36 -2
- data/lib/model_context_protocol/version.rb +1 -1
- data/lib/model_context_protocol.rb +1 -1
- data/tasks/templates/dev.erb +4 -0
- metadata +18 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 99ee11ebae0c9c984a15954b386e89489f6715a1d371b288ece0438d066436d7
|
4
|
+
data.tar.gz: cdf41e8742941e9a0a41c714127d02f5a189d901a95a35e6f61a16f1f7b8a704
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: aad64bd6e42b2ecea2b742bb23b902ea1bfae3e77cffc5009b0a83712df7efac417b680bb460649bb73fff31d2d554d33a17d6fe96adb35f3cc9d0addbbcf9be
|
7
|
+
data.tar.gz: 6fa834a1c2bf4494748474dc26a29bede650b1a1913c7c543a7c195fd24fe99eda5f1610c40a63067f454b50f669b1fb47030e106fa6ba80cbde522bccfb9518
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,11 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.3.2] - 2025-05-10
|
4
|
+
|
5
|
+
- Added resource template support.
|
6
|
+
- Added completion support for prompts and resources.
|
7
|
+
- Improved metadata definition for prompts, resources, and tools using simple DSL.
|
8
|
+
|
3
9
|
## [0.3.1] - 2025-04-04
|
4
10
|
|
5
11
|
- Added support for environment variables to MCP servers (thanks @hmk):
|
@@ -31,7 +37,8 @@
|
|
31
37
|
|
32
38
|
- Initial release
|
33
39
|
|
34
|
-
[Unreleased]: https://github.com/dickdavis/model-context-protocol-rb/compare/v0.3.
|
40
|
+
[Unreleased]: https://github.com/dickdavis/model-context-protocol-rb/compare/v0.3.2...HEAD
|
41
|
+
[0.3.1]: https://github.com/dickdavis/model-context-protocol-rb/compare/v0.3.1...v0.3.2
|
35
42
|
[0.3.1]: https://github.com/dickdavis/model-context-protocol-rb/compare/v0.3.0...v0.3.1
|
36
43
|
[0.3.0]: https://github.com/dickdavis/model-context-protocol-rb/compare/v0.2.0...v0.3.0
|
37
44
|
[0.2.0]: https://github.com/dickdavis/model-context-protocol-rb/compare/v0.1.0...v0.2.0
|
data/README.md
CHANGED
@@ -8,13 +8,10 @@ You are welcome to contribute.
|
|
8
8
|
|
9
9
|
TODO's:
|
10
10
|
|
11
|
-
* [Completion](https://spec.modelcontextprotocol.io/specification/2024-11-05/server/utilities/completion/)
|
12
|
-
* [Logging](https://spec.modelcontextprotocol.io/specification/2024-11-05/server/utilities/logging/)
|
13
11
|
* [Pagination](https://spec.modelcontextprotocol.io/specification/2024-11-05/server/utilities/pagination/)
|
14
12
|
* [Prompt list changed notifications](https://spec.modelcontextprotocol.io/specification/2024-11-05/server/prompts/#list-changed-notification)
|
15
13
|
* [Resource list changed notifications](https://spec.modelcontextprotocol.io/specification/2024-11-05/server/resources/#list-changed-notification)
|
16
14
|
* [Resource subscriptions](https://spec.modelcontextprotocol.io/specification/2024-11-05/server/resources/#subscriptions)
|
17
|
-
* [Resource templates](https://spec.modelcontextprotocol.io/specification/2024-11-05/server/resources/#resource-templates)
|
18
15
|
* [Tool list changed notifications](https://spec.modelcontextprotocol.io/specification/2024-11-05/server/tools/#list-changed-notification)
|
19
16
|
|
20
17
|
## Usage
|
@@ -27,7 +24,7 @@ require 'model_context_protocol'
|
|
27
24
|
|
28
25
|
### Building an MCP Server
|
29
26
|
|
30
|
-
Build a simple MCP server by registering your prompts, resources, and tools. Then, configure and run the server.
|
27
|
+
Build a simple MCP server by registering your prompts, resources, resource templates, and tools. Then, configure and run the server.
|
31
28
|
|
32
29
|
```ruby
|
33
30
|
server = ModelContextProtocol::Server.new do |config|
|
@@ -51,6 +48,10 @@ server = ModelContextProtocol::Server.new do |config|
|
|
51
48
|
register TestResource
|
52
49
|
end
|
53
50
|
|
51
|
+
resource_templates do
|
52
|
+
register TestResourceTemplate
|
53
|
+
end
|
54
|
+
|
54
55
|
tools list_changed: true do
|
55
56
|
register TestTool
|
56
57
|
end
|
@@ -62,10 +63,12 @@ server.start
|
|
62
63
|
|
63
64
|
Messages from the MCP client will be routed to the appropriate custom handler. This SDK provides several classes that should be used to build your handlers.
|
64
65
|
|
65
|
-
####
|
66
|
+
#### Prompts
|
66
67
|
|
67
68
|
The `ModelContextProtocol::Server::Prompt` base class allows subclasses to define a prompt that the MCP client can use. Define the [appropriate metadata](https://spec.modelcontextprotocol.io/specification/2024-11-05/server/prompts/) in the `with_metadata` block.
|
68
69
|
|
70
|
+
Define any arguments using the `with_argument` block. You can mark an argument as required, and you can optionally provide the class name of a service object that provides completions. See [Completions](#completions) for more information.
|
71
|
+
|
69
72
|
Then implement the `call` method to build your prompt. Use the `respond_with` instance method to ensure your prompt responds with appropriately formatted response data.
|
70
73
|
|
71
74
|
This is an example prompt that returns a properly formatted response:
|
@@ -73,22 +76,21 @@ This is an example prompt that returns a properly formatted response:
|
|
73
76
|
```ruby
|
74
77
|
class TestPrompt < ModelContextProtocol::Server::Prompt
|
75
78
|
with_metadata do
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
}
|
79
|
+
name "test_prompt"
|
80
|
+
description "A test prompt"
|
81
|
+
end
|
82
|
+
|
83
|
+
with_argument do
|
84
|
+
name "message"
|
85
|
+
description "The thing to do"
|
86
|
+
required true
|
87
|
+
completion TestCompletion
|
88
|
+
end
|
89
|
+
|
90
|
+
with_argument do
|
91
|
+
name "other"
|
92
|
+
description "Another thing to do"
|
93
|
+
required false
|
92
94
|
end
|
93
95
|
|
94
96
|
def call
|
@@ -107,7 +109,7 @@ class TestPrompt < ModelContextProtocol::Server::Prompt
|
|
107
109
|
end
|
108
110
|
```
|
109
111
|
|
110
|
-
####
|
112
|
+
#### Resources
|
111
113
|
|
112
114
|
The `ModelContextProtocol::Server::Resource` base class allows subclasses to define a resource that the MCP client can use. Define the [appropriate metadata](https://spec.modelcontextprotocol.io/specification/2024-11-05/server/resources/) in the `with_metadata` block.
|
113
115
|
|
@@ -118,12 +120,10 @@ This is an example resource that returns a text response:
|
|
118
120
|
```ruby
|
119
121
|
class TestResource < ModelContextProtocol::Server::Resource
|
120
122
|
with_metadata do
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
uri: "resource://test-resource"
|
126
|
-
}
|
123
|
+
name "Test Resource"
|
124
|
+
description "A test resource"
|
125
|
+
mime_type "text/plain"
|
126
|
+
uri "resource://test-resource"
|
127
127
|
end
|
128
128
|
|
129
129
|
def call
|
@@ -137,12 +137,10 @@ This is an example resource that returns binary data:
|
|
137
137
|
```ruby
|
138
138
|
class TestBinaryResource < ModelContextProtocol::Server::Resource
|
139
139
|
with_metadata do
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
uri: "resource://project-logo"
|
145
|
-
}
|
140
|
+
name "Project Logo"
|
141
|
+
description "The logo for the project"
|
142
|
+
mime_type "image/jpeg"
|
143
|
+
uri "resource://project-logo"
|
146
144
|
end
|
147
145
|
|
148
146
|
def call
|
@@ -153,7 +151,37 @@ class TestBinaryResource < ModelContextProtocol::Server::Resource
|
|
153
151
|
end
|
154
152
|
```
|
155
153
|
|
156
|
-
####
|
154
|
+
#### Resource Templates
|
155
|
+
|
156
|
+
The `ModelContextProtocol::Server::ResourceTemplate` base class allows subclasses to define a resource template that the MCP client can use. Define the [appropriate metadata](https://modelcontextprotocol.io/specification/2024-11-05/server/resources#resource-templates) in the `with_metadata` block.
|
157
|
+
|
158
|
+
This is an example resource template that provides a completion for a parameter of the URI template:
|
159
|
+
|
160
|
+
```ruby
|
161
|
+
class TestResourceTemplateCompletion < ModelContextProtocol::Server::Completion
|
162
|
+
def call
|
163
|
+
hints = {
|
164
|
+
"name" => ["test-resource", "project-logo"]
|
165
|
+
}
|
166
|
+
values = hints[argument_name].grep(/#{argument_value}/)
|
167
|
+
|
168
|
+
respond_with values:
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
class TestResourceTemplate < ModelContextProtocol::Server::ResourceTemplate
|
173
|
+
with_metadata do
|
174
|
+
name "Test Resource Template"
|
175
|
+
description "A test resource template"
|
176
|
+
mime_type "text/plain"
|
177
|
+
uri_template "resource://{name}" do
|
178
|
+
completion :name, TestResourceTemplateCompletion
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
```
|
183
|
+
|
184
|
+
#### Tools
|
157
185
|
|
158
186
|
The `ModelContextProtocol::Server::Tool` base class allows subclasses to define a tool that the MCP client can use. Define the [appropriate metadata](https://spec.modelcontextprotocol.io/specification/2024-11-05/server/tools/) in the `with_metadata` block.
|
159
187
|
|
@@ -164,19 +192,19 @@ This is an example tool that returns a text response:
|
|
164
192
|
```ruby
|
165
193
|
class TestToolWithTextResponse < ModelContextProtocol::Server::Tool
|
166
194
|
with_metadata do
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
195
|
+
name "double"
|
196
|
+
description "Doubles the provided number"
|
197
|
+
input_schema do
|
198
|
+
{
|
171
199
|
type: "object",
|
172
200
|
properties: {
|
173
201
|
number: {
|
174
|
-
type: "string"
|
202
|
+
type: "string"
|
175
203
|
}
|
176
204
|
},
|
177
205
|
required: ["number"]
|
178
206
|
}
|
179
|
-
|
207
|
+
end
|
180
208
|
end
|
181
209
|
|
182
210
|
def call
|
@@ -192,10 +220,10 @@ This is an example of a tool that returns an image:
|
|
192
220
|
```ruby
|
193
221
|
class TestToolWithImageResponse < ModelContextProtocol::Server::Tool
|
194
222
|
with_metadata do
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
223
|
+
name "custom-chart-generator"
|
224
|
+
description "Generates a chart in various formats"
|
225
|
+
input_schema do
|
226
|
+
{
|
199
227
|
type: "object",
|
200
228
|
properties: {
|
201
229
|
chart_type: {
|
@@ -209,7 +237,7 @@ class TestToolWithImageResponse < ModelContextProtocol::Server::Tool
|
|
209
237
|
},
|
210
238
|
required: ["chart_type", "format"]
|
211
239
|
}
|
212
|
-
|
240
|
+
end
|
213
241
|
end
|
214
242
|
|
215
243
|
def call
|
@@ -236,10 +264,10 @@ If you don't provide a mime type, it will default to `image/png`.
|
|
236
264
|
```ruby
|
237
265
|
class TestToolWithImageResponseDefaultMimeType < ModelContextProtocol::Server::Tool
|
238
266
|
with_metadata do
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
267
|
+
name "other-custom-chart-generator"
|
268
|
+
description "Generates a chart"
|
269
|
+
input_schema do
|
270
|
+
{
|
243
271
|
type: "object",
|
244
272
|
properties: {
|
245
273
|
chart_type: {
|
@@ -249,7 +277,7 @@ class TestToolWithImageResponseDefaultMimeType < ModelContextProtocol::Server::T
|
|
249
277
|
},
|
250
278
|
required: ["chart_type"]
|
251
279
|
}
|
252
|
-
|
280
|
+
end
|
253
281
|
end
|
254
282
|
|
255
283
|
def call
|
@@ -266,10 +294,10 @@ This is an example of a tool that returns a resource response:
|
|
266
294
|
```ruby
|
267
295
|
class TestToolWithResourceResponse < ModelContextProtocol::Server::Tool
|
268
296
|
with_metadata do
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
297
|
+
name "document-finder"
|
298
|
+
description "Finds a the document with the given title"
|
299
|
+
input_schema do
|
300
|
+
{
|
273
301
|
type: "object",
|
274
302
|
properties: {
|
275
303
|
title: {
|
@@ -279,7 +307,7 @@ class TestToolWithResourceResponse < ModelContextProtocol::Server::Tool
|
|
279
307
|
},
|
280
308
|
required: ["title"]
|
281
309
|
}
|
282
|
-
|
310
|
+
end
|
283
311
|
end
|
284
312
|
|
285
313
|
def call
|
@@ -291,6 +319,27 @@ class TestToolWithResourceResponse < ModelContextProtocol::Server::Tool
|
|
291
319
|
end
|
292
320
|
```
|
293
321
|
|
322
|
+
### Completions
|
323
|
+
|
324
|
+
The `ModelContextProtocol::Server::Completion` base class allows subclasses to define a completion that the MCP client can use to obtain hints or suggestions for arguments to prompts and resources.
|
325
|
+
|
326
|
+
implement the `call` method to build your completion. Use the `respond_with` instance method to ensure your completion responds with appropriately formatted response data.
|
327
|
+
|
328
|
+
This is an example completion that returns an array of values in the response:
|
329
|
+
|
330
|
+
```ruby
|
331
|
+
class TestCompletion < ModelContextProtocol::Server::Completion
|
332
|
+
def call
|
333
|
+
hints = {
|
334
|
+
"message" => ["hello", "world", "foo", "bar"]
|
335
|
+
}
|
336
|
+
values = hints[argument_name].grep(/#{argument_value}/)
|
337
|
+
|
338
|
+
respond_with values:
|
339
|
+
end
|
340
|
+
end
|
341
|
+
```
|
342
|
+
|
294
343
|
## Installation
|
295
344
|
|
296
345
|
Add this line to your application's Gemfile:
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module ModelContextProtocol
|
2
|
+
class Server::Completion
|
3
|
+
attr_reader :argument_name, :argument_value
|
4
|
+
|
5
|
+
def initialize(argument_name, argument_value)
|
6
|
+
@argument_name = argument_name
|
7
|
+
@argument_value = argument_value
|
8
|
+
end
|
9
|
+
|
10
|
+
def call
|
11
|
+
raise NotImplementedError, "Subclasses must implement the call method"
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.call(...)
|
15
|
+
new(...).call
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
Response = Data.define(:values, :total, :hasMore) do
|
21
|
+
def serialized
|
22
|
+
{completion: {values:, total:, hasMore:}}
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def respond_with(values:)
|
27
|
+
values_to_return = values.take(100)
|
28
|
+
total = values.size
|
29
|
+
has_more = values_to_return.size != total
|
30
|
+
Response[values:, total:, hasMore: has_more]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class Server::NullCompletion
|
35
|
+
Response = Data.define(:values, :total, :hasMore) do
|
36
|
+
def serialized
|
37
|
+
{completion: {values:, total:, hasMore:}}
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.call(_argument_name, _argument_value)
|
42
|
+
Response[values: [], total: 0, hasMore: false]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -1,10 +1,9 @@
|
|
1
1
|
module ModelContextProtocol
|
2
2
|
class Server::Prompt
|
3
|
-
attr_reader :params
|
3
|
+
attr_reader :params
|
4
4
|
|
5
5
|
def initialize(params)
|
6
6
|
validate!(params)
|
7
|
-
@description = self.class.description
|
8
7
|
@params = params
|
9
8
|
end
|
10
9
|
|
@@ -12,15 +11,15 @@ module ModelContextProtocol
|
|
12
11
|
raise NotImplementedError, "Subclasses must implement the call method"
|
13
12
|
end
|
14
13
|
|
15
|
-
Response = Data.define(:messages, :
|
14
|
+
Response = Data.define(:messages, :description) do
|
16
15
|
def serialized
|
17
|
-
{description
|
16
|
+
{description:, messages:}
|
18
17
|
end
|
19
18
|
end
|
20
19
|
private_constant :Response
|
21
20
|
|
22
21
|
private def respond_with(messages:)
|
23
|
-
Response[messages:,
|
22
|
+
Response[messages:, description: self.class.description]
|
24
23
|
end
|
25
24
|
|
26
25
|
private def validate!(params = {})
|
@@ -45,17 +44,33 @@ module ModelContextProtocol
|
|
45
44
|
attr_reader :name, :description, :arguments
|
46
45
|
|
47
46
|
def with_metadata(&block)
|
48
|
-
|
47
|
+
@arguments ||= []
|
49
48
|
|
50
|
-
|
51
|
-
|
52
|
-
|
49
|
+
metadata_dsl = MetadataDSL.new
|
50
|
+
metadata_dsl.instance_eval(&block)
|
51
|
+
|
52
|
+
@name = metadata_dsl.name
|
53
|
+
@description = metadata_dsl.description
|
54
|
+
end
|
55
|
+
|
56
|
+
def with_argument(&block)
|
57
|
+
@arguments ||= []
|
58
|
+
|
59
|
+
argument_dsl = ArgumentDSL.new
|
60
|
+
argument_dsl.instance_eval(&block)
|
61
|
+
|
62
|
+
@arguments << {
|
63
|
+
name: argument_dsl.name,
|
64
|
+
description: argument_dsl.description,
|
65
|
+
required: argument_dsl.required,
|
66
|
+
completion: argument_dsl.completion
|
67
|
+
}
|
53
68
|
end
|
54
69
|
|
55
70
|
def inherited(subclass)
|
56
71
|
subclass.instance_variable_set(:@name, @name)
|
57
72
|
subclass.instance_variable_set(:@description, @description)
|
58
|
-
subclass.instance_variable_set(:@arguments, @arguments)
|
73
|
+
subclass.instance_variable_set(:@arguments, @arguments&.dup)
|
59
74
|
end
|
60
75
|
|
61
76
|
def call(params)
|
@@ -67,6 +82,46 @@ module ModelContextProtocol
|
|
67
82
|
def metadata
|
68
83
|
{name: @name, description: @description, arguments: @arguments}
|
69
84
|
end
|
85
|
+
|
86
|
+
def complete_for(arg_name, value)
|
87
|
+
arg = @arguments&.find { |a| a[:name] == arg_name.to_s }
|
88
|
+
completion = (arg && arg[:completion]) ? arg[:completion] : ModelContextProtocol::Server::NullCompletion
|
89
|
+
completion.call(arg_name.to_s, value)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
class MetadataDSL
|
94
|
+
def name(value = nil)
|
95
|
+
@name = value if value
|
96
|
+
@name
|
97
|
+
end
|
98
|
+
|
99
|
+
def description(value = nil)
|
100
|
+
@description = value if value
|
101
|
+
@description
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
class ArgumentDSL
|
106
|
+
def name(value = nil)
|
107
|
+
@name = value if value
|
108
|
+
@name
|
109
|
+
end
|
110
|
+
|
111
|
+
def description(value = nil)
|
112
|
+
@description = value if value
|
113
|
+
@description
|
114
|
+
end
|
115
|
+
|
116
|
+
def required(value = nil)
|
117
|
+
@required = value unless value.nil?
|
118
|
+
@required
|
119
|
+
end
|
120
|
+
|
121
|
+
def completion(klass = nil)
|
122
|
+
@completion = klass unless klass.nil?
|
123
|
+
@completion
|
124
|
+
end
|
70
125
|
end
|
71
126
|
end
|
72
127
|
end
|
@@ -12,6 +12,7 @@ module ModelContextProtocol
|
|
12
12
|
def initialize
|
13
13
|
@prompts = []
|
14
14
|
@resources = []
|
15
|
+
@resource_templates = []
|
15
16
|
@tools = []
|
16
17
|
@prompts_options = {}
|
17
18
|
@resources_options = {}
|
@@ -28,6 +29,10 @@ module ModelContextProtocol
|
|
28
29
|
instance_eval(&block) if block
|
29
30
|
end
|
30
31
|
|
32
|
+
def resource_templates(&block)
|
33
|
+
instance_eval(&block) if block
|
34
|
+
end
|
35
|
+
|
31
36
|
def tools(options = {}, &block)
|
32
37
|
@tools_options = options
|
33
38
|
instance_eval(&block) if block
|
@@ -42,6 +47,8 @@ module ModelContextProtocol
|
|
42
47
|
@prompts << entry
|
43
48
|
when ->(ancestors) { ancestors.include?(ModelContextProtocol::Server::Resource) }
|
44
49
|
@resources << entry
|
50
|
+
when ->(ancestors) { ancestors.include?(ModelContextProtocol::Server::ResourceTemplate) }
|
51
|
+
@resource_templates << entry
|
45
52
|
when ->(ancestors) { ancestors.include?(ModelContextProtocol::Server::Tool) }
|
46
53
|
@tools << entry
|
47
54
|
else
|
@@ -58,6 +65,11 @@ module ModelContextProtocol
|
|
58
65
|
entry ? entry[:klass] : nil
|
59
66
|
end
|
60
67
|
|
68
|
+
def find_resource_template(uri)
|
69
|
+
entry = @resource_templates.find { |r| uri == r[:uriTemplate] }
|
70
|
+
entry ? entry[:klass] : nil
|
71
|
+
end
|
72
|
+
|
61
73
|
def find_tool(name)
|
62
74
|
find_by_name(@tools, name)
|
63
75
|
end
|
@@ -70,6 +82,10 @@ module ModelContextProtocol
|
|
70
82
|
ResourcesData[resources: @resources.map { |entry| entry.except(:klass) }]
|
71
83
|
end
|
72
84
|
|
85
|
+
def resource_templates_data
|
86
|
+
ResourceTemplatesData[resource_templates: @resource_templates.map { |entry| entry.except(:klass, :completions) }]
|
87
|
+
end
|
88
|
+
|
73
89
|
def tools_data
|
74
90
|
ToolsData[tools: @tools.map { |entry| entry.except(:klass) }]
|
75
91
|
end
|
@@ -88,6 +104,12 @@ module ModelContextProtocol
|
|
88
104
|
end
|
89
105
|
end
|
90
106
|
|
107
|
+
ResourceTemplatesData = Data.define(:resource_templates) do
|
108
|
+
def serialized
|
109
|
+
{resourceTemplates: resource_templates}
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
91
113
|
ToolsData = Data.define(:tools) do
|
92
114
|
def serialized
|
93
115
|
{tools:}
|
@@ -40,12 +40,13 @@ module ModelContextProtocol
|
|
40
40
|
attr_reader :name, :description, :mime_type, :uri
|
41
41
|
|
42
42
|
def with_metadata(&block)
|
43
|
-
|
43
|
+
metadata_dsl = MetadataDSL.new
|
44
|
+
metadata_dsl.instance_eval(&block)
|
44
45
|
|
45
|
-
@name =
|
46
|
-
@description =
|
47
|
-
@mime_type =
|
48
|
-
@uri =
|
46
|
+
@name = metadata_dsl.name
|
47
|
+
@description = metadata_dsl.description
|
48
|
+
@mime_type = metadata_dsl.mime_type
|
49
|
+
@uri = metadata_dsl.uri
|
49
50
|
end
|
50
51
|
|
51
52
|
def inherited(subclass)
|
@@ -60,7 +61,29 @@ module ModelContextProtocol
|
|
60
61
|
end
|
61
62
|
|
62
63
|
def metadata
|
63
|
-
{name: @name, description: @description,
|
64
|
+
{name: @name, description: @description, mimeType: @mime_type, uri: @uri}
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
class MetadataDSL
|
69
|
+
def name(value = nil)
|
70
|
+
@name = value if value
|
71
|
+
@name
|
72
|
+
end
|
73
|
+
|
74
|
+
def description(value = nil)
|
75
|
+
@description = value if value
|
76
|
+
@description
|
77
|
+
end
|
78
|
+
|
79
|
+
def mime_type(value = nil)
|
80
|
+
@mime_type = value if value
|
81
|
+
@mime_type
|
82
|
+
end
|
83
|
+
|
84
|
+
def uri(value = nil)
|
85
|
+
@uri = value if value
|
86
|
+
@uri
|
64
87
|
end
|
65
88
|
end
|
66
89
|
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
module ModelContextProtocol
|
2
|
+
class Server::ResourceTemplate
|
3
|
+
class << self
|
4
|
+
attr_reader :name, :description, :mime_type, :uri_template, :completions
|
5
|
+
|
6
|
+
def with_metadata(&block)
|
7
|
+
metadata_dsl = MetadataDSL.new
|
8
|
+
metadata_dsl.instance_eval(&block)
|
9
|
+
|
10
|
+
@name = metadata_dsl.name
|
11
|
+
@description = metadata_dsl.description
|
12
|
+
@mime_type = metadata_dsl.mime_type
|
13
|
+
@uri_template = metadata_dsl.uri_template
|
14
|
+
@completions = metadata_dsl.completions
|
15
|
+
end
|
16
|
+
|
17
|
+
def inherited(subclass)
|
18
|
+
subclass.instance_variable_set(:@name, @name)
|
19
|
+
subclass.instance_variable_set(:@description, @description)
|
20
|
+
subclass.instance_variable_set(:@mime_type, @mime_type)
|
21
|
+
subclass.instance_variable_set(:@uri_template, @uri_template)
|
22
|
+
subclass.instance_variable_set(:@completions, @completions&.dup)
|
23
|
+
end
|
24
|
+
|
25
|
+
def complete_for(param_name, value)
|
26
|
+
completion = if @completions && @completions[param_name.to_s]
|
27
|
+
@completions[param_name.to_s]
|
28
|
+
else
|
29
|
+
ModelContextProtocol::Server::NullCompletion
|
30
|
+
end
|
31
|
+
|
32
|
+
completion.call(param_name.to_s, value)
|
33
|
+
end
|
34
|
+
|
35
|
+
def metadata
|
36
|
+
{
|
37
|
+
name: @name,
|
38
|
+
description: @description,
|
39
|
+
mimeType: @mime_type,
|
40
|
+
uriTemplate: @uri_template,
|
41
|
+
completions: @completions&.transform_keys(&:to_s)
|
42
|
+
}
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class MetadataDSL
|
47
|
+
attr_reader :completions
|
48
|
+
|
49
|
+
def initialize
|
50
|
+
@completions = {}
|
51
|
+
end
|
52
|
+
|
53
|
+
def name(value = nil)
|
54
|
+
@name = value if value
|
55
|
+
@name
|
56
|
+
end
|
57
|
+
|
58
|
+
def description(value = nil)
|
59
|
+
@description = value if value
|
60
|
+
@description
|
61
|
+
end
|
62
|
+
|
63
|
+
def mime_type(value = nil)
|
64
|
+
@mime_type = value if value
|
65
|
+
@mime_type
|
66
|
+
end
|
67
|
+
|
68
|
+
def uri_template(value = nil, &block)
|
69
|
+
@uri_template = value if value
|
70
|
+
|
71
|
+
if block_given?
|
72
|
+
completion_dsl = CompletionDSL.new
|
73
|
+
completion_dsl.instance_eval(&block)
|
74
|
+
@completions = completion_dsl.completions
|
75
|
+
end
|
76
|
+
|
77
|
+
@uri_template
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
class CompletionDSL
|
82
|
+
attr_reader :completions
|
83
|
+
|
84
|
+
def initialize
|
85
|
+
@completions = {}
|
86
|
+
end
|
87
|
+
|
88
|
+
def completion(param_name, completion_class)
|
89
|
+
@completions[param_name.to_s] = completion_class
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -76,11 +76,12 @@ module ModelContextProtocol
|
|
76
76
|
attr_reader :name, :description, :input_schema
|
77
77
|
|
78
78
|
def with_metadata(&block)
|
79
|
-
|
79
|
+
metadata_dsl = MetadataDSL.new
|
80
|
+
metadata_dsl.instance_eval(&block)
|
80
81
|
|
81
|
-
@name =
|
82
|
-
@description =
|
83
|
-
@input_schema =
|
82
|
+
@name = metadata_dsl.name
|
83
|
+
@description = metadata_dsl.description
|
84
|
+
@input_schema = metadata_dsl.input_schema
|
84
85
|
end
|
85
86
|
|
86
87
|
def inherited(subclass)
|
@@ -103,5 +104,22 @@ module ModelContextProtocol
|
|
103
104
|
{name: @name, description: @description, inputSchema: @input_schema}
|
104
105
|
end
|
105
106
|
end
|
107
|
+
|
108
|
+
class MetadataDSL
|
109
|
+
def name(value = nil)
|
110
|
+
@name = value if value
|
111
|
+
@name
|
112
|
+
end
|
113
|
+
|
114
|
+
def description(value = nil)
|
115
|
+
@description = value if value
|
116
|
+
@description
|
117
|
+
end
|
118
|
+
|
119
|
+
def input_schema(&block)
|
120
|
+
@input_schema = instance_eval(&block) if block_given?
|
121
|
+
@input_schema
|
122
|
+
end
|
123
|
+
end
|
106
124
|
end
|
107
125
|
end
|
@@ -60,12 +60,45 @@ module ModelContextProtocol
|
|
60
60
|
PingResponse[]
|
61
61
|
end
|
62
62
|
|
63
|
+
router.map("completion/complete") do |message|
|
64
|
+
type = message["params"]["ref"]["type"]
|
65
|
+
|
66
|
+
completion_source = case type
|
67
|
+
when "ref/prompt"
|
68
|
+
name = message["params"]["ref"]["name"]
|
69
|
+
configuration.registry.find_prompt(name)
|
70
|
+
when "ref/resource"
|
71
|
+
uri = message["params"]["ref"]["uri"]
|
72
|
+
configuration.registry.find_resource_template(uri)
|
73
|
+
else
|
74
|
+
raise ModelContextProtocol::Server::ParameterValidationError, "ref/type invalid"
|
75
|
+
end
|
76
|
+
|
77
|
+
arg_name, arg_value = message["params"]["argument"].values_at("name", "value")
|
78
|
+
|
79
|
+
if completion_source
|
80
|
+
completion_source.complete_for(arg_name, arg_value)
|
81
|
+
else
|
82
|
+
ModelContextProtocol::Server::NullCompletion.call(arg_name, arg_value)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
63
86
|
router.map("resources/list") do
|
64
87
|
configuration.registry.resources_data
|
65
88
|
end
|
66
89
|
|
67
90
|
router.map("resources/read") do |message|
|
68
|
-
|
91
|
+
uri = message["params"]["uri"]
|
92
|
+
resource = configuration.registry.find_resource(uri)
|
93
|
+
unless resource
|
94
|
+
raise ModelContextProtocol::Server::ParameterValidationError, "resource not found for #{uri}"
|
95
|
+
end
|
96
|
+
|
97
|
+
resource.call
|
98
|
+
end
|
99
|
+
|
100
|
+
router.map("resources/templates/list") do |message|
|
101
|
+
configuration.registry.resource_templates_data
|
69
102
|
end
|
70
103
|
|
71
104
|
router.map("prompts/list") do
|
@@ -87,6 +120,7 @@ module ModelContextProtocol
|
|
87
120
|
|
88
121
|
def build_capabilities
|
89
122
|
{}.tap do |capabilities|
|
123
|
+
capabilities[:completions] = {}
|
90
124
|
capabilities[:logging] = {} if configuration.logging_enabled?
|
91
125
|
|
92
126
|
registry = configuration.registry
|
@@ -94,7 +128,7 @@ module ModelContextProtocol
|
|
94
128
|
if registry.prompts_options.any? && !registry.instance_variable_get(:@prompts).empty?
|
95
129
|
capabilities[:prompts] = {
|
96
130
|
listChanged: registry.prompts_options[:list_changed]
|
97
|
-
}.compact
|
131
|
+
}.except(:completions).compact
|
98
132
|
end
|
99
133
|
|
100
134
|
if registry.resources_options.any? && !registry.instance_variable_get(:@resources).empty?
|
data/tasks/templates/dev.erb
CHANGED
@@ -19,6 +19,10 @@ server = ModelContextProtocol::Server.new do |config|
|
|
19
19
|
register TestBinaryResource
|
20
20
|
end
|
21
21
|
|
22
|
+
resource_templates do
|
23
|
+
register TestResourceTemplate
|
24
|
+
end
|
25
|
+
|
22
26
|
tools list_changed: true do
|
23
27
|
register TestToolWithTextResponse
|
24
28
|
register TestToolWithImageResponse
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: model-context-protocol-rb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dick Davis
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-05-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: json-schema
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '5.1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: addressable
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.8'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '2.8'
|
27
41
|
description:
|
28
42
|
email:
|
29
43
|
- dick@hey.com
|
@@ -42,10 +56,12 @@ files:
|
|
42
56
|
- Rakefile
|
43
57
|
- lib/model_context_protocol.rb
|
44
58
|
- lib/model_context_protocol/server.rb
|
59
|
+
- lib/model_context_protocol/server/completion.rb
|
45
60
|
- lib/model_context_protocol/server/configuration.rb
|
46
61
|
- lib/model_context_protocol/server/prompt.rb
|
47
62
|
- lib/model_context_protocol/server/registry.rb
|
48
63
|
- lib/model_context_protocol/server/resource.rb
|
64
|
+
- lib/model_context_protocol/server/resource_template.rb
|
49
65
|
- lib/model_context_protocol/server/router.rb
|
50
66
|
- lib/model_context_protocol/server/stdio_transport.rb
|
51
67
|
- lib/model_context_protocol/server/tool.rb
|