rails-active-mcp 0.1.6 → 2.0.7

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 (31) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +106 -279
  3. data/changelog.md +69 -0
  4. data/claude_desktop_config.json +12 -0
  5. data/docs/DEBUGGING.md +40 -8
  6. data/docs/GENERATOR_TESTING.md +121 -0
  7. data/docs/README.md +130 -142
  8. data/exe/rails-active-mcp-server +176 -65
  9. data/lib/generators/rails_active_mcp/install/install_generator.rb +123 -3
  10. data/lib/generators/rails_active_mcp/install/templates/README.md +34 -128
  11. data/lib/generators/rails_active_mcp/install/templates/initializer.rb +37 -38
  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 +202 -78
  15. data/lib/rails_active_mcp/engine.rb +36 -8
  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/version.rb +1 -1
  22. data/lib/rails_active_mcp.rb +10 -11
  23. data/rails_active_mcp.gemspec +8 -4
  24. metadata +43 -17
  25. data/lib/rails_active_mcp/mcp_server.rb +0 -374
  26. data/lib/rails_active_mcp/railtie.rb +0 -48
  27. data/lib/rails_active_mcp/stdio_server.rb +0 -467
  28. data/lib/rails_active_mcp/tools/console_execute_tool.rb +0 -61
  29. data/lib/rails_active_mcp/tools/dry_run_tool.rb +0 -41
  30. data/lib/rails_active_mcp/tools/model_info_tool.rb +0 -70
  31. data/lib/rails_active_mcp/tools/safe_query_tool.rb +0 -41
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: 0.1.6
4
+ version: 2.0.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brandyn Britton
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-06-26 00:00:00.000000000 Z
10
+ date: 2025-06-27 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: concurrent-ruby
@@ -15,28 +15,48 @@ dependencies:
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: 1.3.5
18
+ version: '1.3'
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: 1.3.5
25
+ version: '1.3'
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: rails
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '6.1'
33
+ - - "<"
34
+ - !ruby/object:Gem::Version
35
+ version: '9.0'
36
+ type: :runtime
37
+ prerelease: false
38
+ version_requirements: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: '6.1'
43
+ - - "<"
44
+ - !ruby/object:Gem::Version
45
+ version: '9.0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: mcp
28
48
  requirement: !ruby/object:Gem::Requirement
29
49
  requirements:
30
50
  - - "~>"
31
51
  - !ruby/object:Gem::Version
32
- version: '7.0'
52
+ version: 0.1.0
33
53
  type: :runtime
34
54
  prerelease: false
35
55
  version_requirements: !ruby/object:Gem::Requirement
36
56
  requirements:
37
57
  - - "~>"
38
58
  - !ruby/object:Gem::Version
39
- version: '7.0'
59
+ version: 0.1.0
40
60
  - !ruby/object:Gem::Dependency
41
61
  name: json
42
62
  requirement: !ruby/object:Gem::Requirement
@@ -55,16 +75,22 @@ dependencies:
55
75
  name: rack
56
76
  requirement: !ruby/object:Gem::Requirement
57
77
  requirements:
58
- - - "~>"
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: '2.0'
81
+ - - "<"
59
82
  - !ruby/object:Gem::Version
60
- version: '3.0'
83
+ version: '4.0'
61
84
  type: :runtime
62
85
  prerelease: false
63
86
  version_requirements: !ruby/object:Gem::Requirement
64
87
  requirements:
65
- - - "~>"
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '2.0'
91
+ - - "<"
66
92
  - !ruby/object:Gem::Version
67
- version: '3.0'
93
+ version: '4.0'
68
94
  - !ruby/object:Gem::Dependency
69
95
  name: timeout
70
96
  requirement: !ruby/object:Gem::Requirement
@@ -207,7 +233,9 @@ files:
207
233
  - ".idea/vcs.xml"
208
234
  - README.md
209
235
  - changelog.md
236
+ - claude_desktop_config.json
210
237
  - docs/DEBUGGING.md
238
+ - docs/GENERATOR_TESTING.md
211
239
  - docs/README.md
212
240
  - exe/rails-active-mcp-server
213
241
  - lib/generators/rails_active_mcp/install/install_generator.rb
@@ -218,15 +246,13 @@ files:
218
246
  - lib/rails_active_mcp/configuration.rb
219
247
  - lib/rails_active_mcp/console_executor.rb
220
248
  - lib/rails_active_mcp/engine.rb
221
- - lib/rails_active_mcp/mcp_server.rb
222
- - lib/rails_active_mcp/railtie.rb
223
249
  - lib/rails_active_mcp/safety_checker.rb
224
- - lib/rails_active_mcp/stdio_server.rb
250
+ - lib/rails_active_mcp/sdk/server.rb
251
+ - lib/rails_active_mcp/sdk/tools/console_execute_tool.rb
252
+ - lib/rails_active_mcp/sdk/tools/dry_run_tool.rb
253
+ - lib/rails_active_mcp/sdk/tools/model_info_tool.rb
254
+ - lib/rails_active_mcp/sdk/tools/safe_query_tool.rb
225
255
  - lib/rails_active_mcp/tasks.rake
226
- - lib/rails_active_mcp/tools/console_execute_tool.rb
227
- - lib/rails_active_mcp/tools/dry_run_tool.rb
228
- - lib/rails_active_mcp/tools/model_info_tool.rb
229
- - lib/rails_active_mcp/tools/safe_query_tool.rb
230
256
  - lib/rails_active_mcp/version.rb
231
257
  - mcp.ru
232
258
  - rails_active_mcp.gemspec
@@ -1,374 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'json'
4
- require 'rack'
5
-
6
- module RailsActiveMcp
7
- class McpServer
8
- JSONRPC_VERSION = '2.0'
9
- MCP_VERSION = '2025-06-18'
10
-
11
- def initialize(app = nil)
12
- @app = app
13
- @tools = {}
14
- @resources = {}
15
- register_default_tools
16
- end
17
-
18
- def call(env)
19
- request = Rack::Request.new(env)
20
-
21
- return [405, {}, ['Method Not Allowed']] unless request.post?
22
- return [400, {}, ['Invalid Content-Type']] unless json_request?(request)
23
-
24
- begin
25
- body = request.body.read
26
- data = JSON.parse(body)
27
- response = handle_jsonrpc_request(data)
28
-
29
- [200, {'Content-Type' => 'application/json'}, [response.to_json]]
30
- rescue JSON::ParserError
31
- error_response(400, 'Invalid JSON')
32
- rescue => e
33
- Rails.logger.error "MCP Server Error: #{e.message}" if defined?(Rails)
34
- error_response(500, 'Internal Server Error')
35
- end
36
- end
37
-
38
- private
39
-
40
- def json_request?(request)
41
- request.content_type&.include?('application/json')
42
- end
43
-
44
- def handle_jsonrpc_request(data)
45
- case data['method']
46
- when 'initialize'
47
- handle_initialize(data)
48
- when 'tools/list'
49
- handle_tools_list(data)
50
- when 'tools/call'
51
- handle_tools_call(data)
52
- when 'resources/list'
53
- handle_resources_list(data)
54
- when 'resources/read'
55
- handle_resources_read(data)
56
- else
57
- jsonrpc_error(data['id'], -32601, 'Method not found')
58
- end
59
- end
60
-
61
- def handle_initialize(data)
62
- {
63
- jsonrpc: JSONRPC_VERSION,
64
- id: data['id'],
65
- result: {
66
- protocolVersion: MCP_VERSION,
67
- capabilities: {
68
- tools: {
69
- list: true,
70
- call: true
71
- },
72
- resources: {
73
- read: true,
74
- list: true
75
- }
76
- },
77
- serverInfo: {
78
- name: 'rails-active-mcp',
79
- version: RailsActiveMcp::VERSION
80
- }
81
- }
82
- }
83
- end
84
-
85
- def handle_tools_list(data)
86
- tools_array = @tools.values.map do |tool|
87
- tool_def = {
88
- name: tool[:name],
89
- description: tool[:description],
90
- inputSchema: tool[:input_schema]
91
- }
92
-
93
- # Add annotations if present
94
- if tool[:annotations] && !tool[:annotations].empty?
95
- tool_def[:annotations] = tool[:annotations]
96
- end
97
-
98
- tool_def
99
- end
100
-
101
- {
102
- jsonrpc: JSONRPC_VERSION,
103
- id: data['id'],
104
- result: { tools: tools_array }
105
- }
106
- end
107
-
108
- def handle_tools_call(data)
109
- tool_name = data.dig('params', 'name')
110
- arguments = data.dig('params', 'arguments') || {}
111
-
112
- tool = @tools[tool_name]
113
- return jsonrpc_error(data['id'], -32602, "Tool '#{tool_name}' not found") unless tool
114
-
115
- begin
116
- result = tool[:handler].call(arguments)
117
- {
118
- jsonrpc: JSONRPC_VERSION,
119
- id: data['id'],
120
- result: { content: [{ type: 'text', text: result.to_s }] }
121
- }
122
- rescue => e
123
- jsonrpc_error(data['id'], -32603, "Tool execution failed: #{e.message}")
124
- end
125
- end
126
-
127
- def handle_resources_list(data)
128
- {
129
- jsonrpc: JSONRPC_VERSION,
130
- id: data['id'],
131
- result: { resources: [] }
132
- }
133
- end
134
-
135
- def handle_resources_read(data)
136
- {
137
- jsonrpc: JSONRPC_VERSION,
138
- id: data['id'],
139
- result: { contents: [] }
140
- }
141
- end
142
-
143
- def register_tool(name, description, input_schema, annotations = {}, &handler)
144
- @tools[name] = {
145
- name: name,
146
- description: description,
147
- input_schema: input_schema,
148
- annotations: annotations,
149
- handler: handler
150
- }
151
- end
152
-
153
- def register_default_tools
154
- register_tool(
155
- 'rails_console_execute',
156
- 'Execute Ruby code in Rails console context',
157
- {
158
- type: 'object',
159
- properties: {
160
- code: { type: 'string', description: 'Ruby code to execute' },
161
- timeout: { type: 'number', description: 'Timeout in seconds', default: 30 },
162
- safe_mode: { type: 'boolean', description: 'Enable safety checks', default: true },
163
- capture_output: { type: 'boolean', description: 'Capture console output', default: true }
164
- },
165
- required: ['code']
166
- },
167
- # Add MCP tool annotations
168
- {
169
- title: 'Rails Console Executor',
170
- readOnlyHint: false,
171
- destructiveHint: true,
172
- idempotentHint: false,
173
- openWorldHint: false
174
- }
175
- ) do |args|
176
- execute_console_code(args)
177
- end
178
-
179
- register_tool(
180
- 'rails_model_info',
181
- 'Get information about Rails models',
182
- {
183
- type: 'object',
184
- properties: {
185
- model_name: { type: 'string', description: 'Name of the model to inspect' }
186
- },
187
- required: ['model_name']
188
- },
189
- # Safe read-only tool
190
- {
191
- title: 'Rails Model Inspector',
192
- readOnlyHint: true,
193
- destructiveHint: false,
194
- idempotentHint: true,
195
- openWorldHint: false
196
- }
197
- ) do |args|
198
- get_model_info(args['model_name'])
199
- end
200
-
201
- register_tool(
202
- 'rails_safe_query',
203
- 'Execute safe read-only database queries',
204
- {
205
- type: 'object',
206
- properties: {
207
- query: { type: 'string', description: 'Safe query to execute' },
208
- model: { type: 'string', description: 'Model class name' }
209
- },
210
- required: ['query', 'model']
211
- },
212
- # Safe read-only query tool
213
- {
214
- title: 'Rails Safe Query Executor',
215
- readOnlyHint: true,
216
- destructiveHint: false,
217
- idempotentHint: true,
218
- openWorldHint: false
219
- }
220
- ) do |args|
221
- execute_safe_query(args)
222
- end
223
-
224
- register_tool(
225
- 'rails_dry_run',
226
- 'Analyze Ruby code safety without execution',
227
- {
228
- type: 'object',
229
- properties: {
230
- code: { type: 'string', description: 'Ruby code to analyze' }
231
- },
232
- required: ['code']
233
- },
234
- {
235
- title: 'Rails Code Safety Analyzer',
236
- readOnlyHint: true,
237
- destructiveHint: false,
238
- idempotentHint: true,
239
- openWorldHint: false
240
- }
241
- ) do |args|
242
- dry_run_analysis(args['code'])
243
- end
244
- end
245
-
246
- def execute_console_code(args)
247
- return "Rails Active MCP is disabled" unless RailsActiveMcp.config.enabled
248
-
249
- executor = RailsActiveMcp::ConsoleExecutor.new(RailsActiveMcp.config)
250
-
251
- begin
252
- result = executor.execute(
253
- args['code'],
254
- timeout: args['timeout'] || 30,
255
- safe_mode: args['safe_mode'] != false,
256
- capture_output: args['capture_output'] != false
257
- )
258
-
259
- if result[:success]
260
- format_success_result(result)
261
- else
262
- "Error: #{result[:error]} (#{result[:error_class]})"
263
- end
264
- rescue RailsActiveMcp::SafetyError => e
265
- "Safety check failed: #{e.message}"
266
- rescue RailsActiveMcp::TimeoutError => e
267
- "Execution timed out: #{e.message}"
268
- rescue => e
269
- "Execution failed: #{e.message}"
270
- end
271
- end
272
-
273
- def get_model_info(model_name)
274
- return "Rails Active MCP is disabled" unless RailsActiveMcp.config.enabled
275
-
276
- begin
277
- model_class = model_name.constantize
278
- return "#{model_name} is not an ActiveRecord model" unless model_class < ActiveRecord::Base
279
-
280
- info = []
281
- info << "Model: #{model_class.name}"
282
- info << "Table: #{model_class.table_name}"
283
- info << "Columns: #{model_class.column_names.join(', ')}"
284
- info << "Associations: #{model_class.reflect_on_all_associations.map(&:name).join(', ')}"
285
- info.join("\n")
286
- rescue NameError
287
- "Model '#{model_name}' not found"
288
- rescue => e
289
- "Error getting model info: #{e.message}"
290
- end
291
- end
292
-
293
- def execute_safe_query(args)
294
- return "Rails Active MCP is disabled" unless RailsActiveMcp.config.enabled
295
-
296
- begin
297
- model_class = args['model'].constantize
298
- return "#{args['model']} is not an ActiveRecord model" unless model_class < ActiveRecord::Base
299
-
300
- # Only allow safe read-only methods
301
- safe_methods = %w[find find_by where select count sum average maximum minimum first last pluck ids exists? empty? any? many? include?]
302
- query_method = args['query'].split('.').first
303
-
304
- return "Unsafe query method: #{query_method}" unless safe_methods.include?(query_method)
305
-
306
- result = model_class.instance_eval(args['query'])
307
- result.to_s
308
- rescue NameError
309
- "Model '#{args['model']}' not found"
310
- rescue => e
311
- "Error executing query: #{e.message}"
312
- end
313
- end
314
-
315
- def dry_run_analysis(code)
316
- return "Rails Active MCP is disabled" unless RailsActiveMcp.config.enabled
317
-
318
- executor = RailsActiveMcp::ConsoleExecutor.new(RailsActiveMcp.config)
319
-
320
- begin
321
- analysis = executor.dry_run(code)
322
-
323
- output = []
324
- output << "Code: #{analysis[:code]}"
325
- output << "Safe: #{analysis[:safety_analysis][:safe] ? 'Yes' : 'No'}"
326
- output << "Read-only: #{analysis[:safety_analysis][:read_only] ? 'Yes' : 'No'}"
327
- output << "Risk level: #{analysis[:estimated_risk]}"
328
- output << "Would execute: #{analysis[:would_execute] ? 'Yes' : 'No'}"
329
- output << "Summary: #{analysis[:safety_analysis][:summary]}"
330
-
331
- if analysis[:safety_analysis][:violations].any?
332
- output << "\nViolations:"
333
- analysis[:safety_analysis][:violations].each do |violation|
334
- output << " - #{violation[:description]} (#{violation[:severity]})"
335
- end
336
- end
337
-
338
- if analysis[:recommendations].any?
339
- output << "\nRecommendations:"
340
- analysis[:recommendations].each do |rec|
341
- output << " - #{rec}"
342
- end
343
- end
344
-
345
- output.join("\n")
346
- rescue => e
347
- "Analysis failed: #{e.message}"
348
- end
349
- end
350
-
351
- def format_success_result(result)
352
- output = []
353
- output << "Code: #{result[:code]}"
354
- output << "Result: #{result[:return_value_string] || result[:return_value]}"
355
- output << "Output: #{result[:output]}" if result[:output].present?
356
- output << "Execution time: #{result[:execution_time]}s" if result[:execution_time]
357
- output << "Note: #{result[:note]}" if result[:note]
358
- output.join("\n")
359
- end
360
-
361
- def jsonrpc_error(id, code, message)
362
- {
363
- jsonrpc: JSONRPC_VERSION,
364
- id: id,
365
- error: { code: code, message: message }
366
- }
367
- end
368
-
369
- def error_response(status, message)
370
- [status, {'Content-Type' => 'application/json'},
371
- [{ error: message }.to_json]]
372
- end
373
- end
374
- end
@@ -1,48 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RailsActiveMcp
4
- class Railtie < ::Rails::Railtie
5
- railtie_name :rails_active_mcp
6
-
7
- # Ensure configuration is available very early
8
- config.before_initialize do
9
- RailsActiveMcp.configure unless RailsActiveMcp.configuration
10
- end
11
-
12
- # Add rake tasks
13
- rake_tasks do
14
- load 'rails_active_mcp/tasks.rake'
15
- end
16
-
17
- # Add generators
18
- generators do
19
- # Generators are auto-discovered from lib/generators following Rails conventions
20
- end
21
-
22
- # Console hook for easier access
23
- console do
24
- # Add convenience methods to console
25
- Rails::ConsoleMethods.include(RailsActiveMcp::ConsoleMethods) if defined?(Rails::ConsoleMethods)
26
- end
27
-
28
- # Configure logging
29
- initializer 'rails_active_mcp.logger' do
30
- RailsActiveMcp.logger = Rails.logger if defined?(Rails.logger)
31
- end
32
- end
33
-
34
- # Console convenience methods
35
- module ConsoleMethods
36
- def mcp_execute(code, **options)
37
- RailsActiveMcp.execute(code, **options)
38
- end
39
-
40
- def mcp_safe?(code)
41
- RailsActiveMcp.safe?(code)
42
- end
43
-
44
- def mcp_config
45
- RailsActiveMcp.config
46
- end
47
- end
48
- end