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 +4 -4
- data/CHANGELOG.md +17 -1
- data/README.md +173 -16
- data/Rakefile +2 -0
- data/lib/model_context_protocol/server/configuration.rb +40 -0
- data/lib/model_context_protocol/server/prompt.rb +11 -6
- data/lib/model_context_protocol/server/resource.rb +20 -7
- data/lib/model_context_protocol/server/router.rb +16 -2
- data/lib/model_context_protocol/server/tool.rb +35 -10
- data/lib/model_context_protocol/server.rb +4 -1
- data/lib/model_context_protocol/version.rb +1 -1
- data/tasks/mcp.rake +62 -0
- data/tasks/templates/dev.erb +33 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: aa9e49b895a1502fe4887f7e2089c24b69b980d9ac7c27754f97c52ad1541c7b
|
4
|
+
data.tar.gz: 92e343e66c69cd1e5ae9f9d8adb83567303a2aa5e48021b1dc964c172128426d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
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 `
|
22
|
+
Include `model_context_protocol` in your project.
|
23
23
|
|
24
24
|
```ruby
|
25
|
-
require '
|
25
|
+
require 'model_context_protocol'
|
26
26
|
```
|
27
27
|
|
28
28
|
### Building an MCP Server
|
29
29
|
|
30
|
-
Build a simple MCP server by
|
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
|
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
|
-
|
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
|
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
|
-
|
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
|
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
|
165
|
+
class TestToolWithTextResponse < ModelContextProtocol::Server::Tool
|
125
166
|
with_metadata do
|
126
167
|
{
|
127
|
-
name: "
|
128
|
-
description: "
|
168
|
+
name: "double",
|
169
|
+
description: "Doubles the provided number",
|
129
170
|
inputSchema: {
|
130
171
|
type: "object",
|
131
172
|
properties: {
|
132
|
-
|
133
|
-
type: "string"
|
173
|
+
number: {
|
174
|
+
type: "string",
|
134
175
|
}
|
135
176
|
},
|
136
|
-
required: ["
|
177
|
+
required: ["number"]
|
137
178
|
}
|
138
179
|
}
|
139
180
|
end
|
140
181
|
|
141
182
|
def call
|
142
|
-
|
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.
|
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
@@ -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
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
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
|
-
|
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
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
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 =>
|
72
|
-
raise ModelContextProtocol::Server::ParameterValidationError,
|
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
|
|
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.
|
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-
|
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
|