model-context-protocol-rb 0.3.0 → 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: 0127f55c18cd5b2886b93b9f939b2b1358b14d2700e1f9194b26e5f43935101f
4
- data.tar.gz: c0ce5f9bc0891e5b20fb3b30acee6c6dee0ebc2d412fac31078839fcf94f0010
3
+ metadata.gz: 99ee11ebae0c9c984a15954b386e89489f6715a1d371b288ece0438d066436d7
4
+ data.tar.gz: cdf41e8742941e9a0a41c714127d02f5a189d901a95a35e6f61a16f1f7b8a704
5
5
  SHA512:
6
- metadata.gz: f17f3371c5c07b264b2a495fbc8b3e47fc8d18993cc3bd64f9fe91f4475720dde9ec536f266af1f22e7c7eb30ffd0bc4e5680f0a53f8a39c08d2eff034206e9f
7
- data.tar.gz: 564433c1386b063560c17502b7346ae7db2cc523c47ab4ecd62fb986c51fba178432331a4ceed699ec9469c8d63e719acddce669c03d68969b8fc7086a85e640
6
+ metadata.gz: aad64bd6e42b2ecea2b742bb23b902ea1bfae3e77cffc5009b0a83712df7efac417b680bb460649bb73fff31d2d554d33a17d6fe96adb35f3cc9d0addbbcf9be
7
+ data.tar.gz: 6fa834a1c2bf4494748474dc26a29bede650b1a1913c7c543a7c195fd24fe99eda5f1610c40a63067f454b50f669b1fb47030e106fa6ba80cbde522bccfb9518
data/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
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
+
9
+ ## [0.3.1] - 2025-04-04
10
+
11
+ - Added support for environment variables to MCP servers (thanks @hmk):
12
+ - `require_environment_variable` method to specify required environment variables
13
+ - `set_environment_variable` method to programmatically set environment variables
14
+ - Environment variables accessible within tool/prompt/resource handlers
15
+ - Added `respond_with` helper methods to simplify response creation:
16
+ - For tools: text, image, resource, and error responses
17
+ - For prompts: formatted message responses
18
+ - For resources: text and binary responses
19
+ - Improved development tooling:
20
+ - Generated executable now loads all test classes
21
+ - Fixed test support classes for better compatibility with MCP inspector
22
+ - Organized test tools, prompts, and resources in dedicated directories
23
+
3
24
  ## [0.3.0] - 2025-03-11
4
25
 
5
26
  - (Breaking) Replaced router initialization in favor of registry initialization during server configuration. The server now relies on the registry for auto-discovery of prompts, resources, and tools; this requires the use of SDK-provided builders to facilitate.
@@ -16,7 +37,9 @@
16
37
 
17
38
  - Initial release
18
39
 
19
- [Unreleased]: https://github.com/dickdavis/model-context-protocol-rb/compare/v0.3.0...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
42
+ [0.3.1]: https://github.com/dickdavis/model-context-protocol-rb/compare/v0.3.0...v0.3.1
20
43
  [0.3.0]: https://github.com/dickdavis/model-context-protocol-rb/compare/v0.2.0...v0.3.0
21
44
  [0.2.0]: https://github.com/dickdavis/model-context-protocol-rb/compare/v0.1.0...v0.2.0
22
45
  [0.1.0]: https://github.com/dickdavis/model-context-protocol-rb/releases/tag/v0.1.0
data/README.md CHANGED
@@ -8,32 +8,37 @@ 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
21
18
 
22
- Include `model-context-protocol-rb` in your project.
19
+ Include `model_context_protocol` in your project.
23
20
 
24
21
  ```ruby
25
- require 'model-context-protocol-rb'
22
+ require 'model_context_protocol'
26
23
  ```
27
24
 
28
25
  ### Building an MCP Server
29
26
 
30
- Build a simple MCP server by routing methods to your custom handlers. 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|
34
31
  config.name = "MCP Development Server"
35
32
  config.version = "1.0.0"
36
33
  config.enable_log = true
34
+
35
+ # Environment Variables - https://modelcontextprotocol.io/docs/tools/debugging#environment-variables
36
+ # Require specific environment variables to be set
37
+ config.require_environment_variable("API_KEY")
38
+
39
+ # Set environment variables programmatically
40
+ config.set_environment_variable("DEBUG_MODE", "true")
41
+
37
42
  config.registry = ModelContextProtocol::Server::Registry.new do
38
43
  prompts list_changed: true do
39
44
  register TestPrompt
@@ -43,6 +48,10 @@ server = ModelContextProtocol::Server.new do |config|
43
48
  register TestResource
44
49
  end
45
50
 
51
+ resource_templates do
52
+ register TestResourceTemplate
53
+ end
54
+
46
55
  tools list_changed: true do
47
56
  register TestTool
48
57
  end
@@ -54,29 +63,34 @@ server.start
54
63
 
55
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.
56
65
 
57
- #### ModelContextProtocol::Server::Prompt
66
+ #### Prompts
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.
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
+
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.
58
73
 
59
- The `Prompt` class is used 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, and then implement the call method to build your prompt. The `call` method should return a `Response` data object.
74
+ This is an example prompt that returns a properly formatted response:
60
75
 
61
76
  ```ruby
62
77
  class TestPrompt < ModelContextProtocol::Server::Prompt
63
78
  with_metadata do
64
- {
65
- name: "Test Prompt",
66
- description: "A test prompt",
67
- arguments: [
68
- {
69
- name: "message",
70
- description: "The thing to do",
71
- required: true
72
- },
73
- {
74
- name: "other",
75
- description: "Another thing to do",
76
- required: false
77
- }
78
- ]
79
- }
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
80
94
  end
81
95
 
82
96
  def call
@@ -90,56 +104,238 @@ class TestPrompt < ModelContextProtocol::Server::Prompt
90
104
  }
91
105
  ]
92
106
 
93
- Response[messages:, prompt: self]
107
+ respond_with messages: messages
94
108
  end
95
109
  end
96
110
  ```
97
111
 
98
- #### ModelContextProtocol::Server::Resource
112
+ #### Resources
99
113
 
100
- The `Resource` class is used 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, and then implement the 'call' method to build your prompt. The `call` method should return a `TextResponse` or a `BinaryResponse` data object.
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.
115
+
116
+ Then, implement the `call` method to build your resource. Use the `respond_with` instance method to ensure your resource responds with appropriately formatted response data.
117
+
118
+ This is an example resource that returns a text response:
101
119
 
102
120
  ```ruby
103
121
  class TestResource < ModelContextProtocol::Server::Resource
104
122
  with_metadata do
105
- {
106
- name: "Test Resource",
107
- description: "A test resource",
108
- mime_type: "text/plain",
109
- uri: "resource://test-resource"
110
- }
123
+ name "Test Resource"
124
+ description "A test resource"
125
+ mime_type "text/plain"
126
+ uri "resource://test-resource"
127
+ end
128
+
129
+ def call
130
+ respond_with :text, text: "Here's the data"
131
+ end
132
+ end
133
+ ```
134
+
135
+ This is an example resource that returns binary data:
136
+
137
+ ```ruby
138
+ class TestBinaryResource < ModelContextProtocol::Server::Resource
139
+ with_metadata do
140
+ name "Project Logo"
141
+ description "The logo for the project"
142
+ mime_type "image/jpeg"
143
+ uri "resource://project-logo"
144
+ end
145
+
146
+ def call
147
+ # In a real implementation, we would retrieve the binary resource
148
+ data = "dGVzdA=="
149
+ respond_with :binary, blob: data
111
150
  end
151
+ end
152
+ ```
153
+
154
+ #### Resource Templates
112
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
113
162
  def call
114
- TextResponse[resource: self, text: "Here's the data"]
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
115
180
  end
116
181
  end
117
182
  ```
118
183
 
119
- #### ModelContextProtocol::Server::Tool
184
+ #### Tools
185
+
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.
120
187
 
121
- The `Tool` class is used 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, and then implement the `call` method to build your prompt. The `call` method should return a `TextResponse`, `ImageResponse`, `ResourceResponse`, or `ToolErrorResponse` data object.
188
+ Then implement the `call` method to build your tool. Use the `respond_with` instance method to ensure your tool responds with appropriately formatted response data.
189
+
190
+ This is an example tool that returns a text response:
122
191
 
123
192
  ```ruby
124
- class TestTool < ModelContextProtocol::Server::Tool
193
+ class TestToolWithTextResponse < ModelContextProtocol::Server::Tool
125
194
  with_metadata do
126
- {
127
- name: "test-tool",
128
- description: "A test tool",
129
- inputSchema: {
195
+ name "double"
196
+ description "Doubles the provided number"
197
+ input_schema do
198
+ {
130
199
  type: "object",
131
200
  properties: {
132
- message: {
201
+ number: {
133
202
  type: "string"
134
203
  }
135
204
  },
136
- required: ["message"]
205
+ required: ["number"]
137
206
  }
138
- }
207
+ end
208
+ end
209
+
210
+ def call
211
+ number = params["number"].to_i
212
+ result = number * 2
213
+ respond_with :text, text: "#{number} doubled is #{result}"
214
+ end
215
+ end
216
+ ```
217
+
218
+ This is an example of a tool that returns an image:
219
+
220
+ ```ruby
221
+ class TestToolWithImageResponse < ModelContextProtocol::Server::Tool
222
+ with_metadata do
223
+ name "custom-chart-generator"
224
+ description "Generates a chart in various formats"
225
+ input_schema do
226
+ {
227
+ type: "object",
228
+ properties: {
229
+ chart_type: {
230
+ type: "string",
231
+ description: "Type of chart (pie, bar, line)"
232
+ },
233
+ format: {
234
+ type: "string",
235
+ description: "Image format (jpg, svg, etc)"
236
+ }
237
+ },
238
+ required: ["chart_type", "format"]
239
+ }
240
+ end
241
+ end
242
+
243
+ def call
244
+ # Map format to mime type
245
+ mime_type = case params["format"].downcase
246
+ when "svg"
247
+ "image/svg+xml"
248
+ when "jpg", "jpeg"
249
+ "image/jpeg"
250
+ else
251
+ "image/png"
252
+ end
253
+
254
+ # In a real implementation, we would generate an actual chart
255
+ # This is a small valid base64 encoded string (represents "test")
256
+ chart_data = "dGVzdA=="
257
+ respond_with :image, data: chart_data, mime_type:
258
+ end
259
+ end
260
+ ```
261
+
262
+ If you don't provide a mime type, it will default to `image/png`.
263
+
264
+ ```ruby
265
+ class TestToolWithImageResponseDefaultMimeType < ModelContextProtocol::Server::Tool
266
+ with_metadata do
267
+ name "other-custom-chart-generator"
268
+ description "Generates a chart"
269
+ input_schema do
270
+ {
271
+ type: "object",
272
+ properties: {
273
+ chart_type: {
274
+ type: "string",
275
+ description: "Type of chart (pie, bar, line)"
276
+ }
277
+ },
278
+ required: ["chart_type"]
279
+ }
280
+ end
281
+ end
282
+
283
+ def call
284
+ # In a real implementation, we would generate an actual chart
285
+ # This is a small valid base64 encoded string (represents "test")
286
+ chart_data = "dGVzdA=="
287
+ respond_with :image, data: chart_data
288
+ end
289
+ end
290
+ ```
291
+
292
+ This is an example of a tool that returns a resource response:
293
+
294
+ ```ruby
295
+ class TestToolWithResourceResponse < ModelContextProtocol::Server::Tool
296
+ with_metadata do
297
+ name "document-finder"
298
+ description "Finds a the document with the given title"
299
+ input_schema do
300
+ {
301
+ type: "object",
302
+ properties: {
303
+ title: {
304
+ type: "string",
305
+ description: "The title of the document"
306
+ }
307
+ },
308
+ required: ["title"]
309
+ }
310
+ end
311
+ end
312
+
313
+ def call
314
+ title = params["title"].downcase
315
+ # In a real implementation, we would do a lookup to get the document data
316
+ document = "richtextdata"
317
+ respond_with :resource, uri: "resource://document/#{title}", text: document, mime_type: "application/rtf"
139
318
  end
319
+ end
320
+ ```
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.
140
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
141
332
  def call
142
- TextResponse[text: "You said: #{params["message"]}"]
333
+ hints = {
334
+ "message" => ["hello", "world", "foo", "bar"]
335
+ }
336
+ values = hints[argument_name].grep(/#{argument_value}/)
337
+
338
+ respond_with values:
143
339
  end
144
340
  end
145
341
  ```
@@ -166,7 +362,17 @@ gem install model-context-protocol-rb
166
362
 
167
363
  ## Development
168
364
 
169
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
365
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests.
366
+
367
+ Generate an executable that you can use for testing:
368
+
369
+ ```bash
370
+ bundle exec rake mcp:generate_executable
371
+ ```
372
+
373
+ This will generate a `bin/dev` executable you can provide to MCP clients.
374
+
375
+ You can also run `bin/console` for an interactive prompt that will allow you to experiment.
170
376
 
171
377
  To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
172
378
 
data/Rakefile CHANGED
@@ -7,4 +7,6 @@ RSpec::Core::RakeTask.new(:spec)
7
7
 
8
8
  require "standard/rake"
9
9
 
10
+ Dir.glob("tasks/*.rake").each { |r| load r }
11
+
10
12
  task default: %i[spec standard]
@@ -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
@@ -9,6 +9,9 @@ module ModelContextProtocol
9
9
  # Raised when configured with invalid registry.
10
10
  class InvalidRegistryError < StandardError; end
11
11
 
12
+ # Raised when a required environment variable is not set
13
+ class MissingRequiredEnvironmentVariable < StandardError; end
14
+
12
15
  attr_accessor :enable_log, :name, :registry, :version
13
16
 
14
17
  def logging_enabled?
@@ -19,10 +22,47 @@ module ModelContextProtocol
19
22
  raise InvalidServerNameError unless valid_name?
20
23
  raise InvalidRegistryError unless valid_registry?
21
24
  raise InvalidServerVersionError unless valid_version?
25
+
26
+ validate_environment_variables!
27
+ end
28
+
29
+ def environment_variables
30
+ @environment_variables ||= {}
31
+ end
32
+
33
+ def environment_variable(key)
34
+ environment_variables[key.to_s.upcase] || ENV[key.to_s.upcase] || nil
35
+ end
36
+
37
+ def require_environment_variable(key)
38
+ required_environment_variables << key.to_s.upcase
39
+ end
40
+
41
+ # Programatically set an environment variable - useful if an alternative
42
+ # to environment variables is used for security purposes. Despite being
43
+ # more like 'configuration variables', these are called environment variables
44
+ # to align with the Model Context Protocol terminology.
45
+ #
46
+ # see: https://modelcontextprotocol.io/docs/tools/debugging#environment-variables
47
+ #
48
+ # @param key [String] The key to set the environment variable for
49
+ # @param value [String] The value to set the environment variable to
50
+ def set_environment_variable(key, value)
51
+ environment_variables[key.to_s.upcase] = value
22
52
  end
23
53
 
24
54
  private
25
55
 
56
+ def required_environment_variables
57
+ @required_environment_variables ||= []
58
+ end
59
+
60
+ def validate_environment_variables!
61
+ required_environment_variables.each do |key|
62
+ raise MissingRequiredEnvironmentVariable, "#{key} is not set" unless environment_variable(key)
63
+ end
64
+ end
65
+
26
66
  def valid_name?
27
67
  name&.is_a?(String)
28
68
  end
@@ -1,16 +1,9 @@
1
1
  module ModelContextProtocol
2
2
  class Server::Prompt
3
- attr_reader :params, :description
4
-
5
- Response = Data.define(:messages, :prompt) do
6
- def serialized
7
- {description: prompt.description, messages:}
8
- end
9
- end
3
+ attr_reader :params
10
4
 
11
5
  def initialize(params)
12
6
  validate!(params)
13
- @description = self.class.description
14
7
  @params = params
15
8
  end
16
9
 
@@ -18,6 +11,17 @@ module ModelContextProtocol
18
11
  raise NotImplementedError, "Subclasses must implement the call method"
19
12
  end
20
13
 
14
+ Response = Data.define(:messages, :description) do
15
+ def serialized
16
+ {description:, messages:}
17
+ end
18
+ end
19
+ private_constant :Response
20
+
21
+ private def respond_with(messages:)
22
+ Response[messages:, description: self.class.description]
23
+ end
24
+
21
25
  private def validate!(params = {})
22
26
  arguments = self.class.arguments || []
23
27
  required_args = arguments.select { |arg| arg[:required] }.map { |arg| arg[:name] }
@@ -40,17 +44,33 @@ module ModelContextProtocol
40
44
  attr_reader :name, :description, :arguments
41
45
 
42
46
  def with_metadata(&block)
43
- metadata = instance_eval(&block)
47
+ @arguments ||= []
48
+
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 ||= []
44
58
 
45
- @name = metadata[:name]
46
- @description = metadata[:description]
47
- @arguments = metadata[:arguments]
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
+ }
48
68
  end
49
69
 
50
70
  def inherited(subclass)
51
71
  subclass.instance_variable_set(:@name, @name)
52
72
  subclass.instance_variable_set(:@description, @description)
53
- subclass.instance_variable_set(:@arguments, @arguments)
73
+ subclass.instance_variable_set(:@arguments, @arguments&.dup)
54
74
  end
55
75
 
56
76
  def call(params)
@@ -62,6 +82,46 @@ module ModelContextProtocol
62
82
  def metadata
63
83
  {name: @name, description: @description, arguments: @arguments}
64
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
65
125
  end
66
126
  end
67
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:}
@@ -2,37 +2,51 @@ module ModelContextProtocol
2
2
  class Server::Resource
3
3
  attr_reader :mime_type, :uri
4
4
 
5
+ def initialize
6
+ @mime_type = self.class.mime_type
7
+ @uri = self.class.uri
8
+ end
9
+
10
+ def call
11
+ raise NotImplementedError, "Subclasses must implement the call method"
12
+ end
13
+
5
14
  TextResponse = Data.define(:resource, :text) do
6
15
  def serialized
7
16
  {contents: [{mimeType: resource.mime_type, text:, uri: resource.uri}]}
8
17
  end
9
18
  end
19
+ private_constant :TextResponse
10
20
 
11
21
  BinaryResponse = Data.define(:blob, :resource) do
12
22
  def serialized
13
23
  {contents: [{blob:, mimeType: resource.mime_type, uri: resource.uri}]}
14
24
  end
15
25
  end
26
+ private_constant :BinaryResponse
16
27
 
17
- def initialize
18
- @mime_type = self.class.mime_type
19
- @uri = self.class.uri
20
- end
21
-
22
- def call
23
- raise NotImplementedError, "Subclasses must implement the call method"
28
+ private def respond_with(type, **options)
29
+ case [type, options]
30
+ in [:text, {text:}]
31
+ TextResponse[resource: self, text:]
32
+ in [:binary, {blob:}]
33
+ BinaryResponse[blob:, resource: self]
34
+ else
35
+ raise ModelContextProtocol::Server::ResponseArgumentsError, "Invalid arguments: #{type}, #{options}"
36
+ end
24
37
  end
25
38
 
26
39
  class << self
27
40
  attr_reader :name, :description, :mime_type, :uri
28
41
 
29
42
  def with_metadata(&block)
30
- metadata = instance_eval(&block)
43
+ metadata_dsl = MetadataDSL.new
44
+ metadata_dsl.instance_eval(&block)
31
45
 
32
- @name = metadata[:name]
33
- @description = metadata[:description]
34
- @mime_type = metadata[:mime_type]
35
- @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
36
50
  end
37
51
 
38
52
  def inherited(subclass)
@@ -47,7 +61,29 @@ module ModelContextProtocol
47
61
  end
48
62
 
49
63
  def metadata
50
- {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
51
87
  end
52
88
  end
53
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
@@ -3,8 +3,9 @@ module ModelContextProtocol
3
3
  # Raised when an invalid method is provided.
4
4
  class MethodNotFoundError < StandardError; end
5
5
 
6
- def initialize
6
+ def initialize(configuration: nil)
7
7
  @handlers = {}
8
+ @configuration = configuration
8
9
  end
9
10
 
10
11
  def map(method, &handler)
@@ -16,7 +17,20 @@ module ModelContextProtocol
16
17
  handler = @handlers[method]
17
18
  raise MethodNotFoundError, "Method not found: #{method}" unless handler
18
19
 
19
- handler.call(message)
20
+ with_environment(@configuration&.environment_variables) do
21
+ handler.call(message)
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def with_environment(vars)
28
+ original = ENV.to_h
29
+ vars&.each { |key, value| ENV[key] = value }
30
+ yield
31
+ ensure
32
+ ENV.clear
33
+ original.each { |key, value| ENV[key] = value }
20
34
  end
21
35
  end
22
36
  end
@@ -4,11 +4,21 @@ module ModelContextProtocol
4
4
  class Server::Tool
5
5
  attr_reader :params
6
6
 
7
+ def initialize(params)
8
+ validate!(params)
9
+ @params = params
10
+ end
11
+
12
+ def call
13
+ raise NotImplementedError, "Subclasses must implement the call method"
14
+ end
15
+
7
16
  TextResponse = Data.define(:text) do
8
17
  def serialized
9
18
  {content: [{type: "text", text:}], isError: false}
10
19
  end
11
20
  end
21
+ private_constant :TextResponse
12
22
 
13
23
  ImageResponse = Data.define(:data, :mime_type) do
14
24
  def initialize(data:, mime_type: "image/png")
@@ -19,6 +29,7 @@ module ModelContextProtocol
19
29
  {content: [{type: "image", data:, mimeType: mime_type}], isError: false}
20
30
  end
21
31
  end
32
+ private_constant :ImageResponse
22
33
 
23
34
  ResourceResponse = Data.define(:uri, :text, :mime_type) do
24
35
  def initialize(uri:, text:, mime_type: "text/plain")
@@ -29,20 +40,32 @@ module ModelContextProtocol
29
40
  {content: [{type: "resource", resource: {uri:, mimeType: mime_type, text:}}], isError: false}
30
41
  end
31
42
  end
43
+ private_constant :ResourceResponse
32
44
 
33
45
  ToolErrorResponse = Data.define(:text) do
34
46
  def serialized
35
47
  {content: [{type: "text", text:}], isError: true}
36
48
  end
37
49
  end
38
-
39
- def initialize(params)
40
- validate!(params)
41
- @params = params
42
- end
43
-
44
- def call
45
- raise NotImplementedError, "Subclasses must implement the call method"
50
+ private_constant :ToolErrorResponse
51
+
52
+ private def respond_with(type, **options)
53
+ case [type, options]
54
+ in [:text, {text:}]
55
+ TextResponse[text:]
56
+ in [:image, {data:, mime_type:}]
57
+ ImageResponse[data:, mime_type:]
58
+ in [:image, {data:}]
59
+ ImageResponse[data:]
60
+ in [:resource, {mime_type:, text:, uri:}]
61
+ ResourceResponse[mime_type:, text:, uri:]
62
+ in [:resource, {text:, uri:}]
63
+ ResourceResponse[text:, uri:]
64
+ in [:error, {text:}]
65
+ ToolErrorResponse[text:]
66
+ else
67
+ raise ModelContextProtocol::Server::ResponseArgumentsError, "Invalid arguments: #{type}, #{options}"
68
+ end
46
69
  end
47
70
 
48
71
  private def validate!(params)
@@ -53,11 +76,12 @@ module ModelContextProtocol
53
76
  attr_reader :name, :description, :input_schema
54
77
 
55
78
  def with_metadata(&block)
56
- metadata = instance_eval(&block)
79
+ metadata_dsl = MetadataDSL.new
80
+ metadata_dsl.instance_eval(&block)
57
81
 
58
- @name = metadata[:name]
59
- @description = metadata[:description]
60
- @input_schema = metadata[:inputSchema]
82
+ @name = metadata_dsl.name
83
+ @description = metadata_dsl.description
84
+ @input_schema = metadata_dsl.input_schema
61
85
  end
62
86
 
63
87
  def inherited(subclass)
@@ -68,8 +92,10 @@ module ModelContextProtocol
68
92
 
69
93
  def call(params)
70
94
  new(params).call
71
- rescue JSON::Schema::ValidationError => error
72
- raise ModelContextProtocol::Server::ParameterValidationError, error.message
95
+ rescue JSON::Schema::ValidationError => validation_error
96
+ raise ModelContextProtocol::Server::ParameterValidationError, validation_error.message
97
+ rescue ModelContextProtocol::Server::ResponseArgumentsError => response_arguments_error
98
+ raise response_arguments_error
73
99
  rescue => error
74
100
  ToolErrorResponse[text: error.message]
75
101
  end
@@ -78,5 +104,22 @@ module ModelContextProtocol
78
104
  {name: @name, description: @description, inputSchema: @input_schema}
79
105
  end
80
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
81
124
  end
82
125
  end
@@ -2,6 +2,9 @@ require "logger"
2
2
 
3
3
  module ModelContextProtocol
4
4
  class Server
5
+ # Raised when invalid response arguments are provided.
6
+ class ResponseArgumentsError < StandardError; end
7
+
5
8
  # Raised when invalid parameters are provided.
6
9
  class ParameterValidationError < StandardError; end
7
10
 
@@ -10,7 +13,7 @@ module ModelContextProtocol
10
13
  def initialize
11
14
  @configuration = Configuration.new
12
15
  yield(@configuration) if block_given?
13
- @router = Router.new
16
+ @router = Router.new(configuration:)
14
17
  map_handlers
15
18
  end
16
19
 
@@ -57,12 +60,45 @@ module ModelContextProtocol
57
60
  PingResponse[]
58
61
  end
59
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
+
60
86
  router.map("resources/list") do
61
87
  configuration.registry.resources_data
62
88
  end
63
89
 
64
90
  router.map("resources/read") do |message|
65
- 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
66
102
  end
67
103
 
68
104
  router.map("prompts/list") do
@@ -84,6 +120,7 @@ module ModelContextProtocol
84
120
 
85
121
  def build_capabilities
86
122
  {}.tap do |capabilities|
123
+ capabilities[:completions] = {}
87
124
  capabilities[:logging] = {} if configuration.logging_enabled?
88
125
 
89
126
  registry = configuration.registry
@@ -91,7 +128,7 @@ module ModelContextProtocol
91
128
  if registry.prompts_options.any? && !registry.instance_variable_get(:@prompts).empty?
92
129
  capabilities[:prompts] = {
93
130
  listChanged: registry.prompts_options[:list_changed]
94
- }.compact
131
+ }.except(:completions).compact
95
132
  end
96
133
 
97
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.0"
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
 
data/tasks/mcp.rake ADDED
@@ -0,0 +1,62 @@
1
+ require "fileutils"
2
+
3
+ namespace :mcp do
4
+ desc "Generate the development server executable with the correct Ruby path"
5
+ task :generate_executable do
6
+ destination_path = "bin/dev"
7
+ template_path = File.expand_path("templates/dev.erb", __dir__)
8
+
9
+ # Create directory if it doesn't exist
10
+ FileUtils.mkdir_p(File.dirname(destination_path))
11
+
12
+ # Get the Ruby path
13
+ ruby_path = detect_ruby_path
14
+
15
+ # Read and process the template
16
+ template = File.read(template_path)
17
+ content = template.gsub("<%= @ruby_path %>", ruby_path)
18
+
19
+ # Write the executable
20
+ File.write(destination_path, content)
21
+
22
+ # Set permissions
23
+ FileUtils.chmod(0o755, destination_path)
24
+
25
+ # Show success message
26
+ puts "\nCreated executable at: #{File.expand_path(destination_path)}"
27
+ puts "Using Ruby path: #{ruby_path}"
28
+ end
29
+
30
+ def detect_ruby_path
31
+ # Get Ruby version from project config
32
+ ruby_version = get_project_ruby_version
33
+
34
+ if ruby_version && ruby_version.strip != ""
35
+ # Find the absolute path to the Ruby executable via ASDF
36
+ asdf_ruby_path = `asdf where ruby #{ruby_version}`.strip
37
+
38
+ if asdf_ruby_path && !asdf_ruby_path.empty? && File.directory?(asdf_ruby_path)
39
+ return File.join(asdf_ruby_path, "bin", "ruby")
40
+ end
41
+ end
42
+
43
+ # Fallback to current Ruby
44
+ `which ruby`.strip
45
+ end
46
+
47
+ def get_project_ruby_version
48
+ # Try ASDF first
49
+ if File.exist?(".tool-versions")
50
+ content = File.read(".tool-versions")
51
+ ruby_line = content.lines.find { |line| line.start_with?("ruby ") }
52
+ return ruby_line.split[1].strip if ruby_line
53
+ end
54
+
55
+ # Try .ruby-version file
56
+ if File.exist?(".ruby-version")
57
+ return File.read(".ruby-version").strip
58
+ end
59
+
60
+ nil
61
+ end
62
+ end
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env <%= @ruby_path %>
2
+
3
+ require "bundler/setup"
4
+ require_relative "../lib/model_context_protocol"
5
+
6
+ Dir[File.join(__dir__, "../spec/support/**/*.rb")].each { |file| require file }
7
+
8
+ server = ModelContextProtocol::Server.new do |config|
9
+ config.name = "MCP Development Server"
10
+ config.version = "1.0.0"
11
+ config.enable_log = true
12
+ config.registry = ModelContextProtocol::Server::Registry.new do
13
+ prompts list_changed: true do
14
+ register TestPrompt
15
+ end
16
+
17
+ resources list_changed: true, subscribe: true do
18
+ register TestResource
19
+ register TestBinaryResource
20
+ end
21
+
22
+ resource_templates do
23
+ register TestResourceTemplate
24
+ end
25
+
26
+ tools list_changed: true do
27
+ register TestToolWithTextResponse
28
+ register TestToolWithImageResponse
29
+ register TestToolWithImageResponseDefaultMimeType
30
+ register TestToolWithResourceResponse
31
+ register TestToolWithResourceResponseDefaultMimeType
32
+ register TestToolWithToolErrorResponse
33
+ end
34
+ end
35
+ end
36
+
37
+ server.start
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.0
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-03-11 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,14 +56,18 @@ 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
52
68
  - lib/model_context_protocol/version.rb
69
+ - tasks/mcp.rake
70
+ - tasks/templates/dev.erb
53
71
  homepage: https://github.com/dickdavis/model-context-protocol-rb
54
72
  licenses:
55
73
  - MIT