rails-active-mcp 0.1.7 → 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.
- checksums.yaml +4 -4
- data/README.md +106 -279
- data/changelog.md +69 -0
- data/docs/DEBUGGING.md +5 -5
- data/docs/README.md +130 -142
- data/exe/rails-active-mcp-server +153 -76
- data/lib/generators/rails_active_mcp/install/install_generator.rb +19 -39
- data/lib/generators/rails_active_mcp/install/templates/README.md +30 -164
- data/lib/generators/rails_active_mcp/install/templates/initializer.rb +37 -38
- data/lib/generators/rails_active_mcp/install/templates/mcp.ru +7 -3
- data/lib/rails_active_mcp/configuration.rb +37 -98
- data/lib/rails_active_mcp/console_executor.rb +13 -3
- data/lib/rails_active_mcp/engine.rb +36 -24
- data/lib/rails_active_mcp/sdk/server.rb +183 -0
- data/lib/rails_active_mcp/sdk/tools/console_execute_tool.rb +103 -0
- data/lib/rails_active_mcp/sdk/tools/dry_run_tool.rb +73 -0
- data/lib/rails_active_mcp/sdk/tools/model_info_tool.rb +106 -0
- data/lib/rails_active_mcp/sdk/tools/safe_query_tool.rb +77 -0
- data/lib/rails_active_mcp/version.rb +1 -1
- data/lib/rails_active_mcp.rb +5 -11
- data/rails_active_mcp.gemspec +4 -1
- metadata +22 -11
- data/app/controllers/rails_active_mcp/mcp_controller.rb +0 -80
- data/lib/rails_active_mcp/mcp_server.rb +0 -383
- data/lib/rails_active_mcp/railtie.rb +0 -70
- data/lib/rails_active_mcp/stdio_server.rb +0 -517
- data/lib/rails_active_mcp/tools/console_execute_tool.rb +0 -61
- data/lib/rails_active_mcp/tools/dry_run_tool.rb +0 -41
- data/lib/rails_active_mcp/tools/model_info_tool.rb +0 -70
- data/lib/rails_active_mcp/tools/safe_query_tool.rb +0 -41
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rails-active-mcp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 2.0.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brandyn Britton
|
@@ -32,7 +32,7 @@ dependencies:
|
|
32
32
|
version: '6.1'
|
33
33
|
- - "<"
|
34
34
|
- !ruby/object:Gem::Version
|
35
|
-
version: '
|
35
|
+
version: '9.0'
|
36
36
|
type: :runtime
|
37
37
|
prerelease: false
|
38
38
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -42,7 +42,21 @@ dependencies:
|
|
42
42
|
version: '6.1'
|
43
43
|
- - "<"
|
44
44
|
- !ruby/object:Gem::Version
|
45
|
-
version: '
|
45
|
+
version: '9.0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: mcp
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - "~>"
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: 0.1.0
|
53
|
+
type: :runtime
|
54
|
+
prerelease: false
|
55
|
+
version_requirements: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - "~>"
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: 0.1.0
|
46
60
|
- !ruby/object:Gem::Dependency
|
47
61
|
name: json
|
48
62
|
requirement: !ruby/object:Gem::Requirement
|
@@ -218,7 +232,6 @@ files:
|
|
218
232
|
- ".idea/rails-active-mcp-gem.iml"
|
219
233
|
- ".idea/vcs.xml"
|
220
234
|
- README.md
|
221
|
-
- app/controllers/rails_active_mcp/mcp_controller.rb
|
222
235
|
- changelog.md
|
223
236
|
- claude_desktop_config.json
|
224
237
|
- docs/DEBUGGING.md
|
@@ -233,15 +246,13 @@ files:
|
|
233
246
|
- lib/rails_active_mcp/configuration.rb
|
234
247
|
- lib/rails_active_mcp/console_executor.rb
|
235
248
|
- lib/rails_active_mcp/engine.rb
|
236
|
-
- lib/rails_active_mcp/mcp_server.rb
|
237
|
-
- lib/rails_active_mcp/railtie.rb
|
238
249
|
- lib/rails_active_mcp/safety_checker.rb
|
239
|
-
- lib/rails_active_mcp/
|
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
|
240
255
|
- lib/rails_active_mcp/tasks.rake
|
241
|
-
- lib/rails_active_mcp/tools/console_execute_tool.rb
|
242
|
-
- lib/rails_active_mcp/tools/dry_run_tool.rb
|
243
|
-
- lib/rails_active_mcp/tools/model_info_tool.rb
|
244
|
-
- lib/rails_active_mcp/tools/safe_query_tool.rb
|
245
256
|
- lib/rails_active_mcp/version.rb
|
246
257
|
- mcp.ru
|
247
258
|
- rails_active_mcp.gemspec
|
@@ -1,80 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module RailsActiveMcp
|
4
|
-
class McpController < ApplicationController
|
5
|
-
protect_from_forgery with: :null_session
|
6
|
-
before_action :check_enabled
|
7
|
-
before_action :set_cors_headers
|
8
|
-
|
9
|
-
def handle
|
10
|
-
return head :method_not_allowed unless request.post?
|
11
|
-
return head :bad_request unless json_request?
|
12
|
-
|
13
|
-
begin
|
14
|
-
body = request.body.read
|
15
|
-
data = JSON.parse(body)
|
16
|
-
|
17
|
-
mcp_server = RailsActiveMcp::McpServer.new
|
18
|
-
response_data = mcp_server.handle_jsonrpc_request(data)
|
19
|
-
|
20
|
-
render json: response_data
|
21
|
-
rescue JSON::ParserError
|
22
|
-
render json: { error: 'Invalid JSON' }, status: :bad_request
|
23
|
-
rescue StandardError => e
|
24
|
-
RailsActiveMcp.logger.error "MCP Controller Error: #{e.message}"
|
25
|
-
render json: { error: 'Internal Server Error' }, status: :internal_server_error
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
def sse
|
30
|
-
response.headers['Content-Type'] = 'text/event-stream'
|
31
|
-
response.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate'
|
32
|
-
response.headers['Connection'] = 'keep-alive'
|
33
|
-
response.headers['X-Accel-Buffering'] = 'no'
|
34
|
-
|
35
|
-
# Send initial connection established event
|
36
|
-
render plain: ": SSE connection established\n\nevent: endpoint\ndata: #{request.base_url}#{rails_active_mcp.root_path}messages\n\nretry: 100\n\n"
|
37
|
-
end
|
38
|
-
|
39
|
-
def health
|
40
|
-
status = RailsActiveMcp.config.enabled ? 'healthy' : 'disabled'
|
41
|
-
render json: {
|
42
|
-
status: status,
|
43
|
-
version: RailsActiveMcp::VERSION,
|
44
|
-
timestamp: Time.current.iso8601
|
45
|
-
}
|
46
|
-
end
|
47
|
-
|
48
|
-
def info
|
49
|
-
render json: {
|
50
|
-
name: 'Rails Active MCP',
|
51
|
-
version: RailsActiveMcp::VERSION,
|
52
|
-
description: 'Rails Console access via Model Context Protocol (MCP)',
|
53
|
-
endpoints: {
|
54
|
-
mcp: rails_active_mcp.root_path,
|
55
|
-
health: rails_active_mcp.root_path + 'health'
|
56
|
-
},
|
57
|
-
enabled: RailsActiveMcp.config.enabled
|
58
|
-
}
|
59
|
-
end
|
60
|
-
|
61
|
-
private
|
62
|
-
|
63
|
-
def check_enabled
|
64
|
-
return if RailsActiveMcp.config.enabled
|
65
|
-
|
66
|
-
render json: { error: 'Rails Active MCP is disabled' }, status: :service_unavailable
|
67
|
-
end
|
68
|
-
|
69
|
-
def json_request?
|
70
|
-
request.content_type&.include?('application/json')
|
71
|
-
end
|
72
|
-
|
73
|
-
def set_cors_headers
|
74
|
-
response.headers['Access-Control-Allow-Origin'] = '*'
|
75
|
-
response.headers['Access-Control-Allow-Methods'] = 'GET, POST, OPTIONS'
|
76
|
-
response.headers['Access-Control-Allow-Headers'] = 'Content-Type'
|
77
|
-
response.headers['Access-Control-Max-Age'] = '86400'
|
78
|
-
end
|
79
|
-
end
|
80
|
-
end
|
@@ -1,383 +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 StandardError => e
|
33
|
-
RailsActiveMcp.logger.error "MCP Server Error: #{e.message}"
|
34
|
-
error_response(500, 'Internal Server Error')
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
def handle_jsonrpc_request(data)
|
39
|
-
case data['method']
|
40
|
-
when 'initialize'
|
41
|
-
handle_initialize(data)
|
42
|
-
when 'tools/list'
|
43
|
-
handle_tools_list(data)
|
44
|
-
when 'tools/call'
|
45
|
-
handle_tools_call(data)
|
46
|
-
when 'resources/list'
|
47
|
-
handle_resources_list(data)
|
48
|
-
when 'resources/read'
|
49
|
-
handle_resources_read(data)
|
50
|
-
when 'ping'
|
51
|
-
handle_ping(data)
|
52
|
-
else
|
53
|
-
jsonrpc_error(data['id'], -32_601, 'Method not found')
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
def handle_ping(data)
|
58
|
-
{
|
59
|
-
jsonrpc: JSONRPC_VERSION,
|
60
|
-
id: data['id'],
|
61
|
-
result: {}
|
62
|
-
}
|
63
|
-
end
|
64
|
-
|
65
|
-
def handle_initialize(data)
|
66
|
-
{
|
67
|
-
jsonrpc: JSONRPC_VERSION,
|
68
|
-
id: data['id'],
|
69
|
-
result: {
|
70
|
-
protocolVersion: MCP_VERSION,
|
71
|
-
capabilities: {
|
72
|
-
tools: {
|
73
|
-
list: true,
|
74
|
-
call: true
|
75
|
-
},
|
76
|
-
resources: {
|
77
|
-
read: true,
|
78
|
-
list: true
|
79
|
-
}
|
80
|
-
},
|
81
|
-
serverInfo: {
|
82
|
-
name: 'rails-active-mcp',
|
83
|
-
version: RailsActiveMcp::VERSION
|
84
|
-
}
|
85
|
-
}
|
86
|
-
}
|
87
|
-
end
|
88
|
-
|
89
|
-
def handle_tools_list(data)
|
90
|
-
tools_array = @tools.values.map do |tool|
|
91
|
-
tool_def = {
|
92
|
-
name: tool[:name],
|
93
|
-
description: tool[:description],
|
94
|
-
inputSchema: tool[:input_schema]
|
95
|
-
}
|
96
|
-
|
97
|
-
# Add annotations if present
|
98
|
-
tool_def[:annotations] = tool[:annotations] if tool[:annotations] && !tool[:annotations].empty?
|
99
|
-
|
100
|
-
tool_def
|
101
|
-
end
|
102
|
-
|
103
|
-
{
|
104
|
-
jsonrpc: JSONRPC_VERSION,
|
105
|
-
id: data['id'],
|
106
|
-
result: { tools: tools_array }
|
107
|
-
}
|
108
|
-
end
|
109
|
-
|
110
|
-
def handle_tools_call(data)
|
111
|
-
tool_name = data.dig('params', 'name')
|
112
|
-
arguments = data.dig('params', 'arguments') || {}
|
113
|
-
|
114
|
-
tool = @tools[tool_name]
|
115
|
-
return jsonrpc_error(data['id'], -32_602, "Tool '#{tool_name}' not found") unless tool
|
116
|
-
|
117
|
-
begin
|
118
|
-
result = tool[:handler].call(arguments)
|
119
|
-
{
|
120
|
-
jsonrpc: JSONRPC_VERSION,
|
121
|
-
id: data['id'],
|
122
|
-
result: { content: [{ type: 'text', text: result.to_s }] }
|
123
|
-
}
|
124
|
-
rescue StandardError => e
|
125
|
-
jsonrpc_error(data['id'], -32_603, "Tool execution failed: #{e.message}")
|
126
|
-
end
|
127
|
-
end
|
128
|
-
|
129
|
-
def handle_resources_list(data)
|
130
|
-
{
|
131
|
-
jsonrpc: JSONRPC_VERSION,
|
132
|
-
id: data['id'],
|
133
|
-
result: { resources: [] }
|
134
|
-
}
|
135
|
-
end
|
136
|
-
|
137
|
-
def handle_resources_read(data)
|
138
|
-
{
|
139
|
-
jsonrpc: JSONRPC_VERSION,
|
140
|
-
id: data['id'],
|
141
|
-
result: { contents: [] }
|
142
|
-
}
|
143
|
-
end
|
144
|
-
|
145
|
-
def register_tool(name, description, input_schema, annotations = {}, &handler)
|
146
|
-
@tools[name] = {
|
147
|
-
name: name,
|
148
|
-
description: description,
|
149
|
-
input_schema: input_schema,
|
150
|
-
annotations: annotations,
|
151
|
-
handler: handler
|
152
|
-
}
|
153
|
-
end
|
154
|
-
|
155
|
-
private
|
156
|
-
|
157
|
-
def json_request?(request)
|
158
|
-
request.content_type&.include?('application/json')
|
159
|
-
end
|
160
|
-
|
161
|
-
def register_default_tools
|
162
|
-
register_tool(
|
163
|
-
'rails_console_execute',
|
164
|
-
'Execute Ruby code in Rails console context',
|
165
|
-
{
|
166
|
-
type: 'object',
|
167
|
-
properties: {
|
168
|
-
code: { type: 'string', description: 'Ruby code to execute' },
|
169
|
-
timeout: { type: 'number', description: 'Timeout in seconds', default: 30 },
|
170
|
-
safe_mode: { type: 'boolean', description: 'Enable safety checks', default: true },
|
171
|
-
capture_output: { type: 'boolean', description: 'Capture console output', default: true }
|
172
|
-
},
|
173
|
-
required: ['code']
|
174
|
-
},
|
175
|
-
# Add MCP tool annotations
|
176
|
-
{
|
177
|
-
title: 'Rails Console Executor',
|
178
|
-
readOnlyHint: false,
|
179
|
-
destructiveHint: true,
|
180
|
-
idempotentHint: false,
|
181
|
-
openWorldHint: false
|
182
|
-
}
|
183
|
-
) do |args|
|
184
|
-
execute_console_code(args)
|
185
|
-
end
|
186
|
-
|
187
|
-
register_tool(
|
188
|
-
'rails_model_info',
|
189
|
-
'Get information about Rails models',
|
190
|
-
{
|
191
|
-
type: 'object',
|
192
|
-
properties: {
|
193
|
-
model_name: { type: 'string', description: 'Name of the model to inspect' }
|
194
|
-
},
|
195
|
-
required: ['model_name']
|
196
|
-
},
|
197
|
-
# Safe read-only tool
|
198
|
-
{
|
199
|
-
title: 'Rails Model Inspector',
|
200
|
-
readOnlyHint: true,
|
201
|
-
destructiveHint: false,
|
202
|
-
idempotentHint: true,
|
203
|
-
openWorldHint: false
|
204
|
-
}
|
205
|
-
) do |args|
|
206
|
-
get_model_info(args['model_name'])
|
207
|
-
end
|
208
|
-
|
209
|
-
register_tool(
|
210
|
-
'rails_safe_query',
|
211
|
-
'Execute safe read-only database queries',
|
212
|
-
{
|
213
|
-
type: 'object',
|
214
|
-
properties: {
|
215
|
-
query: { type: 'string', description: 'Safe query to execute' },
|
216
|
-
model: { type: 'string', description: 'Model class name' }
|
217
|
-
},
|
218
|
-
required: %w[query model]
|
219
|
-
},
|
220
|
-
# Safe read-only query tool
|
221
|
-
{
|
222
|
-
title: 'Rails Safe Query Executor',
|
223
|
-
readOnlyHint: true,
|
224
|
-
destructiveHint: false,
|
225
|
-
idempotentHint: true,
|
226
|
-
openWorldHint: false
|
227
|
-
}
|
228
|
-
) do |args|
|
229
|
-
execute_safe_query(args)
|
230
|
-
end
|
231
|
-
|
232
|
-
register_tool(
|
233
|
-
'rails_dry_run',
|
234
|
-
'Analyze Ruby code safety without execution',
|
235
|
-
{
|
236
|
-
type: 'object',
|
237
|
-
properties: {
|
238
|
-
code: { type: 'string', description: 'Ruby code to analyze' }
|
239
|
-
},
|
240
|
-
required: ['code']
|
241
|
-
},
|
242
|
-
{
|
243
|
-
title: 'Rails Code Safety Analyzer',
|
244
|
-
readOnlyHint: true,
|
245
|
-
destructiveHint: false,
|
246
|
-
idempotentHint: true,
|
247
|
-
openWorldHint: false
|
248
|
-
}
|
249
|
-
) do |args|
|
250
|
-
dry_run_analysis(args['code'])
|
251
|
-
end
|
252
|
-
end
|
253
|
-
|
254
|
-
def execute_console_code(args)
|
255
|
-
return 'Rails Active MCP is disabled' unless RailsActiveMcp.config.enabled
|
256
|
-
|
257
|
-
executor = RailsActiveMcp::ConsoleExecutor.new(RailsActiveMcp.config)
|
258
|
-
|
259
|
-
begin
|
260
|
-
result = executor.execute(
|
261
|
-
args['code'],
|
262
|
-
timeout: args['timeout'] || 30,
|
263
|
-
safe_mode: args['safe_mode'] != false,
|
264
|
-
capture_output: args['capture_output'] != false
|
265
|
-
)
|
266
|
-
|
267
|
-
if result[:success]
|
268
|
-
format_success_result(result)
|
269
|
-
else
|
270
|
-
"Error: #{result[:error]} (#{result[:error_class]})"
|
271
|
-
end
|
272
|
-
rescue RailsActiveMcp::SafetyError => e
|
273
|
-
"Safety check failed: #{e.message}"
|
274
|
-
rescue RailsActiveMcp::TimeoutError => e
|
275
|
-
"Execution timed out: #{e.message}"
|
276
|
-
rescue StandardError => e
|
277
|
-
"Execution failed: #{e.message}"
|
278
|
-
end
|
279
|
-
end
|
280
|
-
|
281
|
-
def get_model_info(model_name)
|
282
|
-
return 'Rails Active MCP is disabled' unless RailsActiveMcp.config.enabled
|
283
|
-
|
284
|
-
begin
|
285
|
-
model_class = model_name.constantize
|
286
|
-
return "#{model_name} is not an ActiveRecord model" unless model_class < ActiveRecord::Base
|
287
|
-
|
288
|
-
info = []
|
289
|
-
info << "Model: #{model_class.name}"
|
290
|
-
info << "Table: #{model_class.table_name}"
|
291
|
-
info << "Columns: #{model_class.column_names.join(', ')}"
|
292
|
-
info << "Associations: #{model_class.reflect_on_all_associations.map(&:name).join(', ')}"
|
293
|
-
info.join("\n")
|
294
|
-
rescue NameError
|
295
|
-
"Model '#{model_name}' not found"
|
296
|
-
rescue StandardError => e
|
297
|
-
"Error getting model info: #{e.message}"
|
298
|
-
end
|
299
|
-
end
|
300
|
-
|
301
|
-
def execute_safe_query(args)
|
302
|
-
return 'Rails Active MCP is disabled' unless RailsActiveMcp.config.enabled
|
303
|
-
|
304
|
-
begin
|
305
|
-
model_class = args['model'].constantize
|
306
|
-
return "#{args['model']} is not an ActiveRecord model" unless model_class < ActiveRecord::Base
|
307
|
-
|
308
|
-
# Only allow safe read-only methods
|
309
|
-
safe_methods = %w[find find_by where select count sum average maximum minimum first last pluck ids exists?
|
310
|
-
empty? any? many? include?]
|
311
|
-
query_method = args['query'].split('.').first
|
312
|
-
|
313
|
-
return "Unsafe query method: #{query_method}" unless safe_methods.include?(query_method)
|
314
|
-
|
315
|
-
result = model_class.instance_eval(args['query'])
|
316
|
-
result.to_s
|
317
|
-
rescue NameError
|
318
|
-
"Model '#{args['model']}' not found"
|
319
|
-
rescue StandardError => e
|
320
|
-
"Error executing query: #{e.message}"
|
321
|
-
end
|
322
|
-
end
|
323
|
-
|
324
|
-
def dry_run_analysis(code)
|
325
|
-
return 'Rails Active MCP is disabled' unless RailsActiveMcp.config.enabled
|
326
|
-
|
327
|
-
executor = RailsActiveMcp::ConsoleExecutor.new(RailsActiveMcp.config)
|
328
|
-
|
329
|
-
begin
|
330
|
-
analysis = executor.dry_run(code)
|
331
|
-
|
332
|
-
output = []
|
333
|
-
output << "Code: #{analysis[:code]}"
|
334
|
-
output << "Safe: #{analysis[:safety_analysis][:safe] ? 'Yes' : 'No'}"
|
335
|
-
output << "Read-only: #{analysis[:safety_analysis][:read_only] ? 'Yes' : 'No'}"
|
336
|
-
output << "Risk level: #{analysis[:estimated_risk]}"
|
337
|
-
output << "Would execute: #{analysis[:would_execute] ? 'Yes' : 'No'}"
|
338
|
-
output << "Summary: #{analysis[:safety_analysis][:summary]}"
|
339
|
-
|
340
|
-
if analysis[:safety_analysis][:violations].any?
|
341
|
-
output << "\nViolations:"
|
342
|
-
analysis[:safety_analysis][:violations].each do |violation|
|
343
|
-
output << " - #{violation[:description]} (#{violation[:severity]})"
|
344
|
-
end
|
345
|
-
end
|
346
|
-
|
347
|
-
if analysis[:recommendations].any?
|
348
|
-
output << "\nRecommendations:"
|
349
|
-
analysis[:recommendations].each do |rec|
|
350
|
-
output << " - #{rec}"
|
351
|
-
end
|
352
|
-
end
|
353
|
-
|
354
|
-
output.join("\n")
|
355
|
-
rescue StandardError => e
|
356
|
-
"Analysis failed: #{e.message}"
|
357
|
-
end
|
358
|
-
end
|
359
|
-
|
360
|
-
def format_success_result(result)
|
361
|
-
output = []
|
362
|
-
output << "Code: #{result[:code]}"
|
363
|
-
output << "Result: #{result[:return_value_string] || result[:return_value]}"
|
364
|
-
output << "Output: #{result[:output]}" if result[:output].present?
|
365
|
-
output << "Execution time: #{result[:execution_time]}s" if result[:execution_time]
|
366
|
-
output << "Note: #{result[:note]}" if result[:note]
|
367
|
-
output.join("\n")
|
368
|
-
end
|
369
|
-
|
370
|
-
def jsonrpc_error(id, code, message)
|
371
|
-
{
|
372
|
-
jsonrpc: JSONRPC_VERSION,
|
373
|
-
id: id,
|
374
|
-
error: { code: code, message: message }
|
375
|
-
}
|
376
|
-
end
|
377
|
-
|
378
|
-
def error_response(status, message)
|
379
|
-
[status, { 'Content-Type' => 'application/json' },
|
380
|
-
[{ error: message }.to_json]]
|
381
|
-
end
|
382
|
-
end
|
383
|
-
end
|
@@ -1,70 +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 - Fixed for Rails 7.1 compatibility
|
29
|
-
initializer 'rails_active_mcp.logger', after: :initialize_logger, before: :set_clear_dependencies_hook do
|
30
|
-
# Only set logger if Rails logger is available and responds to logging methods
|
31
|
-
RailsActiveMcp.logger = if defined?(Rails.logger) && Rails.logger.respond_to?(:info)
|
32
|
-
# Check if Rails logger is using semantic logger or other custom loggers
|
33
|
-
if Rails.logger.class.name.include?('SemanticLogger')
|
34
|
-
# For semantic logger, we need to create a tagged logger
|
35
|
-
Rails.logger.tagged('RailsActiveMcp')
|
36
|
-
else
|
37
|
-
# For standard Rails logger, use it directly
|
38
|
-
Rails.logger
|
39
|
-
end
|
40
|
-
else
|
41
|
-
# Fallback to our own logger if Rails logger is not available
|
42
|
-
# This should not happen in normal Rails apps but provides safety
|
43
|
-
Logger.new(STDERR).tap do |logger|
|
44
|
-
logger.level = Rails.env.production? ? Logger::WARN : Logger::INFO
|
45
|
-
logger.formatter = proc do |severity, datetime, progname, msg|
|
46
|
-
"[#{datetime}] #{severity} -- RailsActiveMcp: #{msg}\n"
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
# Log that the logger has been initialized
|
52
|
-
RailsActiveMcp.logger.info "Rails Active MCP logger initialized (#{RailsActiveMcp.logger.class.name})"
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
# Console convenience methods
|
57
|
-
module ConsoleMethods
|
58
|
-
def mcp_execute(code, **options)
|
59
|
-
RailsActiveMcp.execute(code, **options)
|
60
|
-
end
|
61
|
-
|
62
|
-
def mcp_safe?(code)
|
63
|
-
RailsActiveMcp.safe?(code)
|
64
|
-
end
|
65
|
-
|
66
|
-
def mcp_config
|
67
|
-
RailsActiveMcp.config
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|