actionmcp 0.50.7 → 0.50.10
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/app/controllers/action_mcp/application_controller.rb +20 -42
- data/lib/action_mcp/configuration.rb +0 -5
- data/lib/action_mcp/json_rpc_handler_base.rb +0 -4
- data/lib/action_mcp/server/capabilities.rb +5 -22
- data/lib/action_mcp/server/error_handling.rb +1 -10
- data/lib/action_mcp/server/handlers/prompt_handler.rb +2 -5
- data/lib/action_mcp/server/handlers/resource_handler.rb +3 -7
- data/lib/action_mcp/server/handlers/tool_handler.rb +2 -5
- data/lib/action_mcp/server/json_rpc_handler.rb +7 -46
- data/lib/action_mcp/sse_listener.rb +0 -16
- data/lib/action_mcp/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d09ebd13460c234fc23c4c509ffec0b9cb48ea6607108217be7309aee8dbf617
|
4
|
+
data.tar.gz: b269226676ee4974b7757093e721cca942eb7acb60e47fa72b6999b780a7bf7f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e6300709a72e3d8c54d23c4c2ba23bd751c2bac1b5c25f7b7ba5a8ff6d60f455571ef4b1a8e212b2cb22d05b3e06b803926d5b39d2daf9446b38d6296ea3277a
|
7
|
+
data.tar.gz: a2385c3f8910c27b8b36ce7d8cef73d6cf6db18922c0b6d9a7f51abd43d5a06a35f04e715cbfe32b1dbf7c8793a72f4d94b0142553d94e4360b9b1e5825164f9
|
@@ -160,8 +160,10 @@ module ActionMCP
|
|
160
160
|
|
161
161
|
transport_handler = Server::TransportHandler.new(session)
|
162
162
|
json_rpc_handler = Server::JsonRpcHandler.new(transport_handler)
|
163
|
-
|
164
|
-
|
163
|
+
|
164
|
+
result = json_rpc_handler.call(jsonrpc_params)
|
165
|
+
|
166
|
+
process_handler_results(result, session, session_initially_missing, is_initialize_request)
|
165
167
|
rescue ActionController::Live::ClientDisconnected, IOError => e
|
166
168
|
Rails.logger.debug "Unified SSE (POST): Client disconnected during response: #{e.message}"
|
167
169
|
begin
|
@@ -257,45 +259,24 @@ module ActionMCP
|
|
257
259
|
end
|
258
260
|
|
259
261
|
# Processes the results from the JsonRpcHandler.
|
260
|
-
def process_handler_results(
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
if results.is_a?(Hash)
|
265
|
-
request_id = results[:request_id] || results[:id]
|
266
|
-
request_id ||= results[:payload][:id] if results[:payload].is_a?(Hash) && results[:payload][:id]
|
262
|
+
def process_handler_results(result, session, session_initially_missing, is_initialize_request)
|
263
|
+
# Handle empty result (notifications)
|
264
|
+
if result.nil?
|
265
|
+
return head :accepted
|
267
266
|
end
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
when :responses
|
280
|
-
server_preference = ActionMCP.configuration.post_response_preference
|
281
|
-
use_sse = (server_preference == :sse)
|
282
|
-
add_session_header = is_initialize_request && session_initially_missing && session.persisted?
|
283
|
-
if use_sse
|
284
|
-
render_sse_response(result_payload, session, add_session_header)
|
285
|
-
else
|
286
|
-
render_json_response(result_payload, session, add_session_header)
|
287
|
-
end
|
267
|
+
|
268
|
+
# Convert to hash for rendering
|
269
|
+
payload = result.message_json
|
270
|
+
|
271
|
+
# Determine response format
|
272
|
+
server_preference = ActionMCP.configuration.post_response_preference
|
273
|
+
use_sse = (server_preference == :sse)
|
274
|
+
add_session_header = is_initialize_request && session_initially_missing && session.persisted?
|
275
|
+
|
276
|
+
if use_sse
|
277
|
+
render_sse_response(payload, session, add_session_header)
|
288
278
|
else
|
289
|
-
|
290
|
-
if is_notification
|
291
|
-
head :accepted
|
292
|
-
else
|
293
|
-
render json: {
|
294
|
-
jsonrpc: "2.0",
|
295
|
-
id: request_id,
|
296
|
-
result: result_payload
|
297
|
-
}, status: :ok
|
298
|
-
end
|
279
|
+
render_json_response(payload, session, add_session_header)
|
299
280
|
end
|
300
281
|
end
|
301
282
|
|
@@ -332,7 +313,6 @@ module ActionMCP
|
|
332
313
|
data = payload.is_a?(String) ? payload : MultiJson.dump(payload)
|
333
314
|
sse_event = "id: #{event_id}\ndata: #{data}\n\n"
|
334
315
|
sse.write(sse_event)
|
335
|
-
return unless ActionMCP.configuration.enable_sse_resumability
|
336
316
|
|
337
317
|
begin
|
338
318
|
session.store_sse_event(event_id, payload, session.max_stored_sse_events)
|
@@ -343,8 +323,6 @@ module ActionMCP
|
|
343
323
|
|
344
324
|
# Helper to clean up old SSE events for a session
|
345
325
|
def cleanup_old_sse_events(session)
|
346
|
-
return unless ActionMCP.configuration.enable_sse_resumability
|
347
|
-
|
348
326
|
begin
|
349
327
|
retention_period = session.sse_event_retention_period
|
350
328
|
count = session.cleanup_old_sse_events(retention_period)
|
@@ -29,7 +29,6 @@ module ActionMCP
|
|
29
29
|
# --- VibedIgnoreVersion Option ---
|
30
30
|
:vibed_ignore_version,
|
31
31
|
# --- SSE Resumability Options ---
|
32
|
-
:enable_sse_resumability,
|
33
32
|
:sse_event_retention_period,
|
34
33
|
:max_stored_sse_events
|
35
34
|
|
@@ -46,7 +45,6 @@ module ActionMCP
|
|
46
45
|
@vibed_ignore_version = false
|
47
46
|
|
48
47
|
# Resumability defaults
|
49
|
-
@enable_sse_resumability = true
|
50
48
|
@sse_event_retention_period = 15.minutes
|
51
49
|
@max_stored_sse_events = 100
|
52
50
|
end
|
@@ -141,9 +139,6 @@ module ActionMCP
|
|
141
139
|
|
142
140
|
capabilities[:resources] = { subscribe: @resources_subscribe } if filtered_resources.any?
|
143
141
|
|
144
|
-
# Add resumability capability if enabled
|
145
|
-
capabilities[:resumability] = { enabled: @enable_sse_resumability } if @enable_sse_resumability
|
146
|
-
|
147
142
|
capabilities
|
148
143
|
end
|
149
144
|
|
@@ -15,48 +15,32 @@ module ActionMCP
|
|
15
15
|
client_capabilities = params["capabilities"]
|
16
16
|
|
17
17
|
unless client_protocol_version.is_a?(String) && client_protocol_version.present?
|
18
|
-
send_jsonrpc_error(request_id, :invalid_params, "Missing or invalid 'protocolVersion'")
|
19
|
-
return { type: :error, id: request_id,
|
20
|
-
payload: { jsonrpc: "2.0", id: request_id, error: { code: -32_602, message: "Missing or invalid 'protocolVersion'" } } }
|
18
|
+
return send_jsonrpc_error(request_id, :invalid_params, "Missing or invalid 'protocolVersion'")
|
21
19
|
end
|
22
|
-
# Check if the protocol version is supported
|
23
20
|
unless ActionMCP.configuration.vibed_ignore_version || ActionMCP::SUPPORTED_VERSIONS.include?(client_protocol_version)
|
24
21
|
error_data = {
|
25
22
|
supported: ActionMCP::SUPPORTED_VERSIONS,
|
26
23
|
requested: client_protocol_version
|
27
24
|
}
|
28
|
-
send_jsonrpc_error(request_id, :invalid_params, "Unsupported protocol version", error_data)
|
29
|
-
return { type: :error, id: request_id,
|
30
|
-
payload: { jsonrpc: "2.0", id: request_id, error: { code: -32_602, message: "Unsupported protocol version", data: error_data } } }
|
25
|
+
return send_jsonrpc_error(request_id, :invalid_params, "Unsupported protocol version", error_data)
|
31
26
|
end
|
32
27
|
|
33
28
|
unless client_info.is_a?(Hash)
|
34
|
-
send_jsonrpc_error(request_id, :invalid_params, "Missing or invalid 'clientInfo'")
|
35
|
-
return { type: :error, id: request_id,
|
36
|
-
payload: { jsonrpc: "2.0", id: request_id, error: { code: -32_602, message: "Missing or invalid 'clientInfo'" } } }
|
29
|
+
return send_jsonrpc_error(request_id, :invalid_params, "Missing or invalid 'clientInfo'")
|
37
30
|
end
|
38
31
|
unless client_capabilities.is_a?(Hash)
|
39
|
-
send_jsonrpc_error(request_id, :invalid_params, "Missing or invalid 'capabilities'")
|
40
|
-
return { type: :error, id: request_id,
|
41
|
-
payload: { jsonrpc: "2.0", id: request_id, error: { code: -32_602, message: "Missing or invalid 'capabilities'" } } }
|
32
|
+
return send_jsonrpc_error(request_id, :invalid_params, "Missing or invalid 'capabilities'")
|
42
33
|
end
|
43
34
|
|
44
|
-
# Store client information
|
45
35
|
session.store_client_info(client_info)
|
46
36
|
session.store_client_capabilities(client_capabilities)
|
47
37
|
session.set_protocol_version(client_protocol_version)
|
48
38
|
|
49
|
-
# Initialize the session
|
50
39
|
unless session.initialize!
|
51
|
-
send_jsonrpc_error(request_id, :internal_error, "Failed to initialize session")
|
52
|
-
return { type: :error, id: request_id,
|
53
|
-
payload: { jsonrpc: "2.0", id: request_id, error: { code: -32_603, message: "Failed to initialize session" } } }
|
40
|
+
return send_jsonrpc_error(request_id, :internal_error, "Failed to initialize session")
|
54
41
|
end
|
55
42
|
|
56
|
-
# Send the successful response with the correct protocol version
|
57
43
|
capabilities_payload = session.server_capabilities_payload
|
58
|
-
# If vibed_ignore_version is true, always use the latest supported version in the response
|
59
|
-
# Otherwise, use the client's requested version
|
60
44
|
capabilities_payload[:protocolVersion] = if ActionMCP.configuration.vibed_ignore_version
|
61
45
|
PROTOCOL_VERSION
|
62
46
|
else
|
@@ -64,7 +48,6 @@ module ActionMCP
|
|
64
48
|
end
|
65
49
|
|
66
50
|
send_jsonrpc_response(request_id, result: capabilities_payload)
|
67
|
-
{ type: :responses, id: request_id, payload: { jsonrpc: "2.0", id: request_id, result: capabilities_payload } }
|
68
51
|
end
|
69
52
|
end
|
70
53
|
end
|
@@ -12,19 +12,10 @@ module ActionMCP
|
|
12
12
|
when Symbol
|
13
13
|
JSON_RPC::JsonRpcError.new(error_or_symbol, message: message, data: data)
|
14
14
|
else
|
15
|
-
# If it's already an error hash
|
16
15
|
error_or_symbol
|
17
16
|
end
|
18
17
|
|
19
|
-
|
20
|
-
type: :error,
|
21
|
-
request_id: id,
|
22
|
-
payload: {
|
23
|
-
jsonrpc: "2.0",
|
24
|
-
id: id,
|
25
|
-
error: json_rpc_error.to_h
|
26
|
-
}
|
27
|
-
}
|
18
|
+
JSON_RPC::Response.new(id: id, error: json_rpc_error)
|
28
19
|
end
|
29
20
|
|
30
21
|
# Helper method to create error response from any exception
|
@@ -32,14 +32,11 @@ module ActionMCP
|
|
32
32
|
def handle_prompts_get(id, params)
|
33
33
|
name = extract_name(params)
|
34
34
|
arguments = extract_arguments(params)
|
35
|
-
|
36
|
-
message = transport.send_prompts_get(id, name, arguments)
|
37
|
-
extract_message_payload(message, id)
|
35
|
+
transport.send_prompts_get(id, name, arguments)
|
38
36
|
end
|
39
37
|
|
40
38
|
def handle_prompts_list(id, _params)
|
41
|
-
|
42
|
-
extract_message_payload(message, id)
|
39
|
+
transport.send_prompts_list(id)
|
43
40
|
end
|
44
41
|
|
45
42
|
def extract_name(params)
|
@@ -33,20 +33,16 @@ module ActionMCP
|
|
33
33
|
end
|
34
34
|
|
35
35
|
def handle_resources_list(id, _params)
|
36
|
-
|
37
|
-
extract_message_payload(message, id)
|
36
|
+
transport.send_resources_list(id)
|
38
37
|
end
|
39
38
|
|
40
39
|
def handle_resources_templates_list(id, _params)
|
41
|
-
|
42
|
-
extract_message_payload(message, id)
|
40
|
+
transport.send_resource_templates_list(id)
|
43
41
|
end
|
44
42
|
|
45
43
|
def handle_resources_read(id, params)
|
46
44
|
validate_params_present(params, "Resource URI is required")
|
47
|
-
|
48
|
-
message = transport.send_resource_read(id, params)
|
49
|
-
extract_message_payload(message, id)
|
45
|
+
transport.send_resource_read(id, params)
|
50
46
|
end
|
51
47
|
|
52
48
|
def handle_resources_subscribe(id, params)
|
@@ -30,16 +30,13 @@ module ActionMCP
|
|
30
30
|
end
|
31
31
|
|
32
32
|
def handle_tools_list(id, params)
|
33
|
-
|
34
|
-
extract_message_payload(message, id)
|
33
|
+
transport.send_tools_list(id, params)
|
35
34
|
end
|
36
35
|
|
37
36
|
def handle_tools_call(id, params)
|
38
37
|
name = validate_required_param(params, "name", "Tool name is required")
|
39
38
|
arguments = extract_arguments(params)
|
40
|
-
|
41
|
-
message = transport.send_tools_call(id, name, arguments)
|
42
|
-
extract_message_payload(message, id)
|
39
|
+
transport.send_tools_call(id, name, arguments)
|
43
40
|
end
|
44
41
|
|
45
42
|
def extract_arguments(params)
|
@@ -32,10 +32,8 @@ module ActionMCP
|
|
32
32
|
params = request.params
|
33
33
|
|
34
34
|
with_error_handling(id) do
|
35
|
-
|
36
|
-
return if
|
37
|
-
|
38
|
-
# Route to appropriate handler
|
35
|
+
common_method = handle_common_methods(rpc_method, id, params)
|
36
|
+
return common_method if common_method
|
39
37
|
route_to_handler(rpc_method, id, params)
|
40
38
|
end
|
41
39
|
end
|
@@ -58,8 +56,7 @@ module ActionMCP
|
|
58
56
|
end
|
59
57
|
|
60
58
|
def handle_initialize(id, params)
|
61
|
-
|
62
|
-
extract_message_payload(message, id)
|
59
|
+
transport.send_capabilities(id, params)
|
63
60
|
end
|
64
61
|
|
65
62
|
def handle_notification(notification)
|
@@ -67,29 +64,17 @@ module ActionMCP
|
|
67
64
|
params = notification.params || {}
|
68
65
|
|
69
66
|
process_notifications(method_name, params)
|
70
|
-
|
67
|
+
nil
|
71
68
|
end
|
72
69
|
|
73
70
|
def handle_response(response)
|
74
71
|
Rails.logger.debug("Received response: #{response.inspect}")
|
75
|
-
|
76
|
-
{
|
77
|
-
type: :responses,
|
78
|
-
request_id: response.id,
|
79
|
-
payload: build_response_payload(response)
|
80
|
-
}
|
72
|
+
response
|
81
73
|
end
|
82
74
|
|
83
|
-
def process_completion_complete(id, params)
|
84
|
-
params ||= {}
|
85
75
|
|
86
|
-
|
87
|
-
|
88
|
-
if result.is_a?(ActionMCP::Session::Message)
|
89
|
-
extract_message_payload(result, id)
|
90
|
-
else
|
91
|
-
wrap_transport_result(result, id)
|
92
|
-
end
|
76
|
+
def process_completion_complete(id, params)
|
77
|
+
transport.send_jsonrpc_response(id, result: build_completion_result)
|
93
78
|
end
|
94
79
|
|
95
80
|
def process_notifications(rpc_method, params)
|
@@ -101,30 +86,6 @@ module ActionMCP
|
|
101
86
|
end
|
102
87
|
end
|
103
88
|
|
104
|
-
def extract_message_payload(message, id)
|
105
|
-
if message.is_a?(ActionMCP::Session::Message)
|
106
|
-
{
|
107
|
-
type: :responses,
|
108
|
-
request_id: id,
|
109
|
-
payload: message.message_json
|
110
|
-
}
|
111
|
-
else
|
112
|
-
message
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
|
-
def wrap_transport_result(transport_result, id)
|
117
|
-
if transport_result.is_a?(Hash) && transport_result[:type]
|
118
|
-
transport_result
|
119
|
-
else
|
120
|
-
{
|
121
|
-
type: :responses,
|
122
|
-
request_id: id,
|
123
|
-
payload: transport_result
|
124
|
-
}
|
125
|
-
end
|
126
|
-
end
|
127
|
-
|
128
89
|
def build_response_payload(response)
|
129
90
|
{
|
130
91
|
jsonrpc: "2.0",
|
@@ -56,22 +56,6 @@ module ActionMCP
|
|
56
56
|
if raw_message.is_a?(String) && valid_json_format?(raw_message)
|
57
57
|
message = MultiJson.load(raw_message)
|
58
58
|
callback&.call(message)
|
59
|
-
elsif raw_message.respond_to?(:message) && raw_message.message.is_a?(String) && valid_json_format?(raw_message.message)
|
60
|
-
message = MultiJson.load(raw_message.message)
|
61
|
-
callback&.call(message)
|
62
|
-
elsif raw_message.respond_to?(:to_json)
|
63
|
-
# Try to serialize the message object to JSON if it responds to to_json
|
64
|
-
message_json = raw_message.to_json
|
65
|
-
if valid_json_format?(message_json)
|
66
|
-
message = MultiJson.load(message_json)
|
67
|
-
callback&.call(message)
|
68
|
-
else
|
69
|
-
Rails.logger.warn "SSEListener: Message cannot be converted to valid JSON"
|
70
|
-
end
|
71
|
-
else
|
72
|
-
# Log that we received an invalid message format
|
73
|
-
display_message = raw_message.to_s[0..100]
|
74
|
-
Rails.logger.warn "SSEListener: Received invalid JSON format: #{display_message}..."
|
75
59
|
end
|
76
60
|
rescue StandardError => e
|
77
61
|
Rails.logger.error "SSEListener: Error processing message: #{e.message}"
|
data/lib/action_mcp/version.rb
CHANGED