model-context-protocol-rb 0.3.0 → 0.3.1

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: aa9e49b895a1502fe4887f7e2089c24b69b980d9ac7c27754f97c52ad1541c7b
4
+ data.tar.gz: 92e343e66c69cd1e5ae9f9d8adb83567303a2aa5e48021b1dc964c172128426d
5
5
  SHA512:
6
- metadata.gz: f17f3371c5c07b264b2a495fbc8b3e47fc8d18993cc3bd64f9fe91f4475720dde9ec536f266af1f22e7c7eb30ffd0bc4e5680f0a53f8a39c08d2eff034206e9f
7
- data.tar.gz: 564433c1386b063560c17502b7346ae7db2cc523c47ab4ecd62fb986c51fba178432331a4ceed699ec9469c8d63e719acddce669c03d68969b8fc7086a85e640
6
+ metadata.gz: bcbfcb95e17f7e0deaa368f7e2b3dc7915be124b4c98b2b86b8e94c446fe18bd7d9e8c7dbfb6e4301edf6b1ad28e491701fec85bbd31dcd2d79d8b91a3f0c8c1
7
+ data.tar.gz: 284e545287ecacb83d42c7a5f72dbe9b0f5ab359d50c9d0cfcde1eb2e00d9c330e9cce3b6bcb15e68fb48963e8778888ca3dbdf1fab5ac0a0d27d36f1ca4e6bd
data/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.3.1] - 2025-04-04
4
+
5
+ - Added support for environment variables to MCP servers (thanks @hmk):
6
+ - `require_environment_variable` method to specify required environment variables
7
+ - `set_environment_variable` method to programmatically set environment variables
8
+ - Environment variables accessible within tool/prompt/resource handlers
9
+ - Added `respond_with` helper methods to simplify response creation:
10
+ - For tools: text, image, resource, and error responses
11
+ - For prompts: formatted message responses
12
+ - For resources: text and binary responses
13
+ - Improved development tooling:
14
+ - Generated executable now loads all test classes
15
+ - Fixed test support classes for better compatibility with MCP inspector
16
+ - Organized test tools, prompts, and resources in dedicated directories
17
+
3
18
  ## [0.3.0] - 2025-03-11
4
19
 
5
20
  - (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 +31,8 @@
16
31
 
17
32
  - Initial release
18
33
 
19
- [Unreleased]: https://github.com/dickdavis/model-context-protocol-rb/compare/v0.3.0...HEAD
34
+ [Unreleased]: https://github.com/dickdavis/model-context-protocol-rb/compare/v0.3.1...HEAD
35
+ [0.3.1]: https://github.com/dickdavis/model-context-protocol-rb/compare/v0.3.0...v0.3.1
20
36
  [0.3.0]: https://github.com/dickdavis/model-context-protocol-rb/compare/v0.2.0...v0.3.0
21
37
  [0.2.0]: https://github.com/dickdavis/model-context-protocol-rb/compare/v0.1.0...v0.2.0
22
38
  [0.1.0]: https://github.com/dickdavis/model-context-protocol-rb/releases/tag/v0.1.0
data/README.md CHANGED
@@ -19,21 +19,29 @@ TODO's:
19
19
 
20
20
  ## Usage
21
21
 
22
- Include `model-context-protocol-rb` in your project.
22
+ Include `model_context_protocol` in your project.
23
23
 
24
24
  ```ruby
25
- require 'model-context-protocol-rb'
25
+ require 'model_context_protocol'
26
26
  ```
27
27
 
28
28
  ### Building an MCP Server
29
29
 
30
- Build a simple MCP server by routing methods to your custom handlers. Then, configure and run the server.
30
+ Build a simple MCP server by registering your prompts, resources, and tools. Then, configure and run the server.
31
31
 
32
32
  ```ruby
33
33
  server = ModelContextProtocol::Server.new do |config|
34
34
  config.name = "MCP Development Server"
35
35
  config.version = "1.0.0"
36
36
  config.enable_log = true
37
+
38
+ # Environment Variables - https://modelcontextprotocol.io/docs/tools/debugging#environment-variables
39
+ # Require specific environment variables to be set
40
+ config.require_environment_variable("API_KEY")
41
+
42
+ # Set environment variables programmatically
43
+ config.set_environment_variable("DEBUG_MODE", "true")
44
+
37
45
  config.registry = ModelContextProtocol::Server::Registry.new do
38
46
  prompts list_changed: true do
39
47
  register TestPrompt
@@ -56,7 +64,11 @@ Messages from the MCP client will be routed to the appropriate custom handler. T
56
64
 
57
65
  #### ModelContextProtocol::Server::Prompt
58
66
 
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.
67
+ 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
+ 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
+
71
+ This is an example prompt that returns a properly formatted response:
60
72
 
61
73
  ```ruby
62
74
  class TestPrompt < ModelContextProtocol::Server::Prompt
@@ -90,14 +102,18 @@ class TestPrompt < ModelContextProtocol::Server::Prompt
90
102
  }
91
103
  ]
92
104
 
93
- Response[messages:, prompt: self]
105
+ respond_with messages: messages
94
106
  end
95
107
  end
96
108
  ```
97
109
 
98
110
  #### ModelContextProtocol::Server::Resource
99
111
 
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.
112
+ 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
+
114
+ 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.
115
+
116
+ This is an example resource that returns a text response:
101
117
 
102
118
  ```ruby
103
119
  class TestResource < ModelContextProtocol::Server::Resource
@@ -111,35 +127,166 @@ class TestResource < ModelContextProtocol::Server::Resource
111
127
  end
112
128
 
113
129
  def call
114
- TextResponse[resource: self, text: "Here's the data"]
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
+ {
141
+ name: "Project Logo",
142
+ description: "The logo for the project",
143
+ mime_type: "image/jpeg",
144
+ uri: "resource://project-logo"
145
+ }
146
+ end
147
+
148
+ def call
149
+ # In a real implementation, we would retrieve the binary resource
150
+ data = "dGVzdA=="
151
+ respond_with :binary, blob: data
115
152
  end
116
153
  end
117
154
  ```
118
155
 
119
156
  #### ModelContextProtocol::Server::Tool
120
157
 
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.
158
+ 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
+
160
+ 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.
161
+
162
+ This is an example tool that returns a text response:
122
163
 
123
164
  ```ruby
124
- class TestTool < ModelContextProtocol::Server::Tool
165
+ class TestToolWithTextResponse < ModelContextProtocol::Server::Tool
125
166
  with_metadata do
126
167
  {
127
- name: "test-tool",
128
- description: "A test tool",
168
+ name: "double",
169
+ description: "Doubles the provided number",
129
170
  inputSchema: {
130
171
  type: "object",
131
172
  properties: {
132
- message: {
133
- type: "string"
173
+ number: {
174
+ type: "string",
134
175
  }
135
176
  },
136
- required: ["message"]
177
+ required: ["number"]
137
178
  }
138
179
  }
139
180
  end
140
181
 
141
182
  def call
142
- TextResponse[text: "You said: #{params["message"]}"]
183
+ number = params["number"].to_i
184
+ result = number * 2
185
+ respond_with :text, text: "#{number} doubled is #{result}"
186
+ end
187
+ end
188
+ ```
189
+
190
+ This is an example of a tool that returns an image:
191
+
192
+ ```ruby
193
+ class TestToolWithImageResponse < ModelContextProtocol::Server::Tool
194
+ with_metadata do
195
+ {
196
+ name: "custom-chart-generator",
197
+ description: "Generates a chart in various formats",
198
+ inputSchema: {
199
+ type: "object",
200
+ properties: {
201
+ chart_type: {
202
+ type: "string",
203
+ description: "Type of chart (pie, bar, line)"
204
+ },
205
+ format: {
206
+ type: "string",
207
+ description: "Image format (jpg, svg, etc)"
208
+ }
209
+ },
210
+ required: ["chart_type", "format"]
211
+ }
212
+ }
213
+ end
214
+
215
+ def call
216
+ # Map format to mime type
217
+ mime_type = case params["format"].downcase
218
+ when "svg"
219
+ "image/svg+xml"
220
+ when "jpg", "jpeg"
221
+ "image/jpeg"
222
+ else
223
+ "image/png"
224
+ end
225
+
226
+ # In a real implementation, we would generate an actual chart
227
+ # This is a small valid base64 encoded string (represents "test")
228
+ chart_data = "dGVzdA=="
229
+ respond_with :image, data: chart_data, mime_type:
230
+ end
231
+ end
232
+ ```
233
+
234
+ If you don't provide a mime type, it will default to `image/png`.
235
+
236
+ ```ruby
237
+ class TestToolWithImageResponseDefaultMimeType < ModelContextProtocol::Server::Tool
238
+ with_metadata do
239
+ {
240
+ name: "other-custom-chart-generator",
241
+ description: "Generates a chart",
242
+ inputSchema: {
243
+ type: "object",
244
+ properties: {
245
+ chart_type: {
246
+ type: "string",
247
+ description: "Type of chart (pie, bar, line)"
248
+ }
249
+ },
250
+ required: ["chart_type"]
251
+ }
252
+ }
253
+ end
254
+
255
+ def call
256
+ # In a real implementation, we would generate an actual chart
257
+ # This is a small valid base64 encoded string (represents "test")
258
+ chart_data = "dGVzdA=="
259
+ respond_with :image, data: chart_data
260
+ end
261
+ end
262
+ ```
263
+
264
+ This is an example of a tool that returns a resource response:
265
+
266
+ ```ruby
267
+ class TestToolWithResourceResponse < ModelContextProtocol::Server::Tool
268
+ with_metadata do
269
+ {
270
+ name: "document-finder",
271
+ description: "Finds a the document with the given title",
272
+ inputSchema: {
273
+ type: "object",
274
+ properties: {
275
+ title: {
276
+ type: "string",
277
+ description: "The title of the document"
278
+ }
279
+ },
280
+ required: ["title"]
281
+ }
282
+ }
283
+ end
284
+
285
+ def call
286
+ title = params["title"].downcase
287
+ # In a real implementation, we would do a lookup to get the document data
288
+ document = "richtextdata"
289
+ respond_with :resource, uri: "resource://document/#{title}", text: document, mime_type: "application/rtf"
143
290
  end
144
291
  end
145
292
  ```
@@ -166,7 +313,17 @@ gem install model-context-protocol-rb
166
313
 
167
314
  ## Development
168
315
 
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.
316
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests.
317
+
318
+ Generate an executable that you can use for testing:
319
+
320
+ ```bash
321
+ bundle exec rake mcp:generate_executable
322
+ ```
323
+
324
+ This will generate a `bin/dev` executable you can provide to MCP clients.
325
+
326
+ You can also run `bin/console` for an interactive prompt that will allow you to experiment.
170
327
 
171
328
  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
329
 
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]
@@ -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
@@ -2,12 +2,6 @@ module ModelContextProtocol
2
2
  class Server::Prompt
3
3
  attr_reader :params, :description
4
4
 
5
- Response = Data.define(:messages, :prompt) do
6
- def serialized
7
- {description: prompt.description, messages:}
8
- end
9
- end
10
-
11
5
  def initialize(params)
12
6
  validate!(params)
13
7
  @description = self.class.description
@@ -18,6 +12,17 @@ module ModelContextProtocol
18
12
  raise NotImplementedError, "Subclasses must implement the call method"
19
13
  end
20
14
 
15
+ Response = Data.define(:messages, :prompt) do
16
+ def serialized
17
+ {description: prompt.description, messages:}
18
+ end
19
+ end
20
+ private_constant :Response
21
+
22
+ private def respond_with(messages:)
23
+ Response[messages:, prompt: self]
24
+ end
25
+
21
26
  private def validate!(params = {})
22
27
  arguments = self.class.arguments || []
23
28
  required_args = arguments.select { |arg| arg[:required] }.map { |arg| arg[:name] }
@@ -2,25 +2,38 @@ 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
@@ -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)
@@ -68,8 +91,10 @@ module ModelContextProtocol
68
91
 
69
92
  def call(params)
70
93
  new(params).call
71
- rescue JSON::Schema::ValidationError => error
72
- raise ModelContextProtocol::Server::ParameterValidationError, error.message
94
+ rescue JSON::Schema::ValidationError => validation_error
95
+ raise ModelContextProtocol::Server::ParameterValidationError, validation_error.message
96
+ rescue ModelContextProtocol::Server::ResponseArgumentsError => response_arguments_error
97
+ raise response_arguments_error
73
98
  rescue => error
74
99
  ToolErrorResponse[text: error.message]
75
100
  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
 
@@ -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.1"
5
5
  end
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,33 @@
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
+ tools list_changed: true do
23
+ register TestToolWithTextResponse
24
+ register TestToolWithImageResponse
25
+ register TestToolWithImageResponseDefaultMimeType
26
+ register TestToolWithResourceResponse
27
+ register TestToolWithResourceResponseDefaultMimeType
28
+ register TestToolWithToolErrorResponse
29
+ end
30
+ end
31
+ end
32
+
33
+ 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.1
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-04-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json-schema
@@ -50,6 +50,8 @@ files:
50
50
  - lib/model_context_protocol/server/stdio_transport.rb
51
51
  - lib/model_context_protocol/server/tool.rb
52
52
  - lib/model_context_protocol/version.rb
53
+ - tasks/mcp.rake
54
+ - tasks/templates/dev.erb
53
55
  homepage: https://github.com/dickdavis/model-context-protocol-rb
54
56
  licenses:
55
57
  - MIT