model-context-protocol-rb 0.5.1 → 0.6.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +13 -1
- data/README.md +60 -25
- data/lib/model_context_protocol/server/cancellable.rb +5 -5
- data/lib/model_context_protocol/server/{mcp_logger.rb → client_logger.rb} +7 -10
- data/lib/model_context_protocol/server/configuration.rb +17 -34
- data/lib/model_context_protocol/server/global_config/server_logging.rb +78 -0
- data/lib/model_context_protocol/server/progressable.rb +43 -21
- data/lib/model_context_protocol/server/prompt.rb +12 -7
- data/lib/model_context_protocol/server/redis_pool_manager.rb +1 -1
- data/lib/model_context_protocol/server/resource.rb +7 -4
- data/lib/model_context_protocol/server/router.rb +8 -7
- data/lib/model_context_protocol/server/server_logger.rb +28 -0
- data/lib/model_context_protocol/server/stdio_transport/request_store.rb +17 -17
- data/lib/model_context_protocol/server/stdio_transport.rb +18 -12
- data/lib/model_context_protocol/server/streamable_http_transport/message_poller.rb +9 -9
- data/lib/model_context_protocol/server/streamable_http_transport/request_store.rb +36 -36
- data/lib/model_context_protocol/server/streamable_http_transport/server_request_store.rb +231 -0
- data/lib/model_context_protocol/server/streamable_http_transport.rb +419 -181
- data/lib/model_context_protocol/server/tool.rb +6 -5
- data/lib/model_context_protocol/server.rb +15 -13
- data/lib/model_context_protocol/version.rb +1 -1
- metadata +9 -6
|
@@ -4,13 +4,14 @@ module ModelContextProtocol
|
|
|
4
4
|
include ModelContextProtocol::Server::ContentHelpers
|
|
5
5
|
include ModelContextProtocol::Server::Progressable
|
|
6
6
|
|
|
7
|
-
attr_reader :arguments, :context, :
|
|
7
|
+
attr_reader :arguments, :context, :client_logger, :server_logger
|
|
8
8
|
|
|
9
|
-
def initialize(arguments,
|
|
9
|
+
def initialize(arguments, client_logger, server_logger, context = {})
|
|
10
10
|
validate!(arguments)
|
|
11
11
|
@arguments = arguments
|
|
12
12
|
@context = context
|
|
13
|
-
@
|
|
13
|
+
@client_logger = client_logger
|
|
14
|
+
@server_logger = server_logger
|
|
14
15
|
end
|
|
15
16
|
|
|
16
17
|
def call
|
|
@@ -90,8 +91,8 @@ module ModelContextProtocol
|
|
|
90
91
|
subclass.instance_variable_set(:@defined_arguments, @defined_arguments&.dup)
|
|
91
92
|
end
|
|
92
93
|
|
|
93
|
-
def call(arguments,
|
|
94
|
-
new(arguments,
|
|
94
|
+
def call(arguments, client_logger, server_logger, context = {})
|
|
95
|
+
new(arguments, client_logger, server_logger, context).call
|
|
95
96
|
rescue ArgumentError => error
|
|
96
97
|
raise ModelContextProtocol::Server::ParameterValidationError, error.message
|
|
97
98
|
end
|
|
@@ -127,8 +128,12 @@ module ModelContextProtocol
|
|
|
127
128
|
@prompt_instance.context
|
|
128
129
|
end
|
|
129
130
|
|
|
130
|
-
def
|
|
131
|
-
@prompt_instance.
|
|
131
|
+
def client_logger
|
|
132
|
+
@prompt_instance.client_logger
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def server_logger
|
|
136
|
+
@prompt_instance.server_logger
|
|
132
137
|
end
|
|
133
138
|
|
|
134
139
|
def user_message(&block)
|
|
@@ -3,11 +3,14 @@ module ModelContextProtocol
|
|
|
3
3
|
include ModelContextProtocol::Server::Cancellable
|
|
4
4
|
include ModelContextProtocol::Server::Progressable
|
|
5
5
|
|
|
6
|
-
attr_reader :mime_type, :uri
|
|
6
|
+
attr_reader :mime_type, :uri, :client_logger, :server_logger, :context
|
|
7
7
|
|
|
8
|
-
def initialize
|
|
8
|
+
def initialize(client_logger, server_logger, context = {})
|
|
9
9
|
@mime_type = self.class.mime_type
|
|
10
10
|
@uri = self.class.uri
|
|
11
|
+
@client_logger = client_logger
|
|
12
|
+
@server_logger = server_logger
|
|
13
|
+
@context = context
|
|
11
14
|
end
|
|
12
15
|
|
|
13
16
|
def call
|
|
@@ -71,8 +74,8 @@ module ModelContextProtocol
|
|
|
71
74
|
subclass.instance_variable_set(:@annotations, @annotations&.dup)
|
|
72
75
|
end
|
|
73
76
|
|
|
74
|
-
def call
|
|
75
|
-
new.call
|
|
77
|
+
def call(client_logger, server_logger, context = {})
|
|
78
|
+
new(client_logger, server_logger, context).call
|
|
76
79
|
end
|
|
77
80
|
|
|
78
81
|
def definition
|
|
@@ -20,23 +20,24 @@ module ModelContextProtocol
|
|
|
20
20
|
# @param request_store [Object] the request store for tracking cancellation
|
|
21
21
|
# @param session_id [String, nil] the session ID for HTTP transport
|
|
22
22
|
# @param transport [Object, nil] the transport for sending notifications
|
|
23
|
+
# @param stream_id [String, nil] the specific stream ID for targeted notifications
|
|
23
24
|
# @return [Object] the handler result, or nil if cancelled
|
|
24
|
-
def route(message, request_store: nil, session_id: nil, transport: nil)
|
|
25
|
+
def route(message, request_store: nil, session_id: nil, transport: nil, stream_id: nil)
|
|
25
26
|
method = message["method"]
|
|
26
27
|
handler = @handlers[method]
|
|
27
28
|
raise MethodNotFoundError, "Method not found: #{method}" unless handler
|
|
28
29
|
|
|
29
|
-
|
|
30
|
+
jsonrpc_request_id = message["id"]
|
|
30
31
|
progress_token = message.dig("params", "_meta", "progressToken")
|
|
31
32
|
|
|
32
|
-
if
|
|
33
|
-
request_store.register_request(
|
|
33
|
+
if jsonrpc_request_id && request_store
|
|
34
|
+
request_store.register_request(jsonrpc_request_id, session_id)
|
|
34
35
|
end
|
|
35
36
|
|
|
36
37
|
result = nil
|
|
37
38
|
begin
|
|
38
39
|
with_environment(@configuration&.environment_variables) do
|
|
39
|
-
context = {
|
|
40
|
+
context = {jsonrpc_request_id:, request_store:, session_id:, progress_token:, transport:, stream_id:}
|
|
40
41
|
|
|
41
42
|
Thread.current[:mcp_context] = context
|
|
42
43
|
|
|
@@ -45,8 +46,8 @@ module ModelContextProtocol
|
|
|
45
46
|
rescue Server::Cancellable::CancellationError
|
|
46
47
|
return nil
|
|
47
48
|
ensure
|
|
48
|
-
if
|
|
49
|
-
request_store.unregister_request(
|
|
49
|
+
if jsonrpc_request_id && request_store
|
|
50
|
+
request_store.unregister_request(jsonrpc_request_id)
|
|
50
51
|
end
|
|
51
52
|
|
|
52
53
|
Thread.current[:mcp_context] = nil
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
require "logger"
|
|
2
|
+
require "json"
|
|
3
|
+
|
|
4
|
+
module ModelContextProtocol
|
|
5
|
+
class Server::ServerLogger < Logger
|
|
6
|
+
class StdoutNotAllowedError < StandardError; end
|
|
7
|
+
|
|
8
|
+
attr_reader :logdev
|
|
9
|
+
|
|
10
|
+
def initialize(logdev: $stderr, level: Logger::INFO, formatter: nil, progname: "MCP-Server")
|
|
11
|
+
super(logdev)
|
|
12
|
+
@logdev = logdev
|
|
13
|
+
|
|
14
|
+
self.level = level
|
|
15
|
+
self.progname = progname
|
|
16
|
+
|
|
17
|
+
self.formatter = formatter || proc do |severity, datetime, progname, msg|
|
|
18
|
+
timestamp = datetime.strftime("%Y-%m-%d %H:%M:%S.%3N")
|
|
19
|
+
prog_name = progname ? "[#{progname}]" : ""
|
|
20
|
+
mcp_context = Thread.current[:mcp_context]
|
|
21
|
+
request_id = mcp_context&.dig(:jsonrpc_request_id)
|
|
22
|
+
request_id_str = request_id ? " [#{request_id}]" : ""
|
|
23
|
+
|
|
24
|
+
"[#{timestamp}] #{prog_name}#{request_id_str} #{severity}: #{msg}\n"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -10,12 +10,12 @@ module ModelContextProtocol
|
|
|
10
10
|
|
|
11
11
|
# Register a new request with its associated thread
|
|
12
12
|
#
|
|
13
|
-
# @param
|
|
13
|
+
# @param jsonrpc_request_id [String] the unique JSON-RPC request identifier
|
|
14
14
|
# @param thread [Thread] the thread processing this request (defaults to current thread)
|
|
15
15
|
# @return [void]
|
|
16
|
-
def register_request(
|
|
16
|
+
def register_request(jsonrpc_request_id, thread = Thread.current)
|
|
17
17
|
@mutex.synchronize do
|
|
18
|
-
@requests[
|
|
18
|
+
@requests[jsonrpc_request_id] = {
|
|
19
19
|
thread:,
|
|
20
20
|
cancelled: false,
|
|
21
21
|
started_at: Time.now
|
|
@@ -25,11 +25,11 @@ module ModelContextProtocol
|
|
|
25
25
|
|
|
26
26
|
# Mark a request as cancelled
|
|
27
27
|
#
|
|
28
|
-
# @param
|
|
28
|
+
# @param jsonrpc_request_id [String] the unique JSON-RPC request identifier
|
|
29
29
|
# @return [Boolean] true if request was found and marked cancelled, false otherwise
|
|
30
|
-
def mark_cancelled(
|
|
30
|
+
def mark_cancelled(jsonrpc_request_id)
|
|
31
31
|
@mutex.synchronize do
|
|
32
|
-
if (request = @requests[
|
|
32
|
+
if (request = @requests[jsonrpc_request_id])
|
|
33
33
|
request[:cancelled] = true
|
|
34
34
|
return true
|
|
35
35
|
end
|
|
@@ -39,31 +39,31 @@ module ModelContextProtocol
|
|
|
39
39
|
|
|
40
40
|
# Check if a request has been cancelled
|
|
41
41
|
#
|
|
42
|
-
# @param
|
|
42
|
+
# @param jsonrpc_request_id [String] the unique JSON-RPC request identifier
|
|
43
43
|
# @return [Boolean] true if the request is cancelled, false otherwise
|
|
44
|
-
def cancelled?(
|
|
44
|
+
def cancelled?(jsonrpc_request_id)
|
|
45
45
|
@mutex.synchronize do
|
|
46
|
-
@requests[
|
|
46
|
+
@requests[jsonrpc_request_id]&.fetch(:cancelled, false) || false
|
|
47
47
|
end
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
# Unregister a request (typically called when request completes)
|
|
51
51
|
#
|
|
52
|
-
# @param
|
|
52
|
+
# @param jsonrpc_request_id [String] the unique JSON-RPC request identifier
|
|
53
53
|
# @return [Hash, nil] the removed request data, or nil if not found
|
|
54
|
-
def unregister_request(
|
|
54
|
+
def unregister_request(jsonrpc_request_id)
|
|
55
55
|
@mutex.synchronize do
|
|
56
|
-
@requests.delete(
|
|
56
|
+
@requests.delete(jsonrpc_request_id)
|
|
57
57
|
end
|
|
58
58
|
end
|
|
59
59
|
|
|
60
60
|
# Get information about a specific request
|
|
61
61
|
#
|
|
62
|
-
# @param
|
|
62
|
+
# @param jsonrpc_request_id [String] the unique JSON-RPC request identifier
|
|
63
63
|
# @return [Hash, nil] request information or nil if not found
|
|
64
|
-
def get_request(
|
|
64
|
+
def get_request(jsonrpc_request_id)
|
|
65
65
|
@mutex.synchronize do
|
|
66
|
-
@requests[
|
|
66
|
+
@requests[jsonrpc_request_id]&.dup
|
|
67
67
|
end
|
|
68
68
|
end
|
|
69
69
|
|
|
@@ -85,9 +85,9 @@ module ModelContextProtocol
|
|
|
85
85
|
removed_ids = []
|
|
86
86
|
|
|
87
87
|
@mutex.synchronize do
|
|
88
|
-
@requests.delete_if do |
|
|
88
|
+
@requests.delete_if do |jsonrpc_request_id, data|
|
|
89
89
|
if data[:started_at] < cutoff_time
|
|
90
|
-
removed_ids <<
|
|
90
|
+
removed_ids << jsonrpc_request_id
|
|
91
91
|
true
|
|
92
92
|
else
|
|
93
93
|
false
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
require_relative "stdio_transport/request_store"
|
|
2
|
-
|
|
3
1
|
module ModelContextProtocol
|
|
4
2
|
class Server::StdioTransport
|
|
5
3
|
Response = Data.define(:id, :result) do
|
|
@@ -14,16 +12,19 @@ module ModelContextProtocol
|
|
|
14
12
|
end
|
|
15
13
|
end
|
|
16
14
|
|
|
17
|
-
attr_reader :router, :configuration, :request_store
|
|
15
|
+
attr_reader :router, :client_logger, :server_logger, :configuration, :request_store
|
|
18
16
|
|
|
19
17
|
def initialize(router:, configuration:)
|
|
20
18
|
@router = router
|
|
21
19
|
@configuration = configuration
|
|
22
|
-
@
|
|
20
|
+
@client_logger = configuration.client_logger
|
|
21
|
+
@server_logger = configuration.server_logger
|
|
22
|
+
@request_store = ModelContextProtocol::Server::StdioTransport::RequestStore.new
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
def handle
|
|
26
|
-
|
|
26
|
+
client_logger.connect_transport(self)
|
|
27
|
+
server_logger.info("Starting stdio transport handler")
|
|
27
28
|
|
|
28
29
|
loop do
|
|
29
30
|
line = receive_message
|
|
@@ -45,17 +46,21 @@ module ModelContextProtocol
|
|
|
45
46
|
send_message(Response[id: message["id"], result: result.serialized])
|
|
46
47
|
end
|
|
47
48
|
rescue ModelContextProtocol::Server::ParameterValidationError => validation_error
|
|
48
|
-
|
|
49
|
+
client_logger.error("Validation error", error: validation_error.message)
|
|
50
|
+
server_logger.error("Parameter validation failed in stdio transport: #{validation_error.message}")
|
|
49
51
|
send_message(
|
|
50
52
|
ErrorResponse[id: message["id"], error: {code: -32602, message: validation_error.message}]
|
|
51
53
|
)
|
|
52
54
|
rescue JSON::ParserError => parser_error
|
|
53
|
-
|
|
55
|
+
client_logger.error("Parser error", error: parser_error.message)
|
|
56
|
+
server_logger.error("JSON parsing failed in stdio transport: #{parser_error.message}")
|
|
54
57
|
send_message(
|
|
55
58
|
ErrorResponse[id: "", error: {code: -32700, message: parser_error.message}]
|
|
56
59
|
)
|
|
57
60
|
rescue => error
|
|
58
|
-
|
|
61
|
+
client_logger.error("Internal error", error: error.message, backtrace: error.backtrace.first(5))
|
|
62
|
+
server_logger.error("Internal error in stdio transport: #{error.message}")
|
|
63
|
+
server_logger.debug("Backtrace: #{error.backtrace.join("\n")}")
|
|
59
64
|
send_message(
|
|
60
65
|
ErrorResponse[id: message["id"], error: {code: -32603, message: error.message}]
|
|
61
66
|
)
|
|
@@ -72,7 +77,8 @@ module ModelContextProtocol
|
|
|
72
77
|
$stdout.puts(JSON.generate(notification))
|
|
73
78
|
$stdout.flush
|
|
74
79
|
rescue IOError => e
|
|
75
|
-
@configuration.
|
|
80
|
+
@configuration.client_logger.debug("Failed to send notification", error: e.message)
|
|
81
|
+
@configuration.server_logger.debug("Failed to send notification via stdio: #{e.message}")
|
|
76
82
|
end
|
|
77
83
|
|
|
78
84
|
private
|
|
@@ -84,10 +90,10 @@ module ModelContextProtocol
|
|
|
84
90
|
params = message["params"]
|
|
85
91
|
return unless params
|
|
86
92
|
|
|
87
|
-
|
|
88
|
-
return unless
|
|
93
|
+
jsonrpc_request_id = params["requestId"]
|
|
94
|
+
return unless jsonrpc_request_id
|
|
89
95
|
|
|
90
|
-
@request_store.mark_cancelled(
|
|
96
|
+
@request_store.mark_cancelled(jsonrpc_request_id)
|
|
91
97
|
rescue
|
|
92
98
|
nil
|
|
93
99
|
end
|
|
@@ -6,10 +6,10 @@ module ModelContextProtocol
|
|
|
6
6
|
POLL_INTERVAL = 0.1 # 100ms
|
|
7
7
|
BATCH_SIZE = 100
|
|
8
8
|
|
|
9
|
-
def initialize(redis_client, stream_registry,
|
|
9
|
+
def initialize(redis_client, stream_registry, client_logger, &message_delivery_block)
|
|
10
10
|
@redis = redis_client
|
|
11
11
|
@stream_registry = stream_registry
|
|
12
|
-
@
|
|
12
|
+
@client_logger = client_logger
|
|
13
13
|
@message_delivery_block = message_delivery_block
|
|
14
14
|
@running = false
|
|
15
15
|
@poll_thread = nil
|
|
@@ -22,14 +22,14 @@ module ModelContextProtocol
|
|
|
22
22
|
@poll_thread = Thread.new do
|
|
23
23
|
poll_loop
|
|
24
24
|
rescue => e
|
|
25
|
-
@
|
|
25
|
+
@client_logger.error("Message poller thread error", error: e.message, backtrace: e.backtrace.first(5))
|
|
26
26
|
sleep 1
|
|
27
27
|
retry if @running
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
@poll_thread.name = "MCP-MessagePoller" if @poll_thread.respond_to?(:name=)
|
|
31
31
|
|
|
32
|
-
@
|
|
32
|
+
@client_logger.debug("Message poller started")
|
|
33
33
|
end
|
|
34
34
|
|
|
35
35
|
def stop
|
|
@@ -37,11 +37,11 @@ module ModelContextProtocol
|
|
|
37
37
|
|
|
38
38
|
if @poll_thread&.alive?
|
|
39
39
|
@poll_thread.kill
|
|
40
|
-
@poll_thread.join(
|
|
40
|
+
@poll_thread.join(5)
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
@poll_thread = nil
|
|
44
|
-
@
|
|
44
|
+
@client_logger.debug("Message poller stopped")
|
|
45
45
|
end
|
|
46
46
|
|
|
47
47
|
def running?
|
|
@@ -55,7 +55,7 @@ module ModelContextProtocol
|
|
|
55
55
|
begin
|
|
56
56
|
poll_and_deliver_messages
|
|
57
57
|
rescue => e
|
|
58
|
-
@
|
|
58
|
+
@client_logger.error("Error in message polling", error: e.message)
|
|
59
59
|
end
|
|
60
60
|
|
|
61
61
|
sleep POLL_INTERVAL
|
|
@@ -91,9 +91,9 @@ module ModelContextProtocol
|
|
|
91
91
|
@message_delivery_block&.call(stream, message)
|
|
92
92
|
rescue IOError, Errno::EPIPE, Errno::ECONNRESET
|
|
93
93
|
@stream_registry.unregister_stream(session_id)
|
|
94
|
-
@
|
|
94
|
+
@client_logger.debug("Unregistered disconnected stream", session_id: session_id)
|
|
95
95
|
rescue => e
|
|
96
|
-
@
|
|
96
|
+
@client_logger.error("Error delivering message to stream",
|
|
97
97
|
session_id: session_id, error: e.message)
|
|
98
98
|
end
|
|
99
99
|
end
|
|
@@ -19,10 +19,10 @@ module ModelContextProtocol
|
|
|
19
19
|
|
|
20
20
|
# Register a new request with its associated session
|
|
21
21
|
#
|
|
22
|
-
# @param
|
|
22
|
+
# @param jsonrpc_request_id [String] the unique JSON-RPC request identifier
|
|
23
23
|
# @param session_id [String] the session identifier (can be nil for sessionless requests)
|
|
24
24
|
# @return [void]
|
|
25
|
-
def register_request(
|
|
25
|
+
def register_request(jsonrpc_request_id, session_id = nil)
|
|
26
26
|
request_data = {
|
|
27
27
|
session_id: session_id,
|
|
28
28
|
server_instance: @server_instance,
|
|
@@ -30,11 +30,11 @@ module ModelContextProtocol
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
@redis.multi do |multi|
|
|
33
|
-
multi.set("#{REQUEST_KEY_PREFIX}#{
|
|
33
|
+
multi.set("#{REQUEST_KEY_PREFIX}#{jsonrpc_request_id}",
|
|
34
34
|
request_data.to_json, ex: @ttl)
|
|
35
35
|
|
|
36
36
|
if session_id
|
|
37
|
-
multi.set("#{SESSION_KEY_PREFIX}#{session_id}:#{
|
|
37
|
+
multi.set("#{SESSION_KEY_PREFIX}#{session_id}:#{jsonrpc_request_id}",
|
|
38
38
|
true, ex: @ttl)
|
|
39
39
|
end
|
|
40
40
|
end
|
|
@@ -42,34 +42,34 @@ module ModelContextProtocol
|
|
|
42
42
|
|
|
43
43
|
# Mark a request as cancelled
|
|
44
44
|
#
|
|
45
|
-
# @param
|
|
45
|
+
# @param jsonrpc_request_id [String] the unique JSON-RPC request identifier
|
|
46
46
|
# @param reason [String] optional reason for cancellation
|
|
47
47
|
# @return [Boolean] true if cancellation was recorded
|
|
48
|
-
def mark_cancelled(
|
|
48
|
+
def mark_cancelled(jsonrpc_request_id, reason = nil)
|
|
49
49
|
cancellation_data = {
|
|
50
50
|
cancelled_at: Time.now.to_f,
|
|
51
51
|
reason: reason
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
result = @redis.set("#{CANCELLED_KEY_PREFIX}#{
|
|
54
|
+
result = @redis.set("#{CANCELLED_KEY_PREFIX}#{jsonrpc_request_id}",
|
|
55
55
|
cancellation_data.to_json, ex: @ttl)
|
|
56
56
|
result == "OK"
|
|
57
57
|
end
|
|
58
58
|
|
|
59
59
|
# Check if a request has been cancelled
|
|
60
60
|
#
|
|
61
|
-
# @param
|
|
61
|
+
# @param jsonrpc_request_id [String] the unique JSON-RPC request identifier
|
|
62
62
|
# @return [Boolean] true if the request is cancelled, false otherwise
|
|
63
|
-
def cancelled?(
|
|
64
|
-
@redis.exists("#{CANCELLED_KEY_PREFIX}#{
|
|
63
|
+
def cancelled?(jsonrpc_request_id)
|
|
64
|
+
@redis.exists("#{CANCELLED_KEY_PREFIX}#{jsonrpc_request_id}") == 1
|
|
65
65
|
end
|
|
66
66
|
|
|
67
67
|
# Get cancellation information for a request
|
|
68
68
|
#
|
|
69
|
-
# @param
|
|
69
|
+
# @param jsonrpc_request_id [String] the unique JSON-RPC request identifier
|
|
70
70
|
# @return [Hash, nil] cancellation data or nil if not cancelled
|
|
71
|
-
def get_cancellation_info(
|
|
72
|
-
data = @redis.get("#{CANCELLED_KEY_PREFIX}#{
|
|
71
|
+
def get_cancellation_info(jsonrpc_request_id)
|
|
72
|
+
data = @redis.get("#{CANCELLED_KEY_PREFIX}#{jsonrpc_request_id}")
|
|
73
73
|
data ? JSON.parse(data) : nil
|
|
74
74
|
rescue JSON::ParserError
|
|
75
75
|
nil
|
|
@@ -77,13 +77,13 @@ module ModelContextProtocol
|
|
|
77
77
|
|
|
78
78
|
# Unregister a request (typically called when request completes)
|
|
79
79
|
#
|
|
80
|
-
# @param
|
|
80
|
+
# @param jsonrpc_request_id [String] the unique JSON-RPC request identifier
|
|
81
81
|
# @return [void]
|
|
82
|
-
def unregister_request(
|
|
83
|
-
request_data = @redis.get("#{REQUEST_KEY_PREFIX}#{
|
|
82
|
+
def unregister_request(jsonrpc_request_id)
|
|
83
|
+
request_data = @redis.get("#{REQUEST_KEY_PREFIX}#{jsonrpc_request_id}")
|
|
84
84
|
|
|
85
|
-
keys_to_delete = ["#{REQUEST_KEY_PREFIX}#{
|
|
86
|
-
"#{CANCELLED_KEY_PREFIX}#{
|
|
85
|
+
keys_to_delete = ["#{REQUEST_KEY_PREFIX}#{jsonrpc_request_id}",
|
|
86
|
+
"#{CANCELLED_KEY_PREFIX}#{jsonrpc_request_id}"]
|
|
87
87
|
|
|
88
88
|
if request_data
|
|
89
89
|
begin
|
|
@@ -91,7 +91,7 @@ module ModelContextProtocol
|
|
|
91
91
|
session_id = data["session_id"]
|
|
92
92
|
|
|
93
93
|
if session_id
|
|
94
|
-
keys_to_delete << "#{SESSION_KEY_PREFIX}#{session_id}:#{
|
|
94
|
+
keys_to_delete << "#{SESSION_KEY_PREFIX}#{session_id}:#{jsonrpc_request_id}"
|
|
95
95
|
end
|
|
96
96
|
rescue JSON::ParserError
|
|
97
97
|
nil
|
|
@@ -103,10 +103,10 @@ module ModelContextProtocol
|
|
|
103
103
|
|
|
104
104
|
# Get information about a specific request
|
|
105
105
|
#
|
|
106
|
-
# @param
|
|
106
|
+
# @param jsonrpc_request_id [String] the unique JSON-RPC request identifier
|
|
107
107
|
# @return [Hash, nil] request information or nil if not found
|
|
108
|
-
def get_request(
|
|
109
|
-
data = @redis.get("#{REQUEST_KEY_PREFIX}#{
|
|
108
|
+
def get_request(jsonrpc_request_id)
|
|
109
|
+
data = @redis.get("#{REQUEST_KEY_PREFIX}#{jsonrpc_request_id}")
|
|
110
110
|
data ? JSON.parse(data) : nil
|
|
111
111
|
rescue JSON::ParserError
|
|
112
112
|
nil
|
|
@@ -114,10 +114,10 @@ module ModelContextProtocol
|
|
|
114
114
|
|
|
115
115
|
# Check if a request is currently active
|
|
116
116
|
#
|
|
117
|
-
# @param
|
|
117
|
+
# @param jsonrpc_request_id [String] the unique JSON-RPC request identifier
|
|
118
118
|
# @return [Boolean] true if the request is active, false otherwise
|
|
119
|
-
def active?(
|
|
120
|
-
@redis.exists("#{REQUEST_KEY_PREFIX}#{
|
|
119
|
+
def active?(jsonrpc_request_id)
|
|
120
|
+
@redis.exists("#{REQUEST_KEY_PREFIX}#{jsonrpc_request_id}") == 1
|
|
121
121
|
end
|
|
122
122
|
|
|
123
123
|
# Clean up all requests associated with a session
|
|
@@ -131,20 +131,20 @@ module ModelContextProtocol
|
|
|
131
131
|
return [] if request_keys.empty?
|
|
132
132
|
|
|
133
133
|
# Extract request IDs from the keys
|
|
134
|
-
|
|
134
|
+
jsonrpc_request_ids = request_keys.map do |key|
|
|
135
135
|
key.sub("#{SESSION_KEY_PREFIX}#{session_id}:", "")
|
|
136
136
|
end
|
|
137
137
|
|
|
138
138
|
# Delete all related keys
|
|
139
139
|
all_keys = []
|
|
140
|
-
|
|
141
|
-
all_keys << "#{REQUEST_KEY_PREFIX}#{
|
|
142
|
-
all_keys << "#{CANCELLED_KEY_PREFIX}#{
|
|
140
|
+
jsonrpc_request_ids.each do |jsonrpc_request_id|
|
|
141
|
+
all_keys << "#{REQUEST_KEY_PREFIX}#{jsonrpc_request_id}"
|
|
142
|
+
all_keys << "#{CANCELLED_KEY_PREFIX}#{jsonrpc_request_id}"
|
|
143
143
|
end
|
|
144
144
|
all_keys.concat(request_keys)
|
|
145
145
|
|
|
146
146
|
@redis.del(*all_keys) unless all_keys.empty?
|
|
147
|
-
|
|
147
|
+
jsonrpc_request_ids
|
|
148
148
|
end
|
|
149
149
|
|
|
150
150
|
# Get all active request IDs for a specific session
|
|
@@ -196,21 +196,21 @@ module ModelContextProtocol
|
|
|
196
196
|
|
|
197
197
|
# Refresh the TTL for an active request
|
|
198
198
|
#
|
|
199
|
-
# @param
|
|
199
|
+
# @param jsonrpc_request_id [String] the unique JSON-RPC request identifier
|
|
200
200
|
# @return [Boolean] true if TTL was refreshed, false if request doesn't exist
|
|
201
|
-
def refresh_request_ttl(
|
|
202
|
-
request_data = @redis.get("#{REQUEST_KEY_PREFIX}#{
|
|
201
|
+
def refresh_request_ttl(jsonrpc_request_id)
|
|
202
|
+
request_data = @redis.get("#{REQUEST_KEY_PREFIX}#{jsonrpc_request_id}")
|
|
203
203
|
return false unless request_data
|
|
204
204
|
|
|
205
205
|
@redis.multi do |multi|
|
|
206
|
-
multi.expire("#{REQUEST_KEY_PREFIX}#{
|
|
207
|
-
multi.expire("#{CANCELLED_KEY_PREFIX}#{
|
|
206
|
+
multi.expire("#{REQUEST_KEY_PREFIX}#{jsonrpc_request_id}", @ttl)
|
|
207
|
+
multi.expire("#{CANCELLED_KEY_PREFIX}#{jsonrpc_request_id}", @ttl)
|
|
208
208
|
|
|
209
209
|
begin
|
|
210
210
|
data = JSON.parse(request_data)
|
|
211
211
|
session_id = data["session_id"]
|
|
212
212
|
if session_id
|
|
213
|
-
multi.expire("#{SESSION_KEY_PREFIX}#{session_id}:#{
|
|
213
|
+
multi.expire("#{SESSION_KEY_PREFIX}#{session_id}:#{jsonrpc_request_id}", @ttl)
|
|
214
214
|
end
|
|
215
215
|
rescue JSON::ParserError
|
|
216
216
|
nil
|