model-context-protocol-rb 0.4.0 → 0.5.0

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.
Files changed (27) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +9 -1
  3. data/README.md +155 -12
  4. data/lib/model_context_protocol/server/cancellable.rb +54 -0
  5. data/lib/model_context_protocol/server/configuration.rb +4 -9
  6. data/lib/model_context_protocol/server/progressable.rb +72 -0
  7. data/lib/model_context_protocol/server/prompt.rb +3 -1
  8. data/lib/model_context_protocol/server/redis_client_proxy.rb +134 -0
  9. data/lib/model_context_protocol/server/redis_config.rb +108 -0
  10. data/lib/model_context_protocol/server/redis_pool_manager.rb +110 -0
  11. data/lib/model_context_protocol/server/resource.rb +3 -0
  12. data/lib/model_context_protocol/server/router.rb +36 -3
  13. data/lib/model_context_protocol/server/stdio_transport/request_store.rb +102 -0
  14. data/lib/model_context_protocol/server/stdio_transport.rb +31 -6
  15. data/lib/model_context_protocol/server/streamable_http_transport/event_counter.rb +35 -0
  16. data/lib/model_context_protocol/server/streamable_http_transport/message_poller.rb +101 -0
  17. data/lib/model_context_protocol/server/streamable_http_transport/notification_queue.rb +80 -0
  18. data/lib/model_context_protocol/server/streamable_http_transport/request_store.rb +224 -0
  19. data/lib/model_context_protocol/server/streamable_http_transport/session_message_queue.rb +120 -0
  20. data/lib/model_context_protocol/server/{session_store.rb → streamable_http_transport/session_store.rb} +30 -16
  21. data/lib/model_context_protocol/server/streamable_http_transport/stream_registry.rb +119 -0
  22. data/lib/model_context_protocol/server/streamable_http_transport.rb +162 -79
  23. data/lib/model_context_protocol/server/tool.rb +4 -0
  24. data/lib/model_context_protocol/server.rb +9 -3
  25. data/lib/model_context_protocol/version.rb +1 -1
  26. data/tasks/templates/dev-http.erb +58 -14
  27. metadata +57 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0cb9fa1e4fc1430df1df1bd454f3f718cf3693afb7b412976751e7f44f56a10e
4
- data.tar.gz: fc70eac6a64674c886cc246bb3fcc0efcf956f27d406d511cc833fe33a11fc77
3
+ metadata.gz: 5f97580e7f9c5723d25472b545c037bad650d82434865324c6d1dd450ac4031e
4
+ data.tar.gz: 1b73ad6036c7ded852210dc338190675e419c6f6354e027d2b7073a5e655c665
5
5
  SHA512:
6
- metadata.gz: c2819592f7f0e3c3360e5265a0e15f73a92e6a4d439fc3fb86fcce99c41f8e08e03fd361bd1a8c31968c05334e55957a169c5f33f4586380347178edc89fb851
7
- data.tar.gz: ba81388d3e7f1b7f903c6eb3f2094cbdc37d8a8e5ff9af605855d07708dab8fea5cf1c642a5e1e92e0121e5b2207fe1ac99ae563c21c0e0ead6d7157a53ecf4e
6
+ metadata.gz: 3075cfc900a60fd1cb83b973c23f6f517373f6132ab043ea64079653bbc0ecc5c5aeb8a077e2c685396f9921d80ad341f9fa271fd343fb9b9f29ce93b6abda4b
7
+ data.tar.gz: c72619fea381f8968d7dd33e60c306449973bbeda9f5a8ffd6b8d5baf95d26df8278a2106b82daeef61982a950b74b5f168a8afb680baa6e44f10d26626cac69
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.5.0] - 2025-09-22
4
+
5
+ - Make streamable HTTP transport thread-safe by using Redis to manage state.
6
+ - Implement Redis connection pooling with robust management and configuration.
7
+ - Automatically upgrade connection to SSE to send notifications.
8
+ - Add support for cancellations and progress notifications via `cancellable` and `progressable` blocks in prompts, resources, and tools.
9
+
3
10
  ## [0.4.0] - 2025-09-07
4
11
 
5
12
  - Implement pagination support.
@@ -67,7 +74,8 @@
67
74
 
68
75
  - Initial release
69
76
 
70
- [Unreleased]: https://github.com/dickdavis/model-context-protocol-rb/compare/v0.4.0...HEAD
77
+ [Unreleased]: https://github.com/dickdavis/model-context-protocol-rb/compare/v0.5.0...HEAD
78
+ [0.5.0]: https://github.com/dickdavis/model-context-protocol-rb/compare/v0.4.0...v0.5.0
71
79
  [0.4.0]: https://github.com/dickdavis/model-context-protocol-rb/compare/v0.3.4...v0.4.0
72
80
  [0.3.4]: https://github.com/dickdavis/model-context-protocol-rb/compare/v0.3.3...v0.3.4
73
81
  [0.3.3]: https://github.com/dickdavis/model-context-protocol-rb/compare/v0.3.2...v0.3.3
data/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # model-context-protocol-rb
2
2
 
3
- An implementation of the [Model Context Protocol (MCP)](https://spec.modelcontextprotocol.io/specification/2025-06-18/) in Ruby. You are welcome to contribute.
3
+ An implementation of the [Model Context Protocol (MCP)](https://spec.modelcontextprotocol.io/specification/2025-06-18/) in Ruby.
4
+
5
+ Provides simple abstractions that allow you to serve prompts, resources, resource templates, and tools via MCP locally (stdio) or in production (streamable HTTP backed by Redis) with minimal effort.
4
6
 
5
7
  ## Table of Contents
6
8
 
@@ -44,9 +46,9 @@ An implementation of the [Model Context Protocol (MCP)](https://spec.modelcontex
44
46
  | ❌ | [List Changed Notification (Resources)](https://modelcontextprotocol.io/specification/2025-06-18/server/resources#list-changed-notification) |
45
47
  | ❌ | [Subscriptions (Resources)](https://modelcontextprotocol.io/specification/2025-06-18/server/resources#subscriptions) |
46
48
  | ❌ | [List Changed Notification (Tools)](https://modelcontextprotocol.io/specification/2025-06-18/server/tools#list-changed-notification) |
47
- | | [Cancellation](https://modelcontextprotocol.io/specification/2025-06-18/basic/utilities/cancellation) |
49
+ | | [Cancellation](https://modelcontextprotocol.io/specification/2025-06-18/basic/utilities/cancellation) |
48
50
  | ✅ | [Ping](https://modelcontextprotocol.io/specification/2025-06-18/basic/utilities/ping) |
49
- | | [Progress](https://modelcontextprotocol.io/specification/2025-06-18/basic/utilities/progress) |
51
+ | | [Progress](https://modelcontextprotocol.io/specification/2025-06-18/basic/utilities/progress) |
50
52
 
51
53
  ## Usage
52
54
 
@@ -116,7 +118,7 @@ server = ModelContextProtocol::Server.new do |config|
116
118
  # Optional: configure streamable HTTP transport if required
117
119
  # config.transport = {
118
120
  # type: :streamable_http,
119
- # redis_client: Redis.new(url: ENV['REDIS_URL']),
121
+ # env: request.env,
120
122
  # session_ttl: 3600 # Optional: session timeout in seconds (default: 3600)
121
123
  # }
122
124
 
@@ -182,12 +184,33 @@ config.transport = { type: :stdio } # This is the default, can be omitted
182
184
  ```
183
185
 
184
186
  ##### Streamable HTTP Transport
185
- When using `:streamable_http`, the following options are available:
187
+ The `:streamable_http` transport requires Redis to be configured globally before use:
188
+
189
+ ```ruby
190
+ ModelContextProtocol::Server.configure_redis do |config|
191
+ config.redis_url = ENV.fetch('REDIS_URL')
192
+ config.pool_size = 20
193
+ config.pool_timeout = 5
194
+ config.enable_reaper = true
195
+ config.reaper_interval = 60
196
+ config.idle_timeout = 300
197
+ end
198
+ ```
199
+
200
+ | Option | Type | Required | Default | Description |
201
+ |--------|------|----------|---------|-------------|
202
+ | `redis_url` | String | Yes | - | Redis connection URL |
203
+ | `pool_size` | Integer | No | `20` | Connection pool size |
204
+ | `pool_timeout` | Integer | No | `5` | Pool checkout timeout in seconds |
205
+ | `enable_reaper` | Boolean | No | `true` | Enable connection reaping |
206
+ | `reaper_interval` | Integer | No | `60` | Reaper check interval in seconds |
207
+ | `idle_timeout` | Integer | No | `300` | Idle connection timeout in seconds |
208
+
209
+ When using `:streamable_http` transport, the following options are available:
186
210
 
187
211
  | Option | Type | Required | Default | Description |
188
212
  |--------|------|----------|---------|-------------|
189
213
  | `type` | Symbol | Yes | `:stdio` | Must be `:streamable_http` for HTTP transport |
190
- | `redis_client` | Redis | Yes | - | Redis client instance for session management |
191
214
  | `session_ttl` | Integer | No | `3600` | Session timeout in seconds (1 hour) |
192
215
  | `env` | Hash | No | - | Rack environment hash (for Rails integration) |
193
216
 
@@ -226,7 +249,21 @@ end
226
249
 
227
250
  The streamable HTTP transport works with any valid Rack request. Here's an example of how you can integrate with Rails.
228
251
 
229
- First, set the routes:
252
+ First, configure Redis in an initializer:
253
+
254
+ ```ruby
255
+ # config/initializers/model_context_protocol.rb
256
+ ModelContextProtocol::Server.configure_redis do |config|
257
+ config.redis_url = ENV.fetch('REDIS_URL')
258
+ config.pool_size = 20
259
+ config.pool_timeout = 5
260
+ config.enable_reaper = true
261
+ config.reaper_interval = 60
262
+ config.idle_timeout = 300
263
+ end
264
+ ```
265
+
266
+ Then, set the routes:
230
267
 
231
268
  ```ruby
232
269
  constraints format: :json do
@@ -257,8 +294,7 @@ class ModelContextProtocolController < ApplicationController
257
294
  config.registry = build_registry
258
295
  config.transport = {
259
296
  type: :streamable_http,
260
- redis_client: Redis.new(url: ENV['REDIS_URL']), # Prefer initializing a client from a connection pool
261
- env: request.env # Rack environment hash
297
+ env: request.env
262
298
  }
263
299
  config.instructions = <<~INSTRUCTIONS
264
300
  This server provides prompts, tools, and resources for interacting with my app.
@@ -330,12 +366,14 @@ Define any arguments using `argument` blocks nested within the `define` block. Y
330
366
 
331
367
  #### Prompt Methods
332
368
 
333
- Define your prompt properties and arguments, implement the `call` method using the `message_history` DSL to build prompt messages and `respond_with` to serialize them.
369
+ Define your prompt properties and arguments, implement the `call` method using the `message_history` DSL to build prompt messages and `respond_with` to serialize them. You can wrap long running operations in a `cancellable` block to allow clients to cancel the request. Also, you can automatically send progress notifications to clients by wrapping long-running operations in a `progressable` block.
334
370
 
335
371
  | Method | Context | Description |
336
372
  |--------|---------|-------------|
337
373
  | `define` | Class definition | Block for defining prompt metadata and arguments |
338
374
  | `call` | Instance method | Main method to implement prompt logic and build response |
375
+ | `cancellable` | Within `call` | Wrap long-running operations to allow client cancellation (e.g., `cancellable { slow_operation }`) |
376
+ | `progressable` | Within `call` | Wrap long-running operations to send clients progress notifications (e.g., `progressable { slow_operation }`) |
339
377
  | `message_history` | Within `call` | DSL method to build an array of user and assistant messages |
340
378
  | `respond_with` | Within `call` | Return properly formatted response data (e.g., `respond_with messages:`) |
341
379
 
@@ -480,12 +518,14 @@ Define any [resource annotations](https://modelcontextprotocol.io/specification/
480
518
 
481
519
  #### Resource Methods
482
520
 
483
- Define your resource properties and annotations, implement the `call` method to build resource content and `respond_with` to serialize the response.
521
+ Define your resource properties and annotations, implement the `call` method to build resource content and `respond_with` to serialize the response. You can wrap long running operations in a `cancellable` block to allow clients to cancel the request. Also, you can automatically send progress notifications to clients by wrapping long-running operations in a `progressable` block.
484
522
 
485
523
  | Method | Context | Description |
486
524
  |--------|---------|-------------|
487
525
  | `define` | Class definition | Block for defining resource metadata and annotations |
488
526
  | `call` | Instance method | Main method to implement resource logic and build response |
527
+ | `cancellable` | Within `call` | Wrap long-running operations to allow client cancellation (e.g., `cancellable { slow_operation }`) |
528
+ | `progressable` | Within `call` | Wrap long-running operations to send clients progress notifications (e.g., `progressable { slow_operation }`) |
489
529
  | `respond_with` | Within `call` | Return properly formatted response data (e.g., `respond_with text:` or `respond_with binary:`) |
490
530
 
491
531
  #### Available Instance Variables
@@ -648,12 +688,14 @@ Use the `define` block to set [tool properties](https://spec.modelcontextprotoco
648
688
 
649
689
  #### Tool Methods
650
690
 
651
- Define your tool properties and schemas, implement the `call` method using content helpers and `respond_with` to serialize responses.
691
+ Define your tool properties and schemas, implement the `call` method using content helpers and `respond_with` to serialize responses. You can wrap long running operations in a `cancellable` block to allow clients to cancel the request. Also, you can automatically send progress notifications to clients by wrapping long-running operations in a `progressable` block.
652
692
 
653
693
  | Method | Context | Description |
654
694
  |--------|---------|-------------|
655
695
  | `define` | Class definition | Block for defining tool metadata and schemas |
656
696
  | `call` | Instance method | Main method to implement tool logic and build response |
697
+ | `cancellable` | Within `call` | Wrap long-running operations to allow client cancellation (e.g., `cancellable { slow_operation }`) |
698
+ | `progressable` | Within `call` | Wrap long-running operations to send clients progress notifications (e.g., `progressable { slow_operation }`) |
657
699
  | `respond_with` | Within `call` | Return properly formatted response data with various content types |
658
700
 
659
701
  #### Content Blocks
@@ -973,6 +1015,87 @@ class TestToolWithToolErrorResponse < ModelContextProtocol::Server::Tool
973
1015
  end
974
1016
  ```
975
1017
 
1018
+ This is an example of a tool that allows a client to cancel a long-running operation:
1019
+
1020
+ ```ruby
1021
+ class TestToolWithCancellableSleep < ModelContextProtocol::Server::Tool
1022
+ define do
1023
+ name "cancellable_sleep"
1024
+ title "Cancellable Sleep Tool"
1025
+ description "Sleep for 3 seconds with cancellation support"
1026
+ input_schema do
1027
+ {
1028
+ type: "object",
1029
+ properties: {},
1030
+ additionalProperties: false
1031
+ }
1032
+ end
1033
+ end
1034
+
1035
+ def call
1036
+ logger.info("Starting 3 second sleep operation")
1037
+
1038
+ result = cancellable do
1039
+ sleep 3
1040
+ "Sleep completed successfully"
1041
+ end
1042
+
1043
+ respond_with content: text_content(text: result)
1044
+ end
1045
+ end
1046
+ ```
1047
+
1048
+ This is an example of a tool that automatically sends progress notifications to the client and allows the client to cancel the operation:
1049
+
1050
+ ```ruby
1051
+ class TestToolWithProgressableAndCancellable < ModelContextProtocol::Server::Tool
1052
+ define do
1053
+ name "test_tool_with_progressable_and_cancellable"
1054
+ description "A test tool that demonstrates combined progressable and cancellable functionality"
1055
+
1056
+ input_schema do
1057
+ {
1058
+ type: "object",
1059
+ properties: {
1060
+ max_duration: {
1061
+ type: "number",
1062
+ description: "Expected maximum duration in seconds"
1063
+ },
1064
+ work_steps: {
1065
+ type: "number",
1066
+ description: "Number of work steps to perform"
1067
+ }
1068
+ },
1069
+ required: ["max_duration"]
1070
+ }
1071
+ end
1072
+ end
1073
+
1074
+ def call
1075
+ max_duration = arguments[:max_duration] || 10
1076
+ work_steps = arguments[:work_steps] || 10
1077
+ logger.info("Starting progressable call with max_duration=#{max_duration}, work_steps=#{work_steps}")
1078
+
1079
+ result = progressable(max_duration:, message: "Processing #{work_steps} items") do
1080
+ cancellable do
1081
+ processed_items = []
1082
+
1083
+ work_steps.times do |i|
1084
+ sleep(max_duration / work_steps.to_f)
1085
+ processed_items << "item_#{i + 1}"
1086
+ end
1087
+
1088
+ processed_items
1089
+ end
1090
+ end
1091
+
1092
+ response = text_content(text: "Successfully processed #{result.length} items: #{result.join(", ")}")
1093
+
1094
+ respond_with content: response
1095
+ end
1096
+ end
1097
+ ```
1098
+
976
1099
  ### Completions
977
1100
 
978
1101
  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.
@@ -1038,6 +1161,8 @@ gem install model-context-protocol-rb
1038
1161
 
1039
1162
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rspec` to run the tests.
1040
1163
 
1164
+ ### Generate Development Servers
1165
+
1041
1166
  Generate executables that you can use for testing:
1042
1167
 
1043
1168
  ```bash
@@ -1048,6 +1173,24 @@ bundle exec rake mcp:generate_stdio_server
1048
1173
  bundle exec rake mcp:generate_streamable_http_server
1049
1174
  ```
1050
1175
 
1176
+ If you need to test with HTTPS (e.g., for clients that require SSL), generate self-signed certificates:
1177
+
1178
+ ```bash
1179
+ # Create SSL directory and generate certificates
1180
+ mkdir -p tmp/ssl
1181
+ openssl req -x509 -newkey rsa:4096 -keyout tmp/ssl/server.key -out tmp/ssl/server.crt -days 365 -nodes -subj "/C=US/ST=Dev/L=Dev/O=Dev/CN=localhost"
1182
+ ```
1183
+
1184
+ The HTTP server supports both HTTP and HTTPS:
1185
+
1186
+ ```bash
1187
+ # Run HTTP server (default)
1188
+ bin/dev-http
1189
+
1190
+ # Run HTTPS server (requires SSL certificates in tmp/ssl/)
1191
+ SSL=true bin/dev-http
1192
+ ```
1193
+
1051
1194
  You can also run `bin/console` for an interactive prompt that will allow you to experiment. Execute command `rp` to reload the project.
1052
1195
 
1053
1196
  To install this gem onto your local machine, run `bundle exec rake install`.
@@ -0,0 +1,54 @@
1
+ require "concurrent-ruby"
2
+
3
+ module ModelContextProtocol
4
+ module Server::Cancellable
5
+ # Raised when a request has been cancelled by the client
6
+ class CancellationError < StandardError; end
7
+
8
+ # Execute a block with automatic cancellation support for blocking I/O operations.
9
+ # This method uses Concurrent::TimerTask to poll for cancellation every 100ms
10
+ # and can interrupt even blocking operations like HTTP requests or database queries.
11
+ #
12
+ # @param interval [Float] polling interval in seconds (default: 0.1)
13
+ # @yield block to execute with cancellation support
14
+ # @return [Object] the result of the block
15
+ # @raise [CancellationError] if the request is cancelled during execution
16
+ #
17
+ # @example
18
+ # cancellable do
19
+ # response = Net::HTTP.get(URI('https://slow-api.example.com'))
20
+ # process_response(response)
21
+ # end
22
+ def cancellable(interval: 0.1, &block)
23
+ context = Thread.current[:mcp_context]
24
+ executing_thread = Concurrent::AtomicReference.new(nil)
25
+
26
+ timer_task = Concurrent::TimerTask.new(execution_interval: interval) do
27
+ if context && context[:request_store] && context[:request_id]
28
+ if context[:request_store].cancelled?(context[:request_id])
29
+ thread = executing_thread.get
30
+ thread&.raise(CancellationError, "Request was cancelled") if thread&.alive?
31
+ end
32
+ end
33
+ end
34
+
35
+ begin
36
+ executing_thread.set(Thread.current)
37
+
38
+ if context && context[:request_store] && context[:request_id]
39
+ if context[:request_store].cancelled?(context[:request_id])
40
+ raise CancellationError, "Request #{context[:request_id]} was cancelled"
41
+ end
42
+ end
43
+
44
+ timer_task.execute
45
+
46
+ result = block.call
47
+ result
48
+ ensure
49
+ executing_thread.set(nil)
50
+ timer_task&.shutdown if timer_task&.running?
51
+ end
52
+ end
53
+ end
54
+ end
@@ -200,15 +200,10 @@ module ModelContextProtocol
200
200
  end
201
201
 
202
202
  def validate_streamable_http_transport!
203
- options = transport_options
204
-
205
- unless options[:redis_client]
206
- raise InvalidTransportError, "streamable_http transport requires redis_client option"
207
- end
208
-
209
- redis_client = options[:redis_client]
210
- unless redis_client.respond_to?(:hset) && redis_client.respond_to?(:expire)
211
- raise InvalidTransportError, "redis_client must be a Redis-compatible client"
203
+ unless ModelContextProtocol::Server::RedisConfig.configured?
204
+ raise InvalidTransportError,
205
+ "streamable_http transport requires Redis configuration. " \
206
+ "Call ModelContextProtocol::Server.configure_redis in an initializer."
212
207
  end
213
208
  end
214
209
 
@@ -0,0 +1,72 @@
1
+ require "concurrent-ruby"
2
+
3
+ module ModelContextProtocol
4
+ module Server::Progressable
5
+ # Execute a block with automatic time-based progress reporting.
6
+ # Uses Concurrent::TimerTask to send progress notifications at regular intervals.
7
+ #
8
+ # @param max_duration [Numeric] Expected duration in seconds
9
+ # @param message [String, nil] Optional custom progress message
10
+ # @yield block to execute with progress tracking
11
+ # @return [Object] the result of the block
12
+ #
13
+ # @example
14
+ # progressable(max_duration: 30) do # 30 seconds
15
+ # perform_long_operation
16
+ # end
17
+ def progressable(max_duration:, message: nil, &block)
18
+ context = Thread.current[:mcp_context]
19
+
20
+ return yield unless context && context[:progress_token] && context[:transport]
21
+
22
+ progress_token = context[:progress_token]
23
+ transport = context[:transport]
24
+ start_time = Time.now
25
+ update_interval = [1.0, max_duration * 0.05].max
26
+
27
+ timer_task = Concurrent::TimerTask.new(execution_interval: update_interval) do
28
+ elapsed_seconds = Time.now - start_time
29
+ progress_pct = [(elapsed_seconds / max_duration) * 100, 99].min
30
+
31
+ progress_message = if message
32
+ "#{message} (#{elapsed_seconds.round(1)}s / ~#{max_duration}s)"
33
+ else
34
+ "Processing... (#{elapsed_seconds.round(1)}s / ~#{max_duration}s)"
35
+ end
36
+
37
+ begin
38
+ transport.send_notification("notifications/progress", {
39
+ progressToken: progress_token,
40
+ progress: progress_pct.round(1),
41
+ total: 100,
42
+ message: progress_message
43
+ })
44
+ rescue
45
+ nil
46
+ end
47
+
48
+ timer_task.shutdown if elapsed_seconds >= max_duration
49
+ end
50
+
51
+ begin
52
+ timer_task.execute
53
+ result = yield
54
+
55
+ begin
56
+ transport.send_notification("notifications/progress", {
57
+ progressToken: progress_token,
58
+ progress: 100,
59
+ total: 100,
60
+ message: "Completed"
61
+ })
62
+ rescue
63
+ nil
64
+ end
65
+
66
+ result
67
+ ensure
68
+ timer_task&.shutdown if timer_task&.running?
69
+ end
70
+ end
71
+ end
72
+ end
@@ -1,6 +1,8 @@
1
1
  module ModelContextProtocol
2
2
  class Server::Prompt
3
- include Server::ContentHelpers
3
+ include ModelContextProtocol::Server::Cancellable
4
+ include ModelContextProtocol::Server::ContentHelpers
5
+ include ModelContextProtocol::Server::Progressable
4
6
 
5
7
  attr_reader :arguments, :context, :logger
6
8
 
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ModelContextProtocol
4
+ class Server
5
+ class RedisClientProxy
6
+ def initialize(pool)
7
+ @pool = pool
8
+ end
9
+
10
+ def get(key)
11
+ with_connection { |redis| redis.get(key) }
12
+ end
13
+
14
+ def set(key, value, **options)
15
+ with_connection { |redis| redis.set(key, value, **options) }
16
+ end
17
+
18
+ def del(*keys)
19
+ with_connection { |redis| redis.del(*keys) }
20
+ end
21
+
22
+ def exists(*keys)
23
+ with_connection { |redis| redis.exists(*keys) }
24
+ end
25
+
26
+ def expire(key, seconds)
27
+ with_connection { |redis| redis.expire(key, seconds) }
28
+ end
29
+
30
+ def ttl(key)
31
+ with_connection { |redis| redis.ttl(key) }
32
+ end
33
+
34
+ def hget(key, field)
35
+ with_connection { |redis| redis.hget(key, field) }
36
+ end
37
+
38
+ def hset(key, *args)
39
+ with_connection { |redis| redis.hset(key, *args) }
40
+ end
41
+
42
+ def hgetall(key)
43
+ with_connection { |redis| redis.hgetall(key) }
44
+ end
45
+
46
+ def lpush(key, *values)
47
+ with_connection { |redis| redis.lpush(key, *values) }
48
+ end
49
+
50
+ def rpop(key)
51
+ with_connection { |redis| redis.rpop(key) }
52
+ end
53
+
54
+ def lrange(key, start, stop)
55
+ with_connection { |redis| redis.lrange(key, start, stop) }
56
+ end
57
+
58
+ def llen(key)
59
+ with_connection { |redis| redis.llen(key) }
60
+ end
61
+
62
+ def ltrim(key, start, stop)
63
+ with_connection { |redis| redis.ltrim(key, start, stop) }
64
+ end
65
+
66
+ def incr(key)
67
+ with_connection { |redis| redis.incr(key) }
68
+ end
69
+
70
+ def decr(key)
71
+ with_connection { |redis| redis.decr(key) }
72
+ end
73
+
74
+ def keys(pattern)
75
+ with_connection { |redis| redis.keys(pattern) }
76
+ end
77
+
78
+ def multi(&block)
79
+ with_connection do |redis|
80
+ redis.multi do |multi|
81
+ multi_wrapper = RedisMultiWrapper.new(multi)
82
+ block.call(multi_wrapper)
83
+ end
84
+ end
85
+ end
86
+
87
+ def pipelined(&block)
88
+ with_connection do |redis|
89
+ redis.pipelined do |pipeline|
90
+ pipeline_wrapper = RedisMultiWrapper.new(pipeline)
91
+ block.call(pipeline_wrapper)
92
+ end
93
+ end
94
+ end
95
+
96
+ def mget(*keys)
97
+ with_connection { |redis| redis.mget(*keys) }
98
+ end
99
+
100
+ def eval(script, keys: [], argv: [])
101
+ with_connection { |redis| redis.eval(script, keys: keys, argv: argv) }
102
+ end
103
+
104
+ def ping
105
+ with_connection { |redis| redis.ping }
106
+ end
107
+
108
+ def flushdb
109
+ with_connection { |redis| redis.flushdb }
110
+ end
111
+
112
+ private
113
+
114
+ def with_connection(&block)
115
+ @pool.with(&block)
116
+ end
117
+
118
+ # Wrapper for Redis multi/pipeline operations
119
+ class RedisMultiWrapper
120
+ def initialize(multi)
121
+ @multi = multi
122
+ end
123
+
124
+ def method_missing(method, *args, **kwargs, &block)
125
+ @multi.send(method, *args, **kwargs, &block)
126
+ end
127
+
128
+ def respond_to_missing?(method, include_private = false)
129
+ @multi.respond_to?(method, include_private)
130
+ end
131
+ end
132
+ end
133
+ end
134
+ end