rails-active-mcp 0.1.7 โ†’ 2.0.8

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.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +106 -279
  3. data/changelog.md +69 -0
  4. data/docs/DEBUGGING.md +5 -5
  5. data/docs/README.md +130 -142
  6. data/examples/rails_app_integration.md +405 -0
  7. data/exe/rails-active-mcp-server +153 -76
  8. data/gemfiles/rails_7.1.gemfile +34 -0
  9. data/lib/generators/rails_active_mcp/install/install_generator.rb +19 -39
  10. data/lib/generators/rails_active_mcp/install/templates/README.md +134 -188
  11. data/lib/generators/rails_active_mcp/install/templates/initializer.rb +65 -28
  12. data/lib/generators/rails_active_mcp/install/templates/mcp.ru +7 -3
  13. data/lib/rails_active_mcp/configuration.rb +37 -98
  14. data/lib/rails_active_mcp/console_executor.rb +13 -3
  15. data/lib/rails_active_mcp/engine.rb +36 -24
  16. data/lib/rails_active_mcp/sdk/server.rb +183 -0
  17. data/lib/rails_active_mcp/sdk/tools/console_execute_tool.rb +103 -0
  18. data/lib/rails_active_mcp/sdk/tools/dry_run_tool.rb +73 -0
  19. data/lib/rails_active_mcp/sdk/tools/model_info_tool.rb +106 -0
  20. data/lib/rails_active_mcp/sdk/tools/safe_query_tool.rb +77 -0
  21. data/lib/rails_active_mcp/tasks.rake +236 -80
  22. data/lib/rails_active_mcp/version.rb +1 -1
  23. data/lib/rails_active_mcp.rb +5 -11
  24. data/rails_active_mcp.gemspec +62 -11
  25. metadata +83 -24
  26. data/app/controllers/rails_active_mcp/mcp_controller.rb +0 -80
  27. data/lib/rails_active_mcp/mcp_server.rb +0 -383
  28. data/lib/rails_active_mcp/railtie.rb +0 -70
  29. data/lib/rails_active_mcp/stdio_server.rb +0 -517
  30. data/lib/rails_active_mcp/tools/console_execute_tool.rb +0 -61
  31. data/lib/rails_active_mcp/tools/dry_run_tool.rb +0 -41
  32. data/lib/rails_active_mcp/tools/model_info_tool.rb +0 -70
  33. data/lib/rails_active_mcp/tools/safe_query_tool.rb +0 -41
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'mcp'
4
+
5
+ module RailsActiveMcp
6
+ module Sdk
7
+ module Tools
8
+ class ModelInfoTool < MCP::Tool
9
+ description 'Get information about Rails models including schema and associations'
10
+
11
+ input_schema(
12
+ properties: {
13
+ model: {
14
+ type: 'string',
15
+ description: 'Model class name'
16
+ },
17
+ include_schema: {
18
+ type: 'boolean',
19
+ description: 'Include database schema information'
20
+ },
21
+ include_associations: {
22
+ type: 'boolean',
23
+ description: 'Include model associations'
24
+ },
25
+ include_validations: {
26
+ type: 'boolean',
27
+ description: 'Include model validations'
28
+ }
29
+ },
30
+ required: ['model']
31
+ )
32
+
33
+ annotations(
34
+ title: 'Rails Model Inspector',
35
+ destructive_hint: false,
36
+ read_only_hint: true,
37
+ idempotent_hint: true,
38
+ open_world_hint: false
39
+ )
40
+
41
+ def self.call(model:, server_context:, include_schema: true, include_associations: true,
42
+ include_validations: true)
43
+ config = RailsActiveMcp.config
44
+
45
+ begin
46
+ model_class = model.constantize
47
+
48
+ output = []
49
+ output << "Model: #{model}"
50
+ output << "Table: #{model_class.table_name}"
51
+ output << "Primary Key: #{model_class.primary_key}"
52
+
53
+ if include_schema
54
+ output << "\nSchema:"
55
+ model_class.columns.each do |column|
56
+ output << " #{column.name}: #{column.type} (#{column.sql_type})"
57
+ output << " - Null: #{column.null}"
58
+ output << " - Default: #{column.default}" if column.default
59
+ end
60
+ end
61
+
62
+ if include_associations
63
+ output << "\nAssociations:"
64
+ model_class.reflections.each do |name, reflection|
65
+ output << " #{name}: #{reflection.class.name.split('::').last} -> #{reflection.class_name}"
66
+ end
67
+ end
68
+
69
+ if include_validations
70
+ validations = {}
71
+ model_class.validators.each do |validator|
72
+ validator.attributes.each do |attribute|
73
+ validations[attribute] ||= []
74
+ validations[attribute] << validator.class.name.split('::').last
75
+ end
76
+ end
77
+
78
+ if validations.any?
79
+ output << "\nValidations:"
80
+ validations.each do |attr, validators|
81
+ output << " #{attr}: #{validators.join(', ')}"
82
+ end
83
+ end
84
+ end
85
+
86
+ MCP::Tool::Response.new([
87
+ { type: 'text', text: output.join("\n") }
88
+ ])
89
+ rescue NameError
90
+ error_response("Model '#{model}' not found")
91
+ rescue StandardError => e
92
+ error_response("Error analyzing model: #{e.message}")
93
+ end
94
+ end
95
+
96
+ private
97
+
98
+ def self.error_response(message)
99
+ MCP::Tool::Response.new([
100
+ { type: 'text', text: message }
101
+ ])
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'mcp'
4
+
5
+ module RailsActiveMcp
6
+ module Sdk
7
+ module Tools
8
+ class SafeQueryTool < MCP::Tool
9
+ description 'Execute safe read-only database queries on Rails models'
10
+
11
+ input_schema(
12
+ properties: {
13
+ model: {
14
+ type: 'string',
15
+ description: 'Model class name (e.g., "User", "Product")'
16
+ },
17
+ method: {
18
+ type: 'string',
19
+ description: 'Query method (find, where, count, etc.)'
20
+ },
21
+ args: {
22
+ type: 'array',
23
+ description: 'Arguments for the query method'
24
+ },
25
+ limit: {
26
+ type: 'integer',
27
+ description: 'Limit results (default: 100)'
28
+ }
29
+ },
30
+ required: %w[model method]
31
+ )
32
+
33
+ annotations(
34
+ title: 'Safe Query Executor',
35
+ destructive_hint: false,
36
+ read_only_hint: true,
37
+ idempotent_hint: true,
38
+ open_world_hint: false
39
+ )
40
+
41
+ def self.call(model:, method:, server_context:, args: [], limit: 100)
42
+ config = RailsActiveMcp.config
43
+
44
+ executor = RailsActiveMcp::ConsoleExecutor.new(config)
45
+
46
+ result = executor.execute_safe_query(
47
+ model: model,
48
+ method: method,
49
+ args: args,
50
+ limit: limit
51
+ )
52
+
53
+ if result[:success]
54
+ output = []
55
+ output << "Query: #{model}.#{method}(#{args.join(', ')})"
56
+ output << "Count: #{result[:count]}" if result[:count]
57
+ output << "Result: #{result[:result].inspect}"
58
+
59
+ MCP::Tool::Response.new([
60
+ { type: 'text', text: output.join("\n") }
61
+ ])
62
+ else
63
+ error_response(result[:error])
64
+ end
65
+ end
66
+
67
+ private
68
+
69
+ def self.error_response(message)
70
+ MCP::Tool::Response.new([
71
+ { type: 'text', text: message }
72
+ ])
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -1,5 +1,5 @@
1
1
  namespace :rails_active_mcp do
2
- desc "Check the safety of Ruby code"
2
+ desc 'Check the safety of Ruby code'
3
3
  task :check_safety, [:code] => :environment do |task, args|
4
4
  code = args[:code]
5
5
 
@@ -24,7 +24,7 @@ namespace :rails_active_mcp do
24
24
  end
25
25
  end
26
26
 
27
- desc "Execute Ruby code with safety checks"
27
+ desc 'Execute Ruby code with safety checks'
28
28
  task :execute, [:code] => :environment do |task, args|
29
29
  code = args[:code]
30
30
 
@@ -44,111 +44,267 @@ namespace :rails_active_mcp do
44
44
  puts "Error: #{result[:error]}"
45
45
  puts "Error class: #{result[:error_class]}" if result[:error_class]
46
46
  end
47
- rescue => e
47
+ rescue StandardError => e
48
48
  puts "Failed to execute: #{e.message}"
49
49
  exit 1
50
50
  end
51
51
  end
52
52
 
53
- desc "Test MCP tools"
54
- task :test_tools => :environment do
55
- puts "Testing Rails Active MCP tools..."
53
+ desc 'Test MCP tools'
54
+ task test_tools: :environment do
55
+ puts 'Testing Rails Active MCP tools...'
56
56
 
57
- # Test SafeQueryTool
58
- puts "\n1. Testing SafeQueryTool..."
59
- if defined?(User)
60
- tool = RailsActiveMcp::Tools::SafeQueryTool.new
61
- result = tool.call(model: "User", method: "count")
62
- puts " User.count: #{result[:success] ? result[:result] : result[:error]}"
57
+ # Test console execution
58
+ puts "\n1. Testing console_execute tool:"
59
+ result = RailsActiveMcp.execute('1 + 1')
60
+ puts " Simple math: #{result[:success] ? 'PASS' : 'FAIL'}"
61
+
62
+ # Test safety checking
63
+ puts "\n2. Testing safety checking:"
64
+ safe_result = RailsActiveMcp.safe?('User.count')
65
+ dangerous_result = RailsActiveMcp.safe?('User.delete_all')
66
+ puts " Safe code detection: #{safe_result ? 'PASS' : 'FAIL'}"
67
+ puts " Dangerous code detection: #{!dangerous_result ? 'PASS' : 'FAIL'}"
68
+
69
+ # Test Rails integration
70
+ puts "\n3. Testing Rails integration:"
71
+ if defined?(Rails) && Rails.respond_to?(:env)
72
+ puts " Rails environment: #{Rails.env} - PASS"
63
73
  else
64
- puts " Skipped (User model not found)"
74
+ puts ' Rails environment: NOT DETECTED - FAIL'
65
75
  end
66
76
 
67
- # Test ConsoleExecuteTool
68
- puts "\n2. Testing ConsoleExecuteTool..."
69
- tool = RailsActiveMcp::Tools::ConsoleExecuteTool.new
70
- result = tool.call(code: "1 + 1")
71
- puts " 1 + 1: #{result[:success] ? result[:return_value] : result[:error]}"
77
+ puts "\nAll tests completed!"
78
+ end
72
79
 
73
- # Test DryRunTool
74
- puts "\n3. Testing DryRunTool..."
75
- tool = RailsActiveMcp::Tools::DryRunTool.new
76
- result = tool.call(code: "User.delete_all")
77
- puts " User.delete_all analysis: #{result[:estimated_risk]} risk"
80
+ desc 'Benchmark MCP tools performance'
81
+ task benchmark: :environment do
82
+ require 'benchmark'
78
83
 
79
- puts "\nAll tools tested!"
80
- end
84
+ puts 'Rails Active MCP Performance Benchmark'
85
+ puts '=' * 50
81
86
 
82
- desc "Show configuration"
83
- task :config => :environment do
84
- config = RailsActiveMcp.config
87
+ # Benchmark safety checking
88
+ puts "\nSafety Checker Performance:"
89
+ Benchmark.bm(20) do |x|
90
+ x.report('Simple check:') do
91
+ 1000.times { RailsActiveMcp.safe?('User.count') }
92
+ end
85
93
 
86
- puts "Rails Active MCP Configuration:"
87
- puts " Enabled: #{config.enabled}"
88
- puts " Safe mode: #{config.safe_mode}"
89
- puts " Default timeout: #{config.default_timeout}s"
90
- puts " Max results: #{config.max_results}"
91
- puts " Log executions: #{config.log_executions}"
92
- puts " Audit file: #{config.audit_file}"
93
- puts " Enable mutation tools: #{config.enable_mutation_tools}"
94
- puts " Execution environment: #{config.execution_environment}"
94
+ x.report('Complex check:') do
95
+ 100.times { RailsActiveMcp.safe?('User.where(active: true).includes(:posts).limit(10)') }
96
+ end
95
97
 
96
- if config.allowed_models.any?
97
- puts " Allowed models: #{config.allowed_models.join(', ')}"
98
+ x.report('Dangerous check:') do
99
+ 1000.times { RailsActiveMcp.safe?('User.delete_all') }
100
+ end
98
101
  end
99
102
 
100
- if config.blocked_models.any?
101
- puts " Blocked models: #{config.blocked_models.join(', ')}"
102
- end
103
+ # Benchmark code execution
104
+ puts "\nCode Execution Performance:"
105
+ Benchmark.bm(20) do |x|
106
+ x.report('Simple math:') do
107
+ 100.times { RailsActiveMcp.execute('1 + 1') }
108
+ end
103
109
 
104
- if config.custom_safety_patterns.any?
105
- puts " Custom safety patterns: #{config.custom_safety_patterns.size}"
110
+ x.report('String operations:') do
111
+ 100.times { RailsActiveMcp.execute('"hello".upcase') }
112
+ end
106
113
  end
107
114
  end
108
115
 
109
- desc "View audit log"
110
- task :audit_log, [:lines] => :environment do |task, args|
111
- lines = args[:lines]&.to_i || 10
112
- audit_file = RailsActiveMcp.config.audit_file
116
+ desc 'Validate Rails Active MCP configuration'
117
+ task validate_config: :environment do
118
+ puts 'Validating Rails Active MCP configuration...'
119
+
120
+ config = RailsActiveMcp.config
121
+
122
+ if config.valid?
123
+ puts 'โœ… Configuration is valid'
113
124
 
114
- unless File.exist?(audit_file)
115
- puts "Audit log not found at: #{audit_file}"
125
+ puts "\nCurrent Settings:"
126
+ puts " Safe mode: #{config.safe_mode}"
127
+ puts " Command timeout: #{config.command_timeout}s"
128
+ puts " Max results: #{config.max_results}"
129
+ puts " Log executions: #{config.log_executions}"
130
+ puts " Log level: #{config.log_level}"
131
+ puts " Allowed models: #{config.allowed_models.any? ? config.allowed_models.join(', ') : 'All models allowed'}"
132
+ puts " Custom safety patterns: #{config.custom_safety_patterns.length} patterns"
133
+ else
134
+ puts 'โŒ Configuration is invalid'
135
+ puts 'Please check your config/initializers/rails_active_mcp.rb file'
116
136
  exit 1
117
137
  end
138
+ end
139
+
140
+ desc 'Generate example usage documentation'
141
+ task generate_examples: :environment do
142
+ examples_file = Rails.root.join('doc', 'rails_active_mcp_examples.md')
143
+ FileUtils.mkdir_p(File.dirname(examples_file))
144
+
145
+ content = <<~MARKDOWN
146
+ # Rails Active MCP Usage Examples
147
+
148
+ Generated on #{Date.current}
149
+
150
+ ## Safe Operations
151
+
152
+ These operations are considered safe and can be executed in safe mode:
153
+
154
+ ```ruby
155
+ # Basic model queries
156
+ User.count
157
+ User.all.limit(10)
158
+ User.where(active: true)
159
+ User.find(1)
160
+
161
+ # Associations
162
+ User.includes(:posts).limit(5)
163
+ Post.joins(:user).where(users: { active: true })
164
+
165
+ # Aggregations
166
+ Order.sum(:total_amount)
167
+ User.group(:status).count
168
+
169
+ # System information
170
+ Rails.env
171
+ Rails.version
172
+ Time.current
173
+ ```
174
+
175
+ ## Dangerous Operations (Blocked in Safe Mode)
176
+
177
+ These operations are blocked when safe_mode is enabled:
118
178
 
119
- puts "Last #{lines} entries from audit log:"
120
- puts "=" * 50
121
-
122
- File.readlines(audit_file).last(lines).each do |line|
123
- begin
124
- entry = JSON.parse(line)
125
- timestamp = entry['timestamp']
126
- code = entry['code']
127
- user = entry.dig('user', 'email') || entry.dig('user', 'environment') || 'unknown'
128
-
129
- puts "#{timestamp} [#{user}]: #{code}"
130
-
131
- if entry['type'] == 'error'
132
- puts " ERROR: #{entry['error']}"
133
- elsif entry['safety_check'] && !entry['safety_check']['safe']
134
- puts " SAFETY: #{entry['safety_check']['summary']}"
135
- end
136
- puts
137
- rescue JSON::ParserError
138
- puts "Invalid JSON entry: #{line}"
179
+ ```ruby
180
+ # Mass deletions
181
+ User.delete_all
182
+ User.destroy_all
183
+
184
+ # System commands
185
+ system('rm -rf /')
186
+ `ls -la`
187
+
188
+ # File operations
189
+ File.delete('important_file.txt')
190
+ FileUtils.rm_rf('/important/directory')
191
+
192
+ # Code evaluation
193
+ eval(user_input)
194
+ send(dynamic_method)
195
+ ```
196
+
197
+ ## Claude Desktop Usage Examples
198
+
199
+ Ask Claude these questions to interact with your Rails app:
200
+
201
+ - "How many users do we have?"
202
+ - "Show me the User model structure"
203
+ - "What are the most recent orders?"
204
+ - "Check if this code is safe: User.where(active: false).delete_all"
205
+ - "Find users created in the last week"
206
+ - "What associations does the Post model have?"
207
+
208
+ ## Configuration Examples
209
+
210
+ ### Development Configuration
211
+ ```ruby
212
+ RailsActiveMcp.configure do |config|
213
+ config.safe_mode = false
214
+ config.log_level = :debug
215
+ config.command_timeout = 60
216
+ config.max_results = 200
139
217
  end
140
- end
218
+ ```
219
+
220
+ ### Production Configuration
221
+ ```ruby
222
+ RailsActiveMcp.configure do |config|
223
+ config.safe_mode = true
224
+ config.log_level = :warn
225
+ config.command_timeout = 15
226
+ config.max_results = 50
227
+ config.allowed_models = %w[User Post Comment]
228
+ end
229
+ ```
230
+ MARKDOWN
231
+
232
+ File.write(examples_file, content)
233
+ puts "Examples generated at: #{examples_file}"
141
234
  end
142
235
 
143
- desc "Clear audit log"
144
- task :clear_audit_log => :environment do
145
- audit_file = RailsActiveMcp.config.audit_file
236
+ desc 'Install Claude Desktop configuration'
237
+ task install_claude_config: :environment do
238
+ config_template = {
239
+ mcpServers: {
240
+ 'rails-active-mcp' => {
241
+ command: Rails.root.join('bin', 'rails-active-mcp-wrapper').to_s,
242
+ cwd: Rails.root.to_s,
243
+ env: {
244
+ RAILS_ENV: Rails.env
245
+ }
246
+ }
247
+ }
248
+ }
146
249
 
147
- if File.exist?(audit_file)
148
- File.truncate(audit_file, 0)
149
- puts "Audit log cleared: #{audit_file}"
150
- else
151
- puts "Audit log not found: #{audit_file}"
250
+ puts 'Claude Desktop Configuration:'
251
+ puts JSON.pretty_generate(config_template)
252
+ puts ''
253
+ puts 'Add this to your Claude Desktop configuration file:'
254
+ puts ' macOS/Linux: ~/.config/claude-desktop/claude_desktop_config.json'
255
+ puts ' Windows: %APPDATA%\\Claude\\claude_desktop_config.json'
256
+ end
257
+
258
+ desc 'Show comprehensive status and diagnostics'
259
+ task status: :environment do
260
+ puts 'Rails Active MCP Status Report'
261
+ puts '=' * 50
262
+
263
+ # Basic environment info
264
+ puts "\n๐Ÿ“‹ Environment Information:"
265
+ puts " Rails version: #{Rails.version}"
266
+ puts " Ruby version: #{RUBY_VERSION}"
267
+ puts " Rails environment: #{Rails.env}"
268
+ puts " Rails Active MCP version: #{RailsActiveMcp::VERSION}"
269
+
270
+ # Configuration status
271
+ puts "\nโš™๏ธ Configuration Status:"
272
+ config = RailsActiveMcp.config
273
+ puts " Valid: #{config.valid? ? 'โœ…' : 'โŒ'}"
274
+ puts " Safe mode: #{config.safe_mode ? '๐Ÿ”’ Enabled' : 'โš ๏ธ Disabled'}"
275
+ puts " Timeout: #{config.command_timeout}s"
276
+ puts " Max results: #{config.max_results}"
277
+
278
+ # File status
279
+ puts "\n๐Ÿ“ File Status:"
280
+ files_to_check = [
281
+ 'bin/rails-active-mcp-server',
282
+ 'bin/rails-active-mcp-wrapper',
283
+ 'config/initializers/rails_active_mcp.rb'
284
+ ]
285
+
286
+ files_to_check.each do |file|
287
+ full_path = Rails.root.join(file)
288
+ if File.exist?(full_path)
289
+ executable = File.executable?(full_path)
290
+ puts " #{file}: โœ… #{executable ? '(executable)' : ''}"
291
+ else
292
+ puts " #{file}: โŒ Missing"
293
+ end
294
+ end
295
+
296
+ # Test basic functionality
297
+ puts "\n๐Ÿงช Functionality Test:"
298
+ begin
299
+ test_result = RailsActiveMcp.execute('1 + 1')
300
+ puts " Basic execution: #{test_result[:success] ? 'โœ…' : 'โŒ'}"
301
+ rescue StandardError => e
302
+ puts " Basic execution: โŒ (#{e.message})"
152
303
  end
304
+
305
+ puts "\n๐Ÿ”— Integration URLs:"
306
+ puts ' Documentation: https://github.com/goodpie/rails-active-mcp'
307
+ puts ' Issues: https://github.com/goodpie/rails-active-mcp/issues'
308
+ puts ' MCP Protocol: https://modelcontextprotocol.io'
153
309
  end
154
- end
310
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RailsActiveMcp
4
- VERSION = '0.1.7'
4
+ VERSION = '2.0.8'
5
5
  end
@@ -5,18 +5,17 @@ require_relative 'rails_active_mcp/version'
5
5
  require_relative 'rails_active_mcp/configuration'
6
6
  require_relative 'rails_active_mcp/safety_checker'
7
7
  require_relative 'rails_active_mcp/console_executor'
8
- require_relative 'rails_active_mcp/mcp_server'
8
+
9
+ # Load SDK server
10
+ require_relative 'rails_active_mcp/sdk/server'
9
11
 
10
12
  # Load Engine for Rails integration
11
13
  require_relative 'rails_active_mcp/engine' if defined?(Rails)
12
14
 
13
15
  module RailsActiveMcp
14
16
  class Error < StandardError; end
15
-
16
17
  class SafetyError < Error; end
17
-
18
18
  class ExecutionError < Error; end
19
-
20
19
  class TimeoutError < Error; end
21
20
 
22
21
  class << self
@@ -42,16 +41,11 @@ module RailsActiveMcp
42
41
  ConsoleExecutor.new(config).execute(code, **options)
43
42
  end
44
43
 
45
- # Access to MCP server instance
46
- def server
47
- @server ||= McpServer.new
48
- end
49
-
50
- # Logger accessor - configured by railtie or defaults to stderr
44
+ # Logger accessor - configured by engine or defaults to stderr
51
45
  attr_accessor :logger
52
46
 
53
47
  def logger
54
- @logger ||= Logger.new(STDERR).tap do |logger|
48
+ @logger ||= Logger.new($stderr).tap do |logger|
55
49
  logger.level = Logger::INFO
56
50
  logger.formatter = proc do |severity, datetime, progname, msg|
57
51
  "[#{datetime}] #{severity} -- RailsActiveMcp: #{msg}\n"