actionmcp 0.82.0 → 0.83.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/lib/action_mcp/gateway.rb +16 -3
- data/lib/action_mcp/resource_template.rb +45 -33
- data/lib/action_mcp/tool.rb +6 -6
- data/lib/action_mcp/tools_registry.rb +7 -2
- data/lib/action_mcp/version.rb +1 -1
- metadata +5 -19
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 78f8c65c494089838368555cda5818fcf8cb74f1c9ecf46e8e93d830efab878d
|
|
4
|
+
data.tar.gz: c31e29bcaaf55f0f5dc662e144b859f22315cea77702bcda842cc03cb525eaa5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f582244915ed79650cb23424423d0bf80862f7d4a47ac0a563a830bc47b340cdaa3e2bc435934862fe16db6934b3328c633efb1130b4b5aa55a4e12ec12c94df
|
|
7
|
+
data.tar.gz: 93d40e3def9b7c16abacc08c6f866a91f2f43669c746bd6a577323af3d1496cbfda1421a036f8638aed370aa444b62e3a9ed64232dfc1c975bed0687498d677d
|
data/lib/action_mcp/gateway.rb
CHANGED
|
@@ -4,6 +4,11 @@ module ActionMCP
|
|
|
4
4
|
class UnauthorizedError < StandardError; end
|
|
5
5
|
|
|
6
6
|
class Gateway
|
|
7
|
+
# Whitelist of allowed identity attribute names to prevent method shadowing
|
|
8
|
+
# and unauthorized attribute assignment. Extend this list if you use custom
|
|
9
|
+
# identifier names in your GatewayIdentifier implementations.
|
|
10
|
+
ALLOWED_IDENTITY_KEYS = %w[user api_key jwt bearer token account session].freeze
|
|
11
|
+
|
|
7
12
|
class << self
|
|
8
13
|
# pluck in one or many GatewayIdentifier classes
|
|
9
14
|
def identified_by(*klasses)
|
|
@@ -69,13 +74,21 @@ module ActionMCP
|
|
|
69
74
|
|
|
70
75
|
def assign_identities(identities)
|
|
71
76
|
identities.each do |name, value|
|
|
77
|
+
name_str = name.to_s
|
|
78
|
+
|
|
79
|
+
# Validate identity key against whitelist to prevent method shadowing
|
|
80
|
+
unless ALLOWED_IDENTITY_KEYS.include?(name_str)
|
|
81
|
+
raise ArgumentError, "Invalid identity key: '#{name_str}'. " \
|
|
82
|
+
"Allowed keys: #{ALLOWED_IDENTITY_KEYS.join(', ')}"
|
|
83
|
+
end
|
|
84
|
+
|
|
72
85
|
# define accessor on the fly
|
|
73
86
|
self.class.attr_reader name unless respond_to?(name)
|
|
74
|
-
instance_variable_set("@#{
|
|
87
|
+
instance_variable_set("@#{name_str}", value)
|
|
75
88
|
|
|
76
89
|
# also set current context if you have one
|
|
77
|
-
ActionMCP::Current.public_send("#{
|
|
78
|
-
ActionMCP::Current.respond_to?("#{
|
|
90
|
+
ActionMCP::Current.public_send("#{name_str}=", value) if
|
|
91
|
+
ActionMCP::Current.respond_to?("#{name_str}=")
|
|
79
92
|
end
|
|
80
93
|
ActionMCP::Current.gateway = self if
|
|
81
94
|
ActionMCP::Current.respond_to?(:gateway=)
|
|
@@ -48,6 +48,11 @@ module ActionMCP
|
|
|
48
48
|
@required_parameters ||= []
|
|
49
49
|
end
|
|
50
50
|
|
|
51
|
+
# Cache compiled regex patterns for URI matching to avoid recompilation
|
|
52
|
+
def uri_regex_cache
|
|
53
|
+
@uri_regex_cache ||= {}
|
|
54
|
+
end
|
|
55
|
+
|
|
51
56
|
def parameter(name, description:, required: false, **options)
|
|
52
57
|
@parameters ||= {}
|
|
53
58
|
@parameters[name] = { description: description, required: required, **options }
|
|
@@ -139,48 +144,55 @@ module ActionMCP
|
|
|
139
144
|
|
|
140
145
|
# Extract parameters from a URI using the template pattern
|
|
141
146
|
def extract_params_from_uri(uri_string)
|
|
142
|
-
#
|
|
143
|
-
|
|
144
|
-
current_pos = 0
|
|
145
|
-
param_names = []
|
|
146
|
-
|
|
147
|
-
# Find all template parameters like {param_name}
|
|
148
|
-
@uri_template.scan(/\{([^}]+)\}/) do |param_name|
|
|
149
|
-
param_names << param_name[0]
|
|
150
|
-
|
|
151
|
-
# Get the position of this parameter in the template
|
|
152
|
-
param_start = @uri_template.index("{#{param_name[0]}}", current_pos)
|
|
153
|
-
|
|
154
|
-
# Add the text before the parameter (escaped)
|
|
155
|
-
if param_start > current_pos
|
|
156
|
-
prefix = Regexp.escape(@uri_template[current_pos...param_start])
|
|
157
|
-
regex_parts << prefix
|
|
158
|
-
end
|
|
147
|
+
# Check cache for compiled regex and param names
|
|
148
|
+
cache_entry = uri_regex_cache[@uri_template]
|
|
159
149
|
|
|
160
|
-
|
|
161
|
-
|
|
150
|
+
unless cache_entry
|
|
151
|
+
# Convert template parameters to named capture groups
|
|
152
|
+
regex_parts = []
|
|
153
|
+
current_pos = 0
|
|
154
|
+
param_names = []
|
|
162
155
|
|
|
163
|
-
#
|
|
164
|
-
|
|
165
|
-
|
|
156
|
+
# Find all template parameters like {param_name}
|
|
157
|
+
@uri_template.scan(/\{([^}]+)\}/) do |param_name|
|
|
158
|
+
param_names << param_name[0]
|
|
166
159
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
160
|
+
# Get the position of this parameter in the template
|
|
161
|
+
param_start = @uri_template.index("{#{param_name[0]}}", current_pos)
|
|
162
|
+
|
|
163
|
+
# Add the text before the parameter (escaped)
|
|
164
|
+
if param_start > current_pos
|
|
165
|
+
prefix = Regexp.escape(@uri_template[current_pos...param_start])
|
|
166
|
+
regex_parts << prefix
|
|
167
|
+
end
|
|
172
168
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
169
|
+
# Add the named capture group
|
|
170
|
+
regex_parts << "(?<#{param_name[0]}>[^/]+)"
|
|
171
|
+
|
|
172
|
+
# Update current position
|
|
173
|
+
current_pos = param_start + param_name[0].length + 2 # +2 for { and }
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Add any remaining text after the last parameter
|
|
177
|
+
if current_pos < @uri_template.length
|
|
178
|
+
suffix = Regexp.escape(@uri_template[current_pos..])
|
|
179
|
+
regex_parts << suffix
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Build the final regex and cache it
|
|
183
|
+
regex_pattern = regex_parts.join
|
|
184
|
+
regex = Regexp.new("^#{regex_pattern}$")
|
|
185
|
+
cache_entry = { regex: regex, param_names: param_names }
|
|
186
|
+
uri_regex_cache[@uri_template] = cache_entry
|
|
187
|
+
end
|
|
176
188
|
|
|
177
|
-
# Try to match the URI
|
|
178
|
-
match_data = regex.match(uri_string)
|
|
189
|
+
# Try to match the URI using cached regex
|
|
190
|
+
match_data = cache_entry[:regex].match(uri_string)
|
|
179
191
|
return nil unless match_data
|
|
180
192
|
|
|
181
193
|
# Extract named captures as parameters
|
|
182
194
|
params = {}
|
|
183
|
-
param_names.each do |name|
|
|
195
|
+
cache_entry[:param_names].each do |name|
|
|
184
196
|
params[name.to_sym] = match_data[name] if match_data[name]
|
|
185
197
|
end
|
|
186
198
|
|
data/lib/action_mcp/tool.rb
CHANGED
|
@@ -240,9 +240,9 @@ module ActionMCP
|
|
|
240
240
|
prop_definition.merge!(opts) if opts.any?
|
|
241
241
|
|
|
242
242
|
self._schema_properties = _schema_properties.merge(prop_name.to_s => prop_definition)
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
243
|
+
new_required = _required_properties.dup
|
|
244
|
+
new_required << prop_name.to_s if required
|
|
245
|
+
self._required_properties = new_required
|
|
246
246
|
|
|
247
247
|
# Map the JSON Schema type to an ActiveModel attribute type.
|
|
248
248
|
attribute prop_name, map_json_type_to_active_model_type(type), default: default
|
|
@@ -271,9 +271,9 @@ module ActionMCP
|
|
|
271
271
|
collection_definition[:description] = description if description && !description.empty?
|
|
272
272
|
|
|
273
273
|
self._schema_properties = _schema_properties.merge(prop_name.to_s => collection_definition)
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
274
|
+
new_required = _required_properties.dup
|
|
275
|
+
new_required << prop_name.to_s if required
|
|
276
|
+
self._required_properties = new_required
|
|
277
277
|
|
|
278
278
|
# Map the type - for number arrays, use our custom type instance
|
|
279
279
|
mapped_type = if type == "number"
|
|
@@ -26,8 +26,13 @@ module ActionMCP
|
|
|
26
26
|
# Handle parameter validation errors
|
|
27
27
|
error_response(:invalid_params, message: e.message)
|
|
28
28
|
rescue StandardError => e
|
|
29
|
-
#
|
|
30
|
-
|
|
29
|
+
# Hide error details in production to prevent information disclosure
|
|
30
|
+
message = if Rails.env.production?
|
|
31
|
+
"Tool execution failed"
|
|
32
|
+
else
|
|
33
|
+
"Tool execution failed: #{e.message}"
|
|
34
|
+
end
|
|
35
|
+
error_response(:invalid_params, message: message)
|
|
31
36
|
end
|
|
32
37
|
|
|
33
38
|
def item_klass
|
data/lib/action_mcp/version.rb
CHANGED
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.83.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Abdelkader Boudih
|
|
@@ -15,14 +15,14 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - ">="
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: 8.0.
|
|
18
|
+
version: 8.0.4
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - ">="
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: 8.0.
|
|
25
|
+
version: 8.0.4
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: concurrent-ruby
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -71,14 +71,14 @@ dependencies:
|
|
|
71
71
|
requirements:
|
|
72
72
|
- - ">="
|
|
73
73
|
- !ruby/object:Gem::Version
|
|
74
|
-
version: 8.0.
|
|
74
|
+
version: 8.0.4
|
|
75
75
|
type: :runtime
|
|
76
76
|
prerelease: false
|
|
77
77
|
version_requirements: !ruby/object:Gem::Requirement
|
|
78
78
|
requirements:
|
|
79
79
|
- - ">="
|
|
80
80
|
- !ruby/object:Gem::Version
|
|
81
|
-
version: 8.0.
|
|
81
|
+
version: 8.0.4
|
|
82
82
|
- !ruby/object:Gem::Dependency
|
|
83
83
|
name: zeitwerk
|
|
84
84
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -93,20 +93,6 @@ dependencies:
|
|
|
93
93
|
- - "~>"
|
|
94
94
|
- !ruby/object:Gem::Version
|
|
95
95
|
version: '2.6'
|
|
96
|
-
- !ruby/object:Gem::Dependency
|
|
97
|
-
name: ostruct
|
|
98
|
-
requirement: !ruby/object:Gem::Requirement
|
|
99
|
-
requirements:
|
|
100
|
-
- - ">="
|
|
101
|
-
- !ruby/object:Gem::Version
|
|
102
|
-
version: '0'
|
|
103
|
-
type: :runtime
|
|
104
|
-
prerelease: false
|
|
105
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
106
|
-
requirements:
|
|
107
|
-
- - ">="
|
|
108
|
-
- !ruby/object:Gem::Version
|
|
109
|
-
version: '0'
|
|
110
96
|
- !ruby/object:Gem::Dependency
|
|
111
97
|
name: json_schemer
|
|
112
98
|
requirement: !ruby/object:Gem::Requirement
|