actionmcp 0.80.1 → 0.82.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/README.md +62 -3
- data/lib/action_mcp/capability.rb +0 -1
- data/lib/action_mcp/client/base.rb +0 -1
- data/lib/action_mcp/client/transport.rb +0 -1
- data/lib/action_mcp/configuration.rb +4 -4
- data/lib/action_mcp/engine.rb +5 -0
- data/lib/action_mcp/json_rpc_handler_base.rb +3 -0
- data/lib/action_mcp/logging/level.rb +84 -0
- data/lib/action_mcp/logging/logger.rb +208 -0
- data/lib/action_mcp/logging/mixin.rb +149 -0
- data/lib/action_mcp/logging/null_logger.rb +94 -0
- data/lib/action_mcp/logging/state.rb +83 -0
- data/lib/action_mcp/logging.rb +81 -5
- data/lib/action_mcp/output_schema_builder.rb +184 -0
- data/lib/action_mcp/resource_template.rb +0 -1
- data/lib/action_mcp/schema_helpers.rb +36 -0
- data/lib/action_mcp/server/handlers/logging_handler.rb +43 -0
- data/lib/action_mcp/server/json_rpc_handler.rb +3 -0
- data/lib/action_mcp/server/tools.rb +4 -5
- data/lib/action_mcp/server/transport_handler.rb +0 -1
- data/lib/action_mcp/tool.rb +104 -19
- data/lib/action_mcp/tool_response.rb +26 -3
- data/lib/action_mcp/version.rb +1 -1
- data/lib/action_mcp.rb +0 -1
- data/lib/generators/action_mcp/tool/templates/tool.rb.erb +12 -1
- metadata +9 -1
@@ -0,0 +1,94 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionMCP
|
4
|
+
module Logging
|
5
|
+
# Null logger that performs no operations when logging is disabled
|
6
|
+
# Provides the same interface as Logger but with zero overhead
|
7
|
+
class NullLogger
|
8
|
+
# Initialize a null logger (no-op)
|
9
|
+
# @param args [Array] Any arguments (ignored)
|
10
|
+
def initialize(*args, **kwargs)
|
11
|
+
# Intentionally empty - no state needed
|
12
|
+
end
|
13
|
+
|
14
|
+
# Log methods - all no-ops that return nil immediately
|
15
|
+
def debug(*args, **kwargs, &block)
|
16
|
+
nil
|
17
|
+
end
|
18
|
+
|
19
|
+
def info(*args, **kwargs, &block)
|
20
|
+
nil
|
21
|
+
end
|
22
|
+
|
23
|
+
def notice(*args, **kwargs, &block)
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
|
27
|
+
def warning(*args, **kwargs, &block)
|
28
|
+
nil
|
29
|
+
end
|
30
|
+
alias_method :warn, :warning
|
31
|
+
|
32
|
+
def error(*args, **kwargs, &block)
|
33
|
+
nil
|
34
|
+
end
|
35
|
+
|
36
|
+
def critical(*args, **kwargs, &block)
|
37
|
+
nil
|
38
|
+
end
|
39
|
+
|
40
|
+
def alert(*args, **kwargs, &block)
|
41
|
+
nil
|
42
|
+
end
|
43
|
+
|
44
|
+
def emergency(*args, **kwargs, &block)
|
45
|
+
nil
|
46
|
+
end
|
47
|
+
|
48
|
+
# Level check methods - all return false (nothing will be logged)
|
49
|
+
def debug?
|
50
|
+
false
|
51
|
+
end
|
52
|
+
|
53
|
+
def info?
|
54
|
+
false
|
55
|
+
end
|
56
|
+
|
57
|
+
def notice?
|
58
|
+
false
|
59
|
+
end
|
60
|
+
|
61
|
+
def warning?
|
62
|
+
false
|
63
|
+
end
|
64
|
+
alias_method :warn?, :warning?
|
65
|
+
|
66
|
+
def error?
|
67
|
+
false
|
68
|
+
end
|
69
|
+
|
70
|
+
def critical?
|
71
|
+
false
|
72
|
+
end
|
73
|
+
|
74
|
+
def alert?
|
75
|
+
false
|
76
|
+
end
|
77
|
+
|
78
|
+
def emergency?
|
79
|
+
false
|
80
|
+
end
|
81
|
+
|
82
|
+
# Implement any other methods that might be called to avoid NoMethodError
|
83
|
+
def method_missing(method_name, *args, **kwargs, &block)
|
84
|
+
# Return nil for any unknown method calls
|
85
|
+
nil
|
86
|
+
end
|
87
|
+
|
88
|
+
def respond_to_missing?(method_name, include_private = false)
|
89
|
+
# Pretend to respond to any method to avoid issues
|
90
|
+
true
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "concurrent"
|
4
|
+
|
5
|
+
module ActionMCP
|
6
|
+
module Logging
|
7
|
+
# Thread-safe global state for MCP logging
|
8
|
+
class State
|
9
|
+
# Initialize with default values
|
10
|
+
def initialize
|
11
|
+
@enabled = Concurrent::AtomicBoolean.new(false)
|
12
|
+
@global_level = Concurrent::AtomicFixnum.new(Level::LEVELS[:warning])
|
13
|
+
end
|
14
|
+
|
15
|
+
# Check if logging is enabled
|
16
|
+
# @return [Boolean] true if enabled, false otherwise
|
17
|
+
def enabled?
|
18
|
+
@enabled.value
|
19
|
+
end
|
20
|
+
|
21
|
+
# Enable logging
|
22
|
+
# @return [Boolean] true (new value)
|
23
|
+
def enable!
|
24
|
+
@enabled.make_true
|
25
|
+
end
|
26
|
+
|
27
|
+
# Disable logging
|
28
|
+
# @return [Boolean] false (new value)
|
29
|
+
def disable!
|
30
|
+
@enabled.make_false
|
31
|
+
end
|
32
|
+
|
33
|
+
# Set enabled state
|
34
|
+
# @param value [Boolean] true to enable, false to disable
|
35
|
+
# @return [Boolean] the new value
|
36
|
+
def enabled=(value)
|
37
|
+
if value
|
38
|
+
enable!
|
39
|
+
else
|
40
|
+
disable!
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Get current minimum log level as integer
|
45
|
+
# @return [Integer] the current level (0-7)
|
46
|
+
def level
|
47
|
+
@global_level.value
|
48
|
+
end
|
49
|
+
|
50
|
+
# Get current minimum log level as symbol
|
51
|
+
# @return [Symbol] the current level symbol
|
52
|
+
def level_symbol
|
53
|
+
Level.name_for(@global_level.value)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Set minimum log level
|
57
|
+
# @param new_level [String, Symbol, Integer] the new level
|
58
|
+
# @return [Integer] the new level as integer
|
59
|
+
def level=(new_level)
|
60
|
+
level_int = Level.coerce(new_level)
|
61
|
+
@global_level.value = level_int
|
62
|
+
level_int
|
63
|
+
end
|
64
|
+
|
65
|
+
# Check if a message at the given level should be logged
|
66
|
+
# @param message_level [String, Symbol, Integer] the message level
|
67
|
+
# @return [Boolean] true if should be logged, false otherwise
|
68
|
+
def should_log?(message_level)
|
69
|
+
return false unless enabled?
|
70
|
+
|
71
|
+
message_level_int = Level.coerce(message_level)
|
72
|
+
message_level_int >= @global_level.value
|
73
|
+
end
|
74
|
+
|
75
|
+
# Reset to initial state (for testing)
|
76
|
+
# @return [void]
|
77
|
+
def reset!
|
78
|
+
disable!
|
79
|
+
self.level = :warning
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
data/lib/action_mcp/logging.rb
CHANGED
@@ -5,13 +5,89 @@ require "active_support/logger"
|
|
5
5
|
require "logger"
|
6
6
|
|
7
7
|
module ActionMCP
|
8
|
-
#
|
8
|
+
# Global MCP logging interface
|
9
9
|
module Logging
|
10
|
-
|
10
|
+
class << self
|
11
|
+
# Get the global logging state
|
12
|
+
# @return [ActionMCP::Logging::State] The global state
|
13
|
+
def state
|
14
|
+
@state ||= State.new
|
15
|
+
end
|
11
16
|
|
12
|
-
|
13
|
-
|
14
|
-
|
17
|
+
# Reset the global state (for testing)
|
18
|
+
# @return [void]
|
19
|
+
def reset!
|
20
|
+
@state = State.new
|
21
|
+
end
|
22
|
+
|
23
|
+
# Check if logging is enabled
|
24
|
+
# @return [Boolean] true if enabled
|
25
|
+
def enabled?
|
26
|
+
ActionMCP.configuration.logging_enabled && state.enabled?
|
27
|
+
end
|
28
|
+
|
29
|
+
# Enable MCP logging
|
30
|
+
# @return [Boolean] true
|
31
|
+
def enable!
|
32
|
+
ActionMCP.configuration.logging_enabled = true
|
33
|
+
state.enable!
|
34
|
+
end
|
35
|
+
|
36
|
+
# Disable MCP logging
|
37
|
+
# @return [Boolean] false
|
38
|
+
def disable!
|
39
|
+
state.disable!
|
40
|
+
end
|
41
|
+
|
42
|
+
# Get the current minimum log level
|
43
|
+
# @return [Symbol] current level
|
44
|
+
def level
|
45
|
+
state.level_symbol
|
46
|
+
end
|
47
|
+
|
48
|
+
# Set the minimum log level
|
49
|
+
# @param new_level [String, Symbol, Integer] the new level
|
50
|
+
# @return [Symbol] the new level as symbol
|
51
|
+
def level=(new_level)
|
52
|
+
state.level = new_level
|
53
|
+
state.level_symbol
|
54
|
+
end
|
55
|
+
alias_method :set_level, :level=
|
56
|
+
|
57
|
+
# Create a logger for the given session
|
58
|
+
# @param name [String, nil] Optional logger name
|
59
|
+
# @param session [ActionMCP::Session] The MCP session
|
60
|
+
# @return [ActionMCP::Logging::Logger, ActionMCP::Logging::NullLogger] logger instance
|
61
|
+
def logger(name: nil, session:)
|
62
|
+
if enabled?
|
63
|
+
Logger.new(name: name, session: session, state: state)
|
64
|
+
else
|
65
|
+
NullLogger.new
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Convenience method to get a logger for the current session context
|
70
|
+
# @param name [String, nil] Optional logger name
|
71
|
+
# @param execution_context [Hash] Context containing session
|
72
|
+
# @return [ActionMCP::Logging::Logger, ActionMCP::Logging::NullLogger] logger instance
|
73
|
+
def logger_for_context(name: nil, execution_context:)
|
74
|
+
session = execution_context[:session]
|
75
|
+
return NullLogger.new unless session
|
76
|
+
|
77
|
+
logger(name: name, session: session)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Initialize logging state based on configuration
|
82
|
+
def self.initialize_from_config!
|
83
|
+
# Always set the level from configuration
|
84
|
+
state.level = ActionMCP.configuration.logging_level
|
85
|
+
|
86
|
+
if ActionMCP.configuration.logging_enabled
|
87
|
+
state.enable!
|
88
|
+
else
|
89
|
+
state.disable!
|
90
|
+
end
|
15
91
|
end
|
16
92
|
end
|
17
93
|
end
|
@@ -0,0 +1,184 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "schema_helpers"
|
4
|
+
|
5
|
+
module ActionMCP
|
6
|
+
# DSL builder for creating output JSON Schema from Ruby-like syntax
|
7
|
+
# Unlike SchemaBuilder, this preserves nested structure for validation
|
8
|
+
class OutputSchemaBuilder
|
9
|
+
include SchemaHelpers
|
10
|
+
|
11
|
+
attr_reader :properties, :required
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@properties = {}
|
15
|
+
@required = []
|
16
|
+
end
|
17
|
+
|
18
|
+
# Define a property with specified type
|
19
|
+
# @param name [Symbol] Property name
|
20
|
+
# @param type [String] JSON Schema type
|
21
|
+
# @param required [Boolean] Whether the property is required
|
22
|
+
# @param description [String] Property description
|
23
|
+
# @param options [Hash] Additional JSON Schema options
|
24
|
+
def property(name, type: "string", required: false, description: nil, **options)
|
25
|
+
schema = { "type" => type }
|
26
|
+
schema["description"] = description if description
|
27
|
+
schema.merge!(options) if options.any?
|
28
|
+
|
29
|
+
@properties[name.to_s] = schema
|
30
|
+
@required << name.to_s if required
|
31
|
+
|
32
|
+
name.to_s
|
33
|
+
end
|
34
|
+
|
35
|
+
# Define a string property
|
36
|
+
def string(name = nil, required: false, description: nil, format: nil, enum: nil,
|
37
|
+
default: nil, min_length: nil, max_length: nil)
|
38
|
+
schema = { "type" => "string" }
|
39
|
+
schema["description"] = description if description
|
40
|
+
schema["format"] = format if format
|
41
|
+
schema["enum"] = enum if enum
|
42
|
+
schema["default"] = default if default
|
43
|
+
schema["minLength"] = min_length if min_length
|
44
|
+
schema["maxLength"] = max_length if max_length
|
45
|
+
|
46
|
+
if name
|
47
|
+
@properties[name.to_s] = schema
|
48
|
+
@required << name.to_s if required
|
49
|
+
name.to_s
|
50
|
+
else
|
51
|
+
# Return schema for use in array items or other contexts
|
52
|
+
schema
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Define a number property
|
57
|
+
def number(name, required: false, description: nil, minimum: nil,
|
58
|
+
maximum: nil, default: nil)
|
59
|
+
schema = { "type" => "number" }
|
60
|
+
schema["description"] = description if description
|
61
|
+
schema["minimum"] = minimum if minimum
|
62
|
+
schema["maximum"] = maximum if maximum
|
63
|
+
schema["default"] = default if default
|
64
|
+
|
65
|
+
@properties[name.to_s] = schema
|
66
|
+
@required << name.to_s if required
|
67
|
+
|
68
|
+
name.to_s
|
69
|
+
end
|
70
|
+
|
71
|
+
# Define a boolean property
|
72
|
+
def boolean(name, required: false, description: nil, default: nil)
|
73
|
+
schema = { "type" => "boolean" }
|
74
|
+
schema["description"] = description if description
|
75
|
+
schema["default"] = default unless default.nil?
|
76
|
+
|
77
|
+
@properties[name.to_s] = schema
|
78
|
+
@required << name.to_s if required
|
79
|
+
|
80
|
+
name.to_s
|
81
|
+
end
|
82
|
+
|
83
|
+
# Define an array property
|
84
|
+
# @param name [Symbol] Array property name
|
85
|
+
# @param description [String] Property description
|
86
|
+
# @param min_items [Integer] Minimum number of items
|
87
|
+
# @param max_items [Integer] Maximum number of items
|
88
|
+
# @param items [Hash] Items schema (if not using block)
|
89
|
+
# @param block [Proc] Block defining item schema
|
90
|
+
def array(name, description: nil, min_items: nil, max_items: nil, items: nil, &block)
|
91
|
+
schema = { "type" => "array" }
|
92
|
+
schema["description"] = description if description
|
93
|
+
schema["minItems"] = min_items if min_items
|
94
|
+
schema["maxItems"] = max_items if max_items
|
95
|
+
|
96
|
+
if block_given?
|
97
|
+
# Create nested builder for items
|
98
|
+
item_builder = OutputSchemaBuilder.new
|
99
|
+
result = item_builder.instance_eval(&block)
|
100
|
+
|
101
|
+
# If the block returned a schema directly (e.g., from string()),
|
102
|
+
# use that. Otherwise, build an object schema from properties.
|
103
|
+
if result.is_a?(Hash) && result["type"]
|
104
|
+
schema["items"] = result
|
105
|
+
elsif item_builder.properties.empty?
|
106
|
+
# Block didn't define properties, assume string items
|
107
|
+
schema["items"] = { "type" => "string" }
|
108
|
+
else
|
109
|
+
# Block defined object properties
|
110
|
+
item_schema = {
|
111
|
+
"type" => "object",
|
112
|
+
"properties" => item_builder.properties
|
113
|
+
}
|
114
|
+
item_schema["required"] = item_builder.required if item_builder.required.any?
|
115
|
+
schema["items"] = item_schema
|
116
|
+
end
|
117
|
+
elsif items
|
118
|
+
schema["items"] = items
|
119
|
+
else
|
120
|
+
# Default to string items
|
121
|
+
schema["items"] = { "type" => "string" }
|
122
|
+
end
|
123
|
+
|
124
|
+
@properties[name.to_s] = schema
|
125
|
+
|
126
|
+
name.to_s
|
127
|
+
end
|
128
|
+
|
129
|
+
# Define an object property
|
130
|
+
# @param name [Symbol] Object property name
|
131
|
+
# @param required [Boolean] Whether the object is required
|
132
|
+
# @param description [String] Property description
|
133
|
+
# @param additional_properties [Boolean, Hash] Whether to allow additional properties
|
134
|
+
# @param block [Proc] Block defining object properties
|
135
|
+
def object(name, required: false, description: nil, additional_properties: nil, &block)
|
136
|
+
raise ArgumentError, "Object definition requires a block" unless block_given?
|
137
|
+
|
138
|
+
# Create nested builder for object properties
|
139
|
+
object_builder = OutputSchemaBuilder.new
|
140
|
+
object_builder.instance_eval(&block)
|
141
|
+
|
142
|
+
schema = {
|
143
|
+
"type" => "object",
|
144
|
+
"properties" => object_builder.properties
|
145
|
+
}
|
146
|
+
schema["description"] = description if description
|
147
|
+
schema["required"] = object_builder.required if object_builder.required.any?
|
148
|
+
|
149
|
+
# Add additionalProperties if specified
|
150
|
+
add_additional_properties_to_schema(schema, additional_properties)
|
151
|
+
|
152
|
+
@properties[name.to_s] = schema
|
153
|
+
@required << name.to_s if required
|
154
|
+
|
155
|
+
name.to_s
|
156
|
+
end
|
157
|
+
|
158
|
+
# Set additionalProperties for the root schema
|
159
|
+
# @param enabled [Boolean, Hash] true to allow any additional properties,
|
160
|
+
# false to disallow them, or a Hash for typed additional properties
|
161
|
+
def additional_properties(enabled = nil)
|
162
|
+
if enabled.nil?
|
163
|
+
@additional_properties
|
164
|
+
else
|
165
|
+
@additional_properties = enabled
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
# Generate the final JSON Schema
|
170
|
+
def to_json_schema
|
171
|
+
schema = {
|
172
|
+
"type" => "object",
|
173
|
+
"properties" => @properties
|
174
|
+
}
|
175
|
+
|
176
|
+
schema["required"] = @required.uniq if @required.any?
|
177
|
+
|
178
|
+
# Add additionalProperties if configured
|
179
|
+
add_additional_properties_to_schema(schema, @additional_properties)
|
180
|
+
|
181
|
+
schema
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionMCP
|
4
|
+
# Shared utilities for JSON Schema manipulation
|
5
|
+
module SchemaHelpers
|
6
|
+
private
|
7
|
+
|
8
|
+
# Helper method to add additionalProperties to a schema hash
|
9
|
+
# @param schema [Hash] The schema hash to modify
|
10
|
+
# @param additional_properties_value [Boolean, Hash, nil] The additionalProperties configuration
|
11
|
+
# @return [Hash] The modified schema hash
|
12
|
+
def add_additional_properties_to_schema(schema, additional_properties_value)
|
13
|
+
return schema if additional_properties_value.nil?
|
14
|
+
|
15
|
+
# Use HashWithIndifferentAccess for checking, but modify original schema
|
16
|
+
indifferent_schema = schema.with_indifferent_access
|
17
|
+
|
18
|
+
# Only add additionalProperties if this is a typed schema
|
19
|
+
return schema unless indifferent_schema[:type]
|
20
|
+
|
21
|
+
additional_props = case additional_properties_value
|
22
|
+
when true then {}
|
23
|
+
when false then false
|
24
|
+
when Hash then additional_properties_value
|
25
|
+
end
|
26
|
+
|
27
|
+
# Add to original schema using its key style (symbol or string)
|
28
|
+
if schema.key?(:type) || schema.key?("type")
|
29
|
+
key = schema.key?(:type) ? :additionalProperties : "additionalProperties"
|
30
|
+
schema[key] = additional_props
|
31
|
+
end
|
32
|
+
|
33
|
+
schema
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionMCP
|
4
|
+
module Server
|
5
|
+
module Handlers
|
6
|
+
# Handler for MCP logging/setLevel requests
|
7
|
+
module LoggingHandler
|
8
|
+
# Handle logging/setLevel request
|
9
|
+
# @param id [String] The request ID
|
10
|
+
# @param params [Hash] Request parameters containing level
|
11
|
+
# @return [Hash] Empty hash on success
|
12
|
+
def handle_logging_set_level(id, params)
|
13
|
+
# Check if logging is enabled
|
14
|
+
unless ActionMCP.configuration.logging_enabled
|
15
|
+
transport.send_jsonrpc_error(id, :method_not_found, "Logging not enabled")
|
16
|
+
return
|
17
|
+
end
|
18
|
+
|
19
|
+
# Extract and validate level parameter
|
20
|
+
level = params[:level] || params["level"]
|
21
|
+
unless level
|
22
|
+
transport.send_jsonrpc_error(id, :invalid_params, "Missing required parameter: level")
|
23
|
+
return
|
24
|
+
end
|
25
|
+
|
26
|
+
begin
|
27
|
+
# Validate and set the new level
|
28
|
+
ActionMCP::Logging.set_level(level)
|
29
|
+
|
30
|
+
# Send successful response (empty object per MCP spec)
|
31
|
+
transport.send_jsonrpc_response(id, result: {})
|
32
|
+
rescue ArgumentError => e
|
33
|
+
# Invalid level
|
34
|
+
transport.send_jsonrpc_error(id, :invalid_params, "Invalid log level: #{e.message}")
|
35
|
+
rescue StandardError => e
|
36
|
+
# Internal error
|
37
|
+
transport.send_jsonrpc_error(id, :internal_error, "Internal error: #{e.message}")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -6,6 +6,7 @@ module ActionMCP
|
|
6
6
|
include Handlers::ResourceHandler
|
7
7
|
include Handlers::ToolHandler
|
8
8
|
include Handlers::PromptHandler
|
9
|
+
include Handlers::LoggingHandler
|
9
10
|
include ErrorHandling
|
10
11
|
include ErrorAware
|
11
12
|
|
@@ -55,6 +56,8 @@ module ActionMCP
|
|
55
56
|
process_tools(rpc_method, id, params)
|
56
57
|
when Methods::COMPLETION_COMPLETE
|
57
58
|
process_completion_complete(id, params)
|
59
|
+
when Methods::LOGGING_SET_LEVEL
|
60
|
+
handle_logging_set_level(id, params)
|
58
61
|
else
|
59
62
|
raise JSON_RPC::JsonRpcError.new(:method_not_found, message: "Method not found: #{rpc_method}")
|
60
63
|
end
|
@@ -89,12 +89,11 @@ module ActionMCP
|
|
89
89
|
end
|
90
90
|
|
91
91
|
if result.is_error
|
92
|
-
#
|
93
|
-
|
94
|
-
error_hash = result.to_h
|
95
|
-
send_jsonrpc_response(request_id, error: error_hash)
|
92
|
+
# Protocol error
|
93
|
+
send_jsonrpc_response(request_id, error: result.to_h)
|
96
94
|
else
|
97
|
-
|
95
|
+
# Success OR tool execution error - both are valid JSON-RPC responses
|
96
|
+
send_jsonrpc_response(request_id, result: result.to_h)
|
98
97
|
end
|
99
98
|
rescue ArgumentError => e
|
100
99
|
# Handle parameter validation errors
|