rails-active-mcp 3.1.11 → 3.3.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 +3 -0
- data/changelog.md +4 -0
- data/lib/generators/rails_active_mcp/install/templates/initializer.rb +5 -0
- data/lib/rails_active_mcp/configuration.rb +4 -2
- data/lib/rails_active_mcp/console_executor.rb +14 -7
- data/lib/rails_active_mcp/sdk/tools/safe_query_tool.rb +21 -3
- data/lib/rails_active_mcp/version.rb +1 -1
- data/lib/rails_active_mcp.rb +1 -1
- data/rails_active_mcp.gemspec +1 -1
- metadata +10 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5483e7e72b7d2ebd1e94669b20bd447fd1bd0b337a95f83b8a2f077569076e81
|
|
4
|
+
data.tar.gz: 0a5440105d9f5e11c92cbbfd6637f1dcf8e5264b7c8386de42e8969a44fa2871
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 12c56e9f36dd7f378232ba005b9431a6e9b88c31f44dd42f25ade608bf12d9ccf65eb49df8ab3b715a5149e81394ee6714630648c0217805a5c07d22de123e31
|
|
7
|
+
data.tar.gz: c57f0243963f7a74b528b240055f51925553d5fce3a85412bec19b41485dc06cc8d12412f90c6857eeceb13b983775311ebed1651147b5cf7e87937720331d75
|
data/README.md
CHANGED
|
@@ -332,6 +332,9 @@ Execute safe, read-only database queries:
|
|
|
332
332
|
|
|
333
333
|
**Example prompt:**
|
|
334
334
|
> "Get the 10 most recent orders"
|
|
335
|
+
> "Count active users"
|
|
336
|
+
|
|
337
|
+
You can pass an optional `where` hash to filter records before invoking the method. For example, `safe_query(model: "User", method: "count", where: { active: true })` runs `User.where(active: true).count`. The same applies to `sum`, `average`, `minimum`, `maximum`, `pluck`, and `exists?`.
|
|
335
338
|
|
|
336
339
|
### 4. `dry_run`
|
|
337
340
|
|
data/changelog.md
CHANGED
|
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- `safe_query` now accepts an optional `where` hash to filter records before invoking terminal methods like `count`, `sum`, `pluck`, `exists?`, `minimum`, `maximum`, and `average`. Example: `safe_query(model: "User", method: "count", where: { active: true })` runs `User.where(active: true).count`.
|
|
13
|
+
|
|
10
14
|
### Changed
|
|
11
15
|
|
|
12
16
|
## [3.1.0] - 2026-02-09
|
|
@@ -24,6 +24,11 @@ RailsActiveMcp.configure do |config|
|
|
|
24
24
|
# { pattern: /YourDangerousMethod/, description: "Your custom dangerous operation" }
|
|
25
25
|
# ]
|
|
26
26
|
|
|
27
|
+
# Replace the safety checker entirely with your own implementation.
|
|
28
|
+
# The class must respond to .new(config) and the resulting instance must
|
|
29
|
+
# respond to safe?(code), analyze(code), and read_only?(code).
|
|
30
|
+
# config.safety_checker = MyCustomSafetyChecker
|
|
31
|
+
|
|
27
32
|
# Environment-specific adjustments
|
|
28
33
|
case Rails.env
|
|
29
34
|
when 'production'
|
|
@@ -9,7 +9,7 @@ module RailsActiveMcp
|
|
|
9
9
|
|
|
10
10
|
# Safety and execution options
|
|
11
11
|
attr_accessor :safe_mode, :max_results, :log_executions, :audit_file, :enabled
|
|
12
|
-
attr_accessor :custom_safety_patterns, :allowed_models
|
|
12
|
+
attr_accessor :custom_safety_patterns, :allowed_models, :safety_checker
|
|
13
13
|
|
|
14
14
|
def initialize
|
|
15
15
|
@command_timeout = 30
|
|
@@ -23,6 +23,7 @@ module RailsActiveMcp
|
|
|
23
23
|
@audit_file = nil
|
|
24
24
|
@custom_safety_patterns = []
|
|
25
25
|
@allowed_models = []
|
|
26
|
+
@safety_checker = RailsActiveMcp::SafetyChecker
|
|
26
27
|
@enabled = true
|
|
27
28
|
end
|
|
28
29
|
|
|
@@ -41,7 +42,8 @@ module RailsActiveMcp
|
|
|
41
42
|
[true, false].include?(log_executions) &&
|
|
42
43
|
custom_safety_patterns.is_a?(Array) &&
|
|
43
44
|
allowed_models.is_a?(Array) &&
|
|
44
|
-
[true, false].include?(enabled)
|
|
45
|
+
[true, false].include?(enabled) &&
|
|
46
|
+
!safety_checker.nil?
|
|
45
47
|
end
|
|
46
48
|
|
|
47
49
|
def validate?
|
|
@@ -17,7 +17,7 @@ module RailsActiveMcp
|
|
|
17
17
|
|
|
18
18
|
def initialize(config)
|
|
19
19
|
@config = config
|
|
20
|
-
@safety_checker =
|
|
20
|
+
@safety_checker = config.safety_checker.new(config)
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
def execute(code, timeout: nil, safe_mode: nil, capture_output: true)
|
|
@@ -47,7 +47,7 @@ module RailsActiveMcp
|
|
|
47
47
|
}
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
-
def execute_safe_query(model:, method:, args: [], limit: nil)
|
|
50
|
+
def execute_safe_query(model:, method:, args: [], where: nil, limit: nil)
|
|
51
51
|
limit ||= @config.max_results
|
|
52
52
|
|
|
53
53
|
begin
|
|
@@ -61,11 +61,15 @@ module RailsActiveMcp
|
|
|
61
61
|
execute_with_rails_executor_and_connection do
|
|
62
62
|
model_class = model.to_s.constantize
|
|
63
63
|
|
|
64
|
+
# Build base relation, applying optional WHERE conditions
|
|
65
|
+
relation = model_class
|
|
66
|
+
relation = relation.where(where) if where.is_a?(Hash) && where.any?
|
|
67
|
+
|
|
64
68
|
# Build and execute query
|
|
65
69
|
query = if args.empty?
|
|
66
|
-
|
|
70
|
+
relation.public_send(method)
|
|
67
71
|
else
|
|
68
|
-
|
|
72
|
+
relation.public_send(method, *args)
|
|
69
73
|
end
|
|
70
74
|
|
|
71
75
|
# Apply limit for enumerable results
|
|
@@ -78,6 +82,7 @@ module RailsActiveMcp
|
|
|
78
82
|
model: model,
|
|
79
83
|
method: method,
|
|
80
84
|
args: args,
|
|
85
|
+
where: where,
|
|
81
86
|
result: serialize_result(result),
|
|
82
87
|
count: calculate_count(result),
|
|
83
88
|
executed_at: Time.now
|
|
@@ -90,17 +95,19 @@ module RailsActiveMcp
|
|
|
90
95
|
error_class: 'SafetyError',
|
|
91
96
|
model: model,
|
|
92
97
|
method: method,
|
|
93
|
-
args: args
|
|
98
|
+
args: args,
|
|
99
|
+
where: where
|
|
94
100
|
}
|
|
95
101
|
rescue StandardError => e
|
|
96
|
-
log_error(e, { model: model, method: method, args: args })
|
|
102
|
+
log_error(e, { model: model, method: method, args: args, where: where })
|
|
97
103
|
{
|
|
98
104
|
success: false,
|
|
99
105
|
error: e.message,
|
|
100
106
|
error_class: e.class.name,
|
|
101
107
|
model: model,
|
|
102
108
|
method: method,
|
|
103
|
-
args: args
|
|
109
|
+
args: args,
|
|
110
|
+
where: where
|
|
104
111
|
}
|
|
105
112
|
end
|
|
106
113
|
end
|
|
@@ -22,6 +22,11 @@ module RailsActiveMcp
|
|
|
22
22
|
type: 'array',
|
|
23
23
|
description: 'Arguments for the query method'
|
|
24
24
|
},
|
|
25
|
+
where: {
|
|
26
|
+
type: 'object',
|
|
27
|
+
description: 'Optional WHERE conditions as a hash (e.g. {"active": true, "status": "paid"}). Applied before the method is called.',
|
|
28
|
+
additionalProperties: true
|
|
29
|
+
},
|
|
25
30
|
limit: {
|
|
26
31
|
type: 'integer',
|
|
27
32
|
description: 'Limit results (default: 100)'
|
|
@@ -38,21 +43,25 @@ module RailsActiveMcp
|
|
|
38
43
|
open_world_hint: false
|
|
39
44
|
)
|
|
40
45
|
|
|
41
|
-
def self.call(model:, method:, server_context:,
|
|
42
|
-
|
|
46
|
+
def self.call(model:, method:, server_context:, **options)
|
|
47
|
+
args = options.fetch(:args, [])
|
|
48
|
+
where = options[:where]
|
|
49
|
+
limit = options.fetch(:limit, 100)
|
|
43
50
|
|
|
51
|
+
config = RailsActiveMcp.config
|
|
44
52
|
executor = RailsActiveMcp::ConsoleExecutor.new(config)
|
|
45
53
|
|
|
46
54
|
result = executor.execute_safe_query(
|
|
47
55
|
model: model,
|
|
48
56
|
method: method,
|
|
49
57
|
args: args,
|
|
58
|
+
where: where,
|
|
50
59
|
limit: limit
|
|
51
60
|
)
|
|
52
61
|
|
|
53
62
|
if result[:success]
|
|
54
63
|
output = []
|
|
55
|
-
output << "Query: #{model
|
|
64
|
+
output << "Query: #{format_query(model, method, args, where)}"
|
|
56
65
|
output << "Count: #{result[:count]}" if result[:count]
|
|
57
66
|
output << "Result: #{result[:result].inspect}"
|
|
58
67
|
|
|
@@ -64,6 +73,15 @@ module RailsActiveMcp
|
|
|
64
73
|
end
|
|
65
74
|
end
|
|
66
75
|
|
|
76
|
+
def self.format_query(model, method, args, where)
|
|
77
|
+
prefix = if where.is_a?(Hash) && where.any?
|
|
78
|
+
"#{model}.where(#{where.inspect})"
|
|
79
|
+
else
|
|
80
|
+
model.to_s
|
|
81
|
+
end
|
|
82
|
+
"#{prefix}.#{method}(#{args.join(', ')})"
|
|
83
|
+
end
|
|
84
|
+
|
|
67
85
|
def self.error_response(message)
|
|
68
86
|
MCP::Tool::Response.new(
|
|
69
87
|
[{ type: 'text', text: message }],
|
data/lib/rails_active_mcp.rb
CHANGED
data/rails_active_mcp.gemspec
CHANGED
|
@@ -63,7 +63,7 @@ Gem::Specification.new do |spec|
|
|
|
63
63
|
spec.add_dependency 'rails', '>= 7.0', '< 9.0'
|
|
64
64
|
|
|
65
65
|
# MCP SDK - Core protocol implementation (MCP 2025-11-25 spec)
|
|
66
|
-
spec.add_dependency 'mcp', '
|
|
66
|
+
spec.add_dependency 'mcp', '>= 0.9.2', '< 1.0.0'
|
|
67
67
|
|
|
68
68
|
# Core dependencies
|
|
69
69
|
spec.add_dependency 'json', '~> 2.0'
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rails-active-mcp
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.
|
|
4
|
+
version: 3.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Brandyn Britton
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-
|
|
10
|
+
date: 2026-05-15 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: concurrent-ruby
|
|
@@ -47,16 +47,22 @@ dependencies:
|
|
|
47
47
|
name: mcp
|
|
48
48
|
requirement: !ruby/object:Gem::Requirement
|
|
49
49
|
requirements:
|
|
50
|
-
- - "
|
|
50
|
+
- - ">="
|
|
51
51
|
- !ruby/object:Gem::Version
|
|
52
52
|
version: 0.9.2
|
|
53
|
+
- - "<"
|
|
54
|
+
- !ruby/object:Gem::Version
|
|
55
|
+
version: 1.0.0
|
|
53
56
|
type: :runtime
|
|
54
57
|
prerelease: false
|
|
55
58
|
version_requirements: !ruby/object:Gem::Requirement
|
|
56
59
|
requirements:
|
|
57
|
-
- - "
|
|
60
|
+
- - ">="
|
|
58
61
|
- !ruby/object:Gem::Version
|
|
59
62
|
version: 0.9.2
|
|
63
|
+
- - "<"
|
|
64
|
+
- !ruby/object:Gem::Version
|
|
65
|
+
version: 1.0.0
|
|
60
66
|
- !ruby/object:Gem::Dependency
|
|
61
67
|
name: json
|
|
62
68
|
requirement: !ruby/object:Gem::Requirement
|