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
data/lib/action_mcp/tool.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "action_mcp/types/float_array_type"
|
4
|
+
require "action_mcp/schema_helpers"
|
4
5
|
|
5
6
|
module ActionMCP
|
6
7
|
# Base class for defining tools.
|
@@ -10,6 +11,7 @@ module ActionMCP
|
|
10
11
|
class Tool < Capability
|
11
12
|
include ActionMCP::Callbacks
|
12
13
|
include ActionMCP::CurrentHelpers
|
14
|
+
extend ActionMCP::SchemaHelpers
|
13
15
|
|
14
16
|
# --------------------------------------------------------------------------
|
15
17
|
# Class Attributes for Tool Metadata and Schema
|
@@ -24,6 +26,9 @@ module ActionMCP
|
|
24
26
|
class_attribute :_output_schema, instance_accessor: false, default: nil
|
25
27
|
class_attribute :_meta, instance_accessor: false, default: {}
|
26
28
|
class_attribute :_requires_consent, instance_accessor: false, default: false
|
29
|
+
class_attribute :_output_schema_builder, instance_accessor: false, default: nil
|
30
|
+
class_attribute :_additional_properties, instance_accessor: false, default: nil
|
31
|
+
class_attribute :_cached_schema_property_keys, instance_accessor: false, default: nil
|
27
32
|
|
28
33
|
# --------------------------------------------------------------------------
|
29
34
|
# Tool Name and Description DSL
|
@@ -126,10 +131,27 @@ module ActionMCP
|
|
126
131
|
_annotations["openWorldHint"] == true
|
127
132
|
end
|
128
133
|
|
129
|
-
|
130
|
-
|
134
|
+
|
135
|
+
# Schema DSL for output structure
|
136
|
+
# @param block [Proc] Block containing output schema definition
|
137
|
+
# @return [Hash] The generated JSON Schema
|
138
|
+
def output_schema(&block)
|
139
|
+
return _output_schema unless block_given?
|
140
|
+
|
141
|
+
builder = OutputSchemaBuilder.new
|
142
|
+
builder.instance_eval(&block)
|
143
|
+
|
144
|
+
# Store both the builder and the generated schema
|
145
|
+
self._output_schema_builder = builder
|
146
|
+
self._output_schema = builder.to_json_schema
|
147
|
+
|
148
|
+
_output_schema
|
149
|
+
end
|
150
|
+
|
151
|
+
# Legacy output_schema method for backward compatibility
|
152
|
+
def output_schema_legacy(schema = nil)
|
131
153
|
if schema
|
132
|
-
raise NotImplementedError, "
|
154
|
+
raise NotImplementedError, "Legacy output schema not yet implemented. Use output_schema DSL instead!"
|
133
155
|
end
|
134
156
|
|
135
157
|
_output_schema
|
@@ -155,6 +177,45 @@ module ActionMCP
|
|
155
177
|
def requires_consent?
|
156
178
|
_requires_consent
|
157
179
|
end
|
180
|
+
|
181
|
+
# Sets or retrieves the additionalProperties setting for the input schema
|
182
|
+
# @param enabled [Boolean, Hash] true to allow any additional properties,
|
183
|
+
# false to disallow them, or a Hash for typed additional properties
|
184
|
+
def additional_properties(enabled = nil)
|
185
|
+
if enabled.nil?
|
186
|
+
_additional_properties
|
187
|
+
else
|
188
|
+
self._additional_properties = enabled
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
# Returns whether this tool accepts additional properties
|
193
|
+
def accepts_additional_properties?
|
194
|
+
!_additional_properties.nil? && _additional_properties != false
|
195
|
+
end
|
196
|
+
|
197
|
+
# Returns cached string keys for schema properties to avoid repeated conversions
|
198
|
+
def schema_property_keys
|
199
|
+
return _cached_schema_property_keys if _cached_schema_property_keys
|
200
|
+
|
201
|
+
self._cached_schema_property_keys = _schema_properties.keys.map(&:to_s)
|
202
|
+
_cached_schema_property_keys
|
203
|
+
end
|
204
|
+
|
205
|
+
# Clear cached keys when properties change - use metaprogramming to avoid duplication
|
206
|
+
[ :property, :collection ].each do |method_name|
|
207
|
+
define_method(method_name) do |prop_name, **opts|
|
208
|
+
invalidate_schema_cache
|
209
|
+
super(prop_name, **opts)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
private
|
214
|
+
|
215
|
+
# Invalidate cached schema property keys
|
216
|
+
def invalidate_schema_cache
|
217
|
+
self._cached_schema_property_keys = nil
|
218
|
+
end
|
158
219
|
end
|
159
220
|
|
160
221
|
# --------------------------------------------------------------------------
|
@@ -245,6 +306,9 @@ module ActionMCP
|
|
245
306
|
}
|
246
307
|
schema[:required] = _required_properties if _required_properties.any?
|
247
308
|
|
309
|
+
# Add additionalProperties if configured
|
310
|
+
add_additional_properties_to_schema(schema, _additional_properties)
|
311
|
+
|
248
312
|
result = {
|
249
313
|
name: tool_name,
|
250
314
|
description: description.presence,
|
@@ -270,11 +334,29 @@ module ActionMCP
|
|
270
334
|
|
271
335
|
# Override initialize to validate parameters before ActiveModel conversion
|
272
336
|
def initialize(attributes = {})
|
337
|
+
# Separate additional properties from defined attributes if enabled
|
338
|
+
if self.class.accepts_additional_properties?
|
339
|
+
defined_keys = self.class.schema_property_keys
|
340
|
+
# Use partition for single-pass separation - more efficient than except/slice
|
341
|
+
defined_attrs, additional_attrs = attributes.partition { |k, _|
|
342
|
+
defined_keys.include?(k.to_s)
|
343
|
+
}.map(&:to_h)
|
344
|
+
@_additional_params = additional_attrs
|
345
|
+
attributes = defined_attrs
|
346
|
+
else
|
347
|
+
@_additional_params = {}
|
348
|
+
end
|
349
|
+
|
273
350
|
# Validate parameters before ActiveModel processes them
|
274
351
|
validate_parameter_types(attributes)
|
275
352
|
super
|
276
353
|
end
|
277
354
|
|
355
|
+
# Returns additional parameters that were passed but not defined in the schema
|
356
|
+
def additional_params
|
357
|
+
@_additional_params || {}
|
358
|
+
end
|
359
|
+
|
278
360
|
# Public entry point for executing the tool
|
279
361
|
# Returns an array of Content objects collected from render calls
|
280
362
|
def call
|
@@ -290,9 +372,9 @@ module ActionMCP
|
|
290
372
|
rescue StandardError => e
|
291
373
|
# Show generic error message for HTTP requests, detailed for direct calls
|
292
374
|
error_message = if execution_context[:request].present?
|
293
|
-
|
375
|
+
"An unexpected error occurred."
|
294
376
|
else
|
295
|
-
|
377
|
+
e.message
|
296
378
|
end
|
297
379
|
@response.mark_as_error!(:internal_error, message: error_message)
|
298
380
|
end
|
@@ -327,11 +409,18 @@ module ActionMCP
|
|
327
409
|
end.join(', ')}, #{response_info}#{errors_info}>"
|
328
410
|
end
|
329
411
|
|
330
|
-
# Override render to collect Content objects
|
331
|
-
def render(**args)
|
332
|
-
|
333
|
-
|
334
|
-
|
412
|
+
# Override render to collect Content objects and support structured content
|
413
|
+
def render(structured: nil, **args)
|
414
|
+
if structured
|
415
|
+
# Render structured content
|
416
|
+
set_structured_content(structured)
|
417
|
+
structured
|
418
|
+
else
|
419
|
+
# Normal content rendering
|
420
|
+
content = super(**args) # Call Renderable's render method
|
421
|
+
@response.add(content) # Add to the response
|
422
|
+
content # Return the content for potential use in perform
|
423
|
+
end
|
335
424
|
end
|
336
425
|
|
337
426
|
# Override render_resource_link to collect ResourceLink objects
|
@@ -352,25 +441,21 @@ module ActionMCP
|
|
352
441
|
private
|
353
442
|
|
354
443
|
# Helper method for tools to manually report errors
|
444
|
+
# Uses the MCP-compliant tool execution error format
|
355
445
|
def report_error(message)
|
356
|
-
@response.
|
357
|
-
render text: message
|
446
|
+
@response.report_tool_error(message)
|
358
447
|
end
|
359
448
|
|
360
449
|
# Helper method to set structured content
|
361
450
|
def set_structured_content(content)
|
362
451
|
return unless @response
|
363
452
|
|
364
|
-
# Validate against output schema if defined
|
365
|
-
# TODO: Add JSON Schema validation here
|
366
|
-
# For now, just ensure it's a hash/object
|
367
|
-
if self.class._output_schema && !content.is_a?(Hash)
|
368
|
-
raise ArgumentError, "Structured content must be a hash/object when output_schema is defined"
|
369
|
-
end
|
370
|
-
|
371
453
|
@response.set_structured_content(content)
|
372
454
|
end
|
373
455
|
|
456
|
+
private
|
457
|
+
|
458
|
+
|
374
459
|
# Maps a JSON Schema type to an ActiveModel attribute type.
|
375
460
|
#
|
376
461
|
# @param type [String] The JSON Schema type.
|
@@ -3,7 +3,7 @@
|
|
3
3
|
module ActionMCP
|
4
4
|
# Manages the collection of content objects for tool results
|
5
5
|
class ToolResponse < BaseResponse
|
6
|
-
attr_reader :contents, :structured_content
|
6
|
+
attr_reader :contents, :structured_content, :tool_execution_error
|
7
7
|
|
8
8
|
delegate :empty?, :size, :each, :find, :map, to: :contents
|
9
9
|
|
@@ -11,6 +11,7 @@ module ActionMCP
|
|
11
11
|
super
|
12
12
|
@contents = []
|
13
13
|
@structured_content = nil
|
14
|
+
@tool_execution_error = false # Track if this is a tool execution error
|
14
15
|
end
|
15
16
|
|
16
17
|
# Add content to the response
|
@@ -24,6 +25,26 @@ module ActionMCP
|
|
24
25
|
@structured_content = content
|
25
26
|
end
|
26
27
|
|
28
|
+
# Report a tool execution error (as opposed to protocol error)
|
29
|
+
# This follows MCP spec for tool execution errors
|
30
|
+
def report_tool_error(message)
|
31
|
+
@tool_execution_error = true
|
32
|
+
add(Content::Text.new(message))
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_h(_options = nil)
|
36
|
+
if @tool_execution_error
|
37
|
+
result = {
|
38
|
+
isError: true,
|
39
|
+
content: @contents.map(&:to_h)
|
40
|
+
}
|
41
|
+
result[:structuredContent] = @structured_content if @structured_content
|
42
|
+
result
|
43
|
+
else
|
44
|
+
super
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
27
48
|
# Implementation of build_success_hash for ToolResponse
|
28
49
|
def build_success_hash
|
29
50
|
result = {
|
@@ -35,12 +56,14 @@ module ActionMCP
|
|
35
56
|
|
36
57
|
# Implementation of compare_with_same_class for ToolResponse
|
37
58
|
def compare_with_same_class(other)
|
38
|
-
contents == other.contents && is_error == other.is_error &&
|
59
|
+
contents == other.contents && is_error == other.is_error &&
|
60
|
+
structured_content == other.structured_content &&
|
61
|
+
tool_execution_error == other.tool_execution_error
|
39
62
|
end
|
40
63
|
|
41
64
|
# Implementation of hash_components for ToolResponse
|
42
65
|
def hash_components
|
43
|
-
[ contents, is_error, structured_content ]
|
66
|
+
[ contents, is_error, structured_content, tool_execution_error ]
|
44
67
|
end
|
45
68
|
|
46
69
|
# Pretty print for better debugging
|
data/lib/action_mcp/version.rb
CHANGED
data/lib/action_mcp.rb
CHANGED
@@ -32,8 +32,19 @@ class <%= class_name %> < ApplicationMCPTool
|
|
32
32
|
<% end %>
|
33
33
|
<% end %>
|
34
34
|
|
35
|
+
# Uncomment to allow additional properties beyond those defined above:
|
36
|
+
# additional_properties true # Allow any additional properties
|
37
|
+
# additional_properties false # Explicitly disallow additional properties
|
38
|
+
# additional_properties({"type" => "string"}) # Allow additional properties but restrict to strings
|
39
|
+
|
35
40
|
def perform
|
36
41
|
render(text: "Processing <%= properties.map { |p| p[:name] }.join(', ') %>")
|
42
|
+
|
43
|
+
# If additional_properties is enabled, you can access extra parameters:
|
44
|
+
# extra_params = additional_params
|
45
|
+
# extra_params.each do |key, value|
|
46
|
+
# render(text: "Additional #{key}: #{value}")
|
47
|
+
# end
|
37
48
|
|
38
49
|
# Optional outputs:
|
39
50
|
# render(audio: "<base64_data>", mime_type: "audio/mpeg")
|
@@ -41,6 +52,6 @@ class <%= class_name %> < ApplicationMCPTool
|
|
41
52
|
# render(resource: "file://path", mime_type: "application/json", text: "{}")
|
42
53
|
# render(resource: "file://path", mime_type: "application/octet-stream", blob: "<base64_data>")
|
43
54
|
rescue => e
|
44
|
-
|
55
|
+
report_error("Error: #{e.message}")
|
45
56
|
end
|
46
57
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: actionmcp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.82.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Abdelkader Boudih
|
@@ -206,6 +206,12 @@ files:
|
|
206
206
|
- lib/action_mcp/json_rpc_handler_base.rb
|
207
207
|
- lib/action_mcp/log_subscriber.rb
|
208
208
|
- lib/action_mcp/logging.rb
|
209
|
+
- lib/action_mcp/logging/level.rb
|
210
|
+
- lib/action_mcp/logging/logger.rb
|
211
|
+
- lib/action_mcp/logging/mixin.rb
|
212
|
+
- lib/action_mcp/logging/null_logger.rb
|
213
|
+
- lib/action_mcp/logging/state.rb
|
214
|
+
- lib/action_mcp/output_schema_builder.rb
|
209
215
|
- lib/action_mcp/prompt.rb
|
210
216
|
- lib/action_mcp/prompt_response.rb
|
211
217
|
- lib/action_mcp/prompts_registry.rb
|
@@ -216,6 +222,7 @@ files:
|
|
216
222
|
- lib/action_mcp/resource_response.rb
|
217
223
|
- lib/action_mcp/resource_template.rb
|
218
224
|
- lib/action_mcp/resource_templates_registry.rb
|
225
|
+
- lib/action_mcp/schema_helpers.rb
|
219
226
|
- lib/action_mcp/server.rb
|
220
227
|
- lib/action_mcp/server/active_record_session_store.rb
|
221
228
|
- lib/action_mcp/server/base_messaging.rb
|
@@ -226,6 +233,7 @@ files:
|
|
226
233
|
- lib/action_mcp/server/elicitation.rb
|
227
234
|
- lib/action_mcp/server/error_aware.rb
|
228
235
|
- lib/action_mcp/server/error_handling.rb
|
236
|
+
- lib/action_mcp/server/handlers/logging_handler.rb
|
229
237
|
- lib/action_mcp/server/handlers/prompt_handler.rb
|
230
238
|
- lib/action_mcp/server/handlers/resource_handler.rb
|
231
239
|
- lib/action_mcp/server/handlers/router.rb
|