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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4838199f9074adc409c21a05e2ba54fa081d92d19492293f0958de3aca0ad43b
4
- data.tar.gz: 1717009443494a6d2790436b5c6831c30e149c5b0450abae0af848c4b1ee1c0c
3
+ metadata.gz: 5483e7e72b7d2ebd1e94669b20bd447fd1bd0b337a95f83b8a2f077569076e81
4
+ data.tar.gz: 0a5440105d9f5e11c92cbbfd6637f1dcf8e5264b7c8386de42e8969a44fa2871
5
5
  SHA512:
6
- metadata.gz: 47100e8a9e667123d5465aa1e165392009055e8c084665a19e756c1cd07a609febcfcbeac74d5cf2dba0fc7be703d476911819b64c66143a6489cfff462ca8ca
7
- data.tar.gz: ab592ed13aea076cb83b8a70969fd85f37ae3283fe01edb301fa0e748eaa4a7b830a34225ce368b73ff426c5e4239357517a99de5d9267d0f2594fa734f80064
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 = SafetyChecker.new(config)
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
- model_class.public_send(method)
70
+ relation.public_send(method)
67
71
  else
68
- model_class.public_send(method, *args)
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:, args: [], limit: 100)
42
- config = RailsActiveMcp.config
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}.#{method}(#{args.join(', ')})"
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 }],
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RailsActiveMcp
4
- VERSION = '3.1.11'
4
+ VERSION = '3.3.0'
5
5
  end
@@ -34,7 +34,7 @@ module RailsActiveMcp
34
34
 
35
35
  # Quick access to safety checker
36
36
  def safe?(code)
37
- SafetyChecker.new(config).safe?(code)
37
+ config.safety_checker.new(config).safe?(code)
38
38
  end
39
39
 
40
40
  # Quick execution method
@@ -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', '~> 0.9.2'
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.1.11
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-04-02 00:00:00.000000000 Z
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