model-context-protocol-rb 0.3.1 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: aa9e49b895a1502fe4887f7e2089c24b69b980d9ac7c27754f97c52ad1541c7b
4
- data.tar.gz: 92e343e66c69cd1e5ae9f9d8adb83567303a2aa5e48021b1dc964c172128426d
3
+ metadata.gz: 5cce144594a63393d124f7edc41428730824f2e5df282b21458b506d1ac59376
4
+ data.tar.gz: f7a1de949d083fbcf9df57f500cad32869aa2c42b7dda17726509bab58ffc0b2
5
5
  SHA512:
6
- metadata.gz: bcbfcb95e17f7e0deaa368f7e2b3dc7915be124b4c98b2b86b8e94c446fe18bd7d9e8c7dbfb6e4301edf6b1ad28e491701fec85bbd31dcd2d79d8b91a3f0c8c1
7
- data.tar.gz: 284e545287ecacb83d42c7a5f72dbe9b0f5ab359d50c9d0cfcde1eb2e00d9c330e9cce3b6bcb15e68fb48963e8778888ca3dbdf1fab5ac0a0d27d36f1ca4e6bd
6
+ metadata.gz: f374a5e4cf00d1f905f86c51be35c812b6074c188dca28e2724a9e478afdba641337b4f17404ed44cdb22a86baf88dbcaf03df4896e5b039f1717eb3c8afbb71
7
+ data.tar.gz: 8bb5dd4e584f9df32265d1a88d9469e73b2c70225a88da510240bb5b7f80ef4f94b13738ed66a1f1bcec39012832efc0032d72d7ff93ede737ec31d737589243
data/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.3.3] - 2025-09-02
4
+
5
+ - (Breaking) Added logging support.
6
+ - Requires updating the `enable_log` configuration option to `logging_enabled`.
7
+ - Added experimental Streamable HTTP transport.
8
+ - (Breaking) Renamed params to arguments in prompts, resources, and tools.
9
+ - Requires updating all references to `params` in prompts, resources, and tools to `arguments` with symbolized keys.
10
+ - Improved ergonomics of completions and resource templates.
11
+ - Added support for providing context to prompts, resources, and tools.
12
+
13
+ ## [0.3.2] - 2025-05-10
14
+
15
+ - Added resource template support.
16
+ - Added completion support for prompts and resources.
17
+ - Improved metadata definition for prompts, resources, and tools using simple DSL.
18
+
3
19
  ## [0.3.1] - 2025-04-04
4
20
 
5
21
  - Added support for environment variables to MCP servers (thanks @hmk):
@@ -31,7 +47,9 @@
31
47
 
32
48
  - Initial release
33
49
 
34
- [Unreleased]: https://github.com/dickdavis/model-context-protocol-rb/compare/v0.3.1...HEAD
50
+ [Unreleased]: https://github.com/dickdavis/model-context-protocol-rb/compare/v0.3.3...HEAD
51
+ [0.3.3]: https://github.com/dickdavis/model-context-protocol-rb/compare/v0.3.2...v0.3.3
52
+ [0.3.2]: https://github.com/dickdavis/model-context-protocol-rb/compare/v0.3.1...v0.3.2
35
53
  [0.3.1]: https://github.com/dickdavis/model-context-protocol-rb/compare/v0.3.0...v0.3.1
36
54
  [0.3.0]: https://github.com/dickdavis/model-context-protocol-rb/compare/v0.2.0...v0.3.0
37
55
  [0.2.0]: https://github.com/dickdavis/model-context-protocol-rb/compare/v0.1.0...v0.2.0
data/README.md CHANGED
@@ -8,13 +8,10 @@ You are welcome to contribute.
8
8
 
9
9
  TODO's:
10
10
 
11
- * [Completion](https://spec.modelcontextprotocol.io/specification/2024-11-05/server/utilities/completion/)
12
- * [Logging](https://spec.modelcontextprotocol.io/specification/2024-11-05/server/utilities/logging/)
13
11
  * [Pagination](https://spec.modelcontextprotocol.io/specification/2024-11-05/server/utilities/pagination/)
14
12
  * [Prompt list changed notifications](https://spec.modelcontextprotocol.io/specification/2024-11-05/server/prompts/#list-changed-notification)
15
13
  * [Resource list changed notifications](https://spec.modelcontextprotocol.io/specification/2024-11-05/server/resources/#list-changed-notification)
16
14
  * [Resource subscriptions](https://spec.modelcontextprotocol.io/specification/2024-11-05/server/resources/#subscriptions)
17
- * [Resource templates](https://spec.modelcontextprotocol.io/specification/2024-11-05/server/resources/#resource-templates)
18
15
  * [Tool list changed notifications](https://spec.modelcontextprotocol.io/specification/2024-11-05/server/tools/#list-changed-notification)
19
16
 
20
17
  ## Usage
@@ -27,13 +24,13 @@ require 'model_context_protocol'
27
24
 
28
25
  ### Building an MCP Server
29
26
 
30
- Build a simple MCP server by registering your prompts, resources, and tools. Then, configure and run the server.
27
+ Build a simple MCP server by registering your prompts, resources, resource templates, and tools. Then, configure and run the server.
31
28
 
32
29
  ```ruby
33
30
  server = ModelContextProtocol::Server.new do |config|
34
31
  config.name = "MCP Development Server"
35
32
  config.version = "1.0.0"
36
- config.enable_log = true
33
+ config.logging_enabled = true
37
34
 
38
35
  # Environment Variables - https://modelcontextprotocol.io/docs/tools/debugging#environment-variables
39
36
  # Require specific environment variables to be set
@@ -42,6 +39,12 @@ server = ModelContextProtocol::Server.new do |config|
42
39
  # Set environment variables programmatically
43
40
  config.set_environment_variable("DEBUG_MODE", "true")
44
41
 
42
+ # Provide prompts, resources, and tools with contextual variables
43
+ config.context = {
44
+ user_id: "123456",
45
+ request_id: SecureRandom.uuid
46
+ }
47
+
45
48
  config.registry = ModelContextProtocol::Server::Registry.new do
46
49
  prompts list_changed: true do
47
50
  register TestPrompt
@@ -51,6 +54,10 @@ server = ModelContextProtocol::Server.new do |config|
51
54
  register TestResource
52
55
  end
53
56
 
57
+ resource_templates do
58
+ register TestResourceTemplate
59
+ end
60
+
54
61
  tools list_changed: true do
55
62
  register TestTool
56
63
  end
@@ -60,44 +67,159 @@ end
60
67
  server.start
61
68
  ```
62
69
 
70
+ ### Transport Configuration
71
+
72
+ The MCP server supports different transport mechanisms for communication with clients. By default, it uses stdio (standard input/output), but you can also configure it to use streamable HTTP transport for distributed deployments.
73
+
74
+ #### Stdio Transport (Default)
75
+
76
+ When no transport is specified, the server uses stdio transport, which is suitable for single-process communication:
77
+
78
+ ```ruby
79
+ server = ModelContextProtocol::Server.new do |config|
80
+ config.name = "MCP Development Server"
81
+ config.version = "1.0.0"
82
+ # No transport specified - uses stdio by default
83
+ config.registry = ModelContextProtocol::Server::Registry.new
84
+ end
85
+
86
+ server.start
87
+ ```
88
+
89
+ #### Streamable HTTP Transport
90
+
91
+ For distributed deployments with load balancers and multiple server instances, use the streamable HTTP transport with Redis-backed session management:
92
+
93
+ ```ruby
94
+ require 'redis'
95
+
96
+ server = ModelContextProtocol::Server.new do |config|
97
+ config.name = "MCP Development Server"
98
+ config.version = "1.0.0"
99
+
100
+ # Configure streamable HTTP transport
101
+ config.transport = {
102
+ type: :streamable_http,
103
+ redis_client: Redis.new(url: ENV['REDIS_URL']),
104
+ session_ttl: 3600 # Optional: session timeout in seconds (default: 3600)
105
+ }
106
+
107
+ config.registry = ModelContextProtocol::Server::Registry.new
108
+ end
109
+
110
+ # For HTTP frameworks, handle the request and return the response
111
+ result = server.start
112
+ # result will be a hash like: {json: {...}, status: 200, headers: {...}}
113
+ ```
114
+
115
+ **Key Features:**
116
+ - **Distributed Sessions**: Redis-backed session storage enables multiple server instances
117
+ - **Load Balancer Support**: Sessions persist across different server instances
118
+ - **HTTP Methods**: Supports POST (requests), GET (Server-Sent Events), DELETE (cleanup)
119
+ - **Cross-Server Routing**: Messages are routed between servers via Redis pub/sub
120
+
121
+ **Integration Example (Rails):**
122
+
123
+ ```ruby
124
+ class McpController < ApplicationController
125
+ def handle
126
+ server = ModelContextProtocol::Server.new do |config|
127
+ config.name = "Rails MCP Server"
128
+ config.version = "1.0.0"
129
+ config.transport = {
130
+ type: :streamable_http,
131
+ redis_client: Redis.new(url: ENV['REDIS_URL']),
132
+ request: request,
133
+ response: response
134
+ }
135
+ config.registry = build_registry
136
+ end
137
+
138
+ result = server.start
139
+ render json: result[:json], status: result[:status], headers: result[:headers]
140
+ end
141
+ end
142
+ ```
143
+
63
144
  Messages from the MCP client will be routed to the appropriate custom handler. This SDK provides several classes that should be used to build your handlers.
64
145
 
65
- #### ModelContextProtocol::Server::Prompt
146
+ ### Server features
147
+
148
+ #### Prompts
66
149
 
67
150
  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
151
 
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.
152
+ Define any arguments using the `with_argument` block. You can mark an argument as required, and you can optionally provide a completion class. See [Completions](#completions) for more information.
153
+
154
+ Then implement the `call` method to build your prompt. Any arguments passed to the tool from the MCP client will be available in the `arguments` hash with symbol keys (e.g., `arguments[:argument_name]`), and any context values provided in the server configuration will be available in the `context` hash. Use the `respond_with` instance method to ensure your prompt responds with appropriately formatted response data.
155
+
156
+ You can also log from within your prompt by calling a valid logger level method on the `logger` and passing a string message.
70
157
 
71
158
  This is an example prompt that returns a properly formatted response:
72
159
 
73
160
  ```ruby
74
161
  class TestPrompt < ModelContextProtocol::Server::Prompt
162
+ ToneCompletion = ModelContextProtocol::Server::Completion.define do
163
+ hints = ["whiny", "angry", "callous", "desperate", "nervous", "sneaky"]
164
+ values = hints.grep(/#{argument_value}/)
165
+
166
+ respond_with values:
167
+ end
168
+
75
169
  with_metadata do
76
- {
77
- name: "Test Prompt",
78
- description: "A test prompt",
79
- arguments: [
80
- {
81
- name: "message",
82
- description: "The thing to do",
83
- required: true
84
- },
85
- {
86
- name: "other",
87
- description: "Another thing to do",
88
- required: false
89
- }
90
- ]
91
- }
170
+ name "brainstorm_excuses"
171
+ description "A prompt for brainstorming excuses to get out of something"
172
+ end
173
+
174
+ with_argument do
175
+ name "undesirable_activity"
176
+ description "The thing to get out of"
177
+ required true
178
+ end
179
+
180
+ with_argument do
181
+ name "tone"
182
+ description "The general tone to be used in the generated excuses"
183
+ required false
184
+ completion ToneCompletion
92
185
  end
93
186
 
94
187
  def call
188
+ logger.info("Brainstorming excuses...")
95
189
  messages = [
96
190
  {
97
191
  role: "user",
98
192
  content: {
99
193
  type: "text",
100
- text: "Do this: #{params["message"]}"
194
+ text: "My wife wants me to: #{arguments[:undesirable_activity]}... Can you believe it?"
195
+ }
196
+ },
197
+ {
198
+ role: "assistant",
199
+ content: {
200
+ type: "text",
201
+ text: "Oh, that's just downright awful. What are you going to do?"
202
+ }
203
+ },
204
+ {
205
+ role: "user",
206
+ content: {
207
+ type: "text",
208
+ text: "Well, I'd like to get out of it, but I'm going to need your help."
209
+ }
210
+ },
211
+ {
212
+ role: "assistant",
213
+ content: {
214
+ type: "text",
215
+ text: "Anything for you."
216
+ }
217
+ },
218
+ {
219
+ role: "user",
220
+ content: {
221
+ type: "text",
222
+ text: "Can you generate some excuses for me?" + (arguments[:tone] ? "Make them as #{arguments[:tone]} as possible." : "")
101
223
  }
102
224
  }
103
225
  ]
@@ -107,27 +229,39 @@ class TestPrompt < ModelContextProtocol::Server::Prompt
107
229
  end
108
230
  ```
109
231
 
110
- #### ModelContextProtocol::Server::Resource
232
+ #### Resources
111
233
 
112
234
  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
235
 
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.
236
+ Then, implement the `call` method to build your resource. Any context values provided in the server configuration will be available in the `context` hash. Use the `respond_with` instance method to ensure your resource responds with appropriately formatted response data.
237
+
238
+ You can also log from within your resource by calling a valid logger level method on the `logger` and passing a string message.
115
239
 
116
240
  This is an example resource that returns a text response:
117
241
 
118
242
  ```ruby
119
243
  class TestResource < ModelContextProtocol::Server::Resource
120
244
  with_metadata do
121
- {
122
- name: "Test Resource",
123
- description: "A test resource",
124
- mime_type: "text/plain",
125
- uri: "resource://test-resource"
126
- }
245
+ name "top-secret-plans.txt"
246
+ description "Top secret plans to do top secret things"
247
+ mime_type "text/plain"
248
+ uri "file:///top-secret-plans.txt"
127
249
  end
128
250
 
129
251
  def call
130
- respond_with :text, text: "Here's the data"
252
+ unless authorized?(context[:user_id])
253
+ logger.info("This fool thinks he can get my top secret plans...")
254
+ return respond_with :text, text: "Nothing to see here, move along."
255
+ end
256
+
257
+ respond_with :text, text: "I'm finna eat all my wife's leftovers."
258
+ end
259
+
260
+ private
261
+
262
+ def authorized?(user_id)
263
+ authorized_users = ["42", "123456"]
264
+ authorized_users.any?(user_id)
131
265
  end
132
266
  end
133
267
  ```
@@ -137,52 +271,84 @@ This is an example resource that returns binary data:
137
271
  ```ruby
138
272
  class TestBinaryResource < ModelContextProtocol::Server::Resource
139
273
  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
- }
274
+ name "project-logo.png"
275
+ description "The logo for the project"
276
+ mime_type "image/png"
277
+ uri "file:///project-logo.png"
146
278
  end
147
279
 
148
280
  def call
149
281
  # In a real implementation, we would retrieve the binary resource
282
+ # This is a small valid base64 encoded string (represents "test")
150
283
  data = "dGVzdA=="
151
284
  respond_with :binary, blob: data
152
285
  end
153
286
  end
154
287
  ```
155
288
 
156
- #### ModelContextProtocol::Server::Tool
289
+ #### Resource Templates
290
+
291
+ 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.
292
+
293
+ This is an example resource template that provides a completion for a parameter of the URI template:
294
+
295
+ ```ruby
296
+ class TestResourceTemplate < ModelContextProtocol::Server::ResourceTemplate
297
+ Completion = ModelContextProtocol::Server::Completion.define do
298
+ hints = {
299
+ "name" => ["top-secret-plans.txt"]
300
+ }
301
+ values = hints[argument_name].grep(/#{argument_value}/)
302
+
303
+ respond_with values:
304
+ end
305
+
306
+ with_metadata do
307
+ name "project-document-resource-template"
308
+ description "A resource template for retrieving project documents"
309
+ mime_type "text/plain"
310
+ uri_template "file:///{name}" do
311
+ completion :name, Completion
312
+ end
313
+ end
314
+ end
315
+ ```
316
+
317
+ #### Tools
157
318
 
158
319
  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
320
 
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.
321
+ Then, implement the `call` method to build your tool. Any arguments passed to the tool from the MCP client will be available in the `arguments` hash with symbol keys (e.g., `arguments[:argument_name]`), and any context values provided in the server configuration will be available in the `context` hash. Use the `respond_with` instance method to ensure your tool responds with appropriately formatted response data.
322
+
323
+ You can also log from within your tool by calling a valid logger level method on the `logger` and passing a string message.
161
324
 
162
325
  This is an example tool that returns a text response:
163
326
 
164
327
  ```ruby
165
328
  class TestToolWithTextResponse < ModelContextProtocol::Server::Tool
166
329
  with_metadata do
167
- {
168
- name: "double",
169
- description: "Doubles the provided number",
170
- inputSchema: {
330
+ name "double"
331
+ description "Doubles the provided number"
332
+ input_schema do
333
+ {
171
334
  type: "object",
172
335
  properties: {
173
336
  number: {
174
- type: "string",
337
+ type: "string"
175
338
  }
176
339
  },
177
340
  required: ["number"]
178
341
  }
179
- }
342
+ end
180
343
  end
181
344
 
182
345
  def call
183
- number = params["number"].to_i
184
- result = number * 2
185
- respond_with :text, text: "#{number} doubled is #{result}"
346
+ user_id = context[:user_id]
347
+ number = arguments[:number].to_i
348
+ logger.info("Silly user doesn't know how to double a number")
349
+ calculation = number * 2
350
+ salutation = user_id ? "User #{user_id}, " : ""
351
+ respond_with :text, text: salutation << "#{number} doubled is #{calculation}"
186
352
  end
187
353
  end
188
354
  ```
@@ -192,10 +358,10 @@ This is an example of a tool that returns an image:
192
358
  ```ruby
193
359
  class TestToolWithImageResponse < ModelContextProtocol::Server::Tool
194
360
  with_metadata do
195
- {
196
- name: "custom-chart-generator",
197
- description: "Generates a chart in various formats",
198
- inputSchema: {
361
+ name "custom-chart-generator"
362
+ description "Generates a chart in various formats"
363
+ input_schema do
364
+ {
199
365
  type: "object",
200
366
  properties: {
201
367
  chart_type: {
@@ -209,12 +375,12 @@ class TestToolWithImageResponse < ModelContextProtocol::Server::Tool
209
375
  },
210
376
  required: ["chart_type", "format"]
211
377
  }
212
- }
378
+ end
213
379
  end
214
380
 
215
381
  def call
216
382
  # Map format to mime type
217
- mime_type = case params["format"].downcase
383
+ mime_type = case arguments[:format].downcase
218
384
  when "svg"
219
385
  "image/svg+xml"
220
386
  when "jpg", "jpeg"
@@ -236,10 +402,10 @@ If you don't provide a mime type, it will default to `image/png`.
236
402
  ```ruby
237
403
  class TestToolWithImageResponseDefaultMimeType < ModelContextProtocol::Server::Tool
238
404
  with_metadata do
239
- {
240
- name: "other-custom-chart-generator",
241
- description: "Generates a chart",
242
- inputSchema: {
405
+ name "other-custom-chart-generator"
406
+ description "Generates a chart"
407
+ input_schema do
408
+ {
243
409
  type: "object",
244
410
  properties: {
245
411
  chart_type: {
@@ -249,7 +415,7 @@ class TestToolWithImageResponseDefaultMimeType < ModelContextProtocol::Server::T
249
415
  },
250
416
  required: ["chart_type"]
251
417
  }
252
- }
418
+ end
253
419
  end
254
420
 
255
421
  def call
@@ -266,10 +432,10 @@ This is an example of a tool that returns a resource response:
266
432
  ```ruby
267
433
  class TestToolWithResourceResponse < ModelContextProtocol::Server::Tool
268
434
  with_metadata do
269
- {
270
- name: "document-finder",
271
- description: "Finds a the document with the given title",
272
- inputSchema: {
435
+ name "document-finder"
436
+ description "Finds a the document with the given title"
437
+ input_schema do
438
+ {
273
439
  type: "object",
274
440
  properties: {
275
441
  title: {
@@ -279,11 +445,11 @@ class TestToolWithResourceResponse < ModelContextProtocol::Server::Tool
279
445
  },
280
446
  required: ["title"]
281
447
  }
282
- }
448
+ end
283
449
  end
284
450
 
285
451
  def call
286
- title = params["title"].downcase
452
+ title = arguments[:title].downcase
287
453
  # In a real implementation, we would do a lookup to get the document data
288
454
  document = "richtextdata"
289
455
  respond_with :resource, uri: "resource://document/#{title}", text: document, mime_type: "application/rtf"
@@ -291,6 +457,27 @@ class TestToolWithResourceResponse < ModelContextProtocol::Server::Tool
291
457
  end
292
458
  ```
293
459
 
460
+ ### Completions
461
+
462
+ 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.
463
+
464
+ implement the `call` method to build your completion. Use the `respond_with` instance method to ensure your completion responds with appropriately formatted response data.
465
+
466
+ This is an example completion that returns an array of values in the response:
467
+
468
+ ```ruby
469
+ class TestCompletion < ModelContextProtocol::Server::Completion
470
+ def call
471
+ hints = {
472
+ "message" => ["hello", "world", "foo", "bar"]
473
+ }
474
+ values = hints[argument_name].grep(/#{argument_value}/)
475
+
476
+ respond_with values:
477
+ end
478
+ end
479
+ ```
480
+
294
481
  ## Installation
295
482
 
296
483
  Add this line to your application's Gemfile:
@@ -0,0 +1,51 @@
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
+ def self.define(&block)
19
+ Class.new(self) do
20
+ define_method(:call, &block)
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ Response = Data.define(:values, :total, :hasMore) do
27
+ def serialized
28
+ {completion: {values:, total:, hasMore:}}
29
+ end
30
+ end
31
+
32
+ def respond_with(values:)
33
+ values_to_return = values.take(100)
34
+ total = values.size
35
+ has_more = values_to_return.size != total
36
+ Response[values:, total:, hasMore: has_more]
37
+ end
38
+ end
39
+
40
+ class Server::NullCompletion
41
+ Response = Data.define(:values, :total, :hasMore) do
42
+ def serialized
43
+ {completion: {values:, total:, hasMore:}}
44
+ end
45
+ end
46
+
47
+ def self.call(_argument_name, _argument_value)
48
+ Response[values: [], total: 0, hasMore: false]
49
+ end
50
+ end
51
+ end