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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: aa9e49b895a1502fe4887f7e2089c24b69b980d9ac7c27754f97c52ad1541c7b
4
- data.tar.gz: 92e343e66c69cd1e5ae9f9d8adb83567303a2aa5e48021b1dc964c172128426d
3
+ metadata.gz: 99ee11ebae0c9c984a15954b386e89489f6715a1d371b288ece0438d066436d7
4
+ data.tar.gz: cdf41e8742941e9a0a41c714127d02f5a189d901a95a35e6f61a16f1f7b8a704
5
5
  SHA512:
6
- metadata.gz: bcbfcb95e17f7e0deaa368f7e2b3dc7915be124b4c98b2b86b8e94c446fe18bd7d9e8c7dbfb6e4301edf6b1ad28e491701fec85bbd31dcd2d79d8b91a3f0c8c1
7
- data.tar.gz: 284e545287ecacb83d42c7a5f72dbe9b0f5ab359d50c9d0cfcde1eb2e00d9c330e9cce3b6bcb15e68fb48963e8778888ca3dbdf1fab5ac0a0d27d36f1ca4e6bd
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.1...HEAD
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
- #### ModelContextProtocol::Server::Prompt
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
- name: "Test Prompt",
78
- description: "A test prompt",
79
- arguments: [
80
- {
81
- name: "message",
82
- description: "The thing to do",
83
- required: true
84
- },
85
- {
86
- name: "other",
87
- description: "Another thing to do",
88
- required: false
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
- #### ModelContextProtocol::Server::Resource
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
- name: "Test Resource",
123
- description: "A test resource",
124
- mime_type: "text/plain",
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
- name: "Project Logo",
142
- description: "The logo for the project",
143
- mime_type: "image/jpeg",
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
- #### ModelContextProtocol::Server::Tool
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
- name: "double",
169
- description: "Doubles the provided number",
170
- inputSchema: {
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
- name: "custom-chart-generator",
197
- description: "Generates a chart in various formats",
198
- inputSchema: {
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
- name: "other-custom-chart-generator",
241
- description: "Generates a chart",
242
- inputSchema: {
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
- name: "document-finder",
271
- description: "Finds a the document with the given title",
272
- inputSchema: {
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, :description
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, :prompt) do
14
+ Response = Data.define(:messages, :description) do
16
15
  def serialized
17
- {description: prompt.description, messages:}
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:, prompt: self]
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
- metadata = instance_eval(&block)
47
+ @arguments ||= []
49
48
 
50
- @name = metadata[:name]
51
- @description = metadata[:description]
52
- @arguments = metadata[:arguments]
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
- metadata = instance_eval(&block)
43
+ metadata_dsl = MetadataDSL.new
44
+ metadata_dsl.instance_eval(&block)
44
45
 
45
- @name = metadata[:name]
46
- @description = metadata[:description]
47
- @mime_type = metadata[:mime_type]
48
- @uri = metadata[: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, mime_type: @mime_type, uri: @uri}
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
- metadata = instance_eval(&block)
79
+ metadata_dsl = MetadataDSL.new
80
+ metadata_dsl.instance_eval(&block)
80
81
 
81
- @name = metadata[:name]
82
- @description = metadata[:description]
83
- @input_schema = metadata[:inputSchema]
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
- configuration.registry.find_resource(message["params"]["uri"]).call
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?
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ModelContextProtocol
4
- VERSION = "0.3.1"
4
+ VERSION = "0.3.2"
5
5
  end
@@ -1,4 +1,4 @@
1
- # frozen_string_literal: true
1
+ require "addressable/template"
2
2
 
3
3
  Dir[File.join(__dir__, "model_context_protocol/", "**", "*.rb")].sort.each { |file| require_relative file }
4
4
 
@@ -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.1
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-04-05 00:00:00.000000000 Z
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