rails-active-mcp 0.1.6 → 0.1.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/app/controllers/rails_active_mcp/mcp_controller.rb +80 -0
- data/claude_desktop_config.json +12 -0
- data/docs/DEBUGGING.md +35 -3
- data/docs/GENERATOR_TESTING.md +121 -0
- data/exe/rails-active-mcp-server +54 -20
- data/lib/generators/rails_active_mcp/install/install_generator.rb +142 -2
- data/lib/generators/rails_active_mcp/install/templates/README.md +48 -8
- data/lib/rails_active_mcp/console_executor.rb +192 -78
- data/lib/rails_active_mcp/engine.rb +16 -0
- data/lib/rails_active_mcp/mcp_server.rb +36 -27
- data/lib/rails_active_mcp/railtie.rb +25 -3
- data/lib/rails_active_mcp/stdio_server.rb +111 -61
- data/lib/rails_active_mcp/version.rb +1 -1
- data/lib/rails_active_mcp.rb +7 -2
- data/rails_active_mcp.gemspec +5 -4
- metadata +27 -12
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'json'
|
4
|
+
require 'io/console'
|
4
5
|
require 'logger'
|
5
6
|
|
6
7
|
module RailsActiveMcp
|
@@ -10,51 +11,112 @@ module RailsActiveMcp
|
|
10
11
|
|
11
12
|
def initialize
|
12
13
|
@tools = {}
|
13
|
-
@
|
14
|
-
@
|
15
|
-
|
16
|
-
|
17
|
-
|
14
|
+
@mcp_server = McpServer.new
|
15
|
+
@running = false
|
16
|
+
|
17
|
+
# Ensure all logging goes to stderr, never stdout
|
18
|
+
setup_logging
|
18
19
|
register_default_tools
|
19
|
-
|
20
|
+
log_to_stderr "Rails Active MCP Server initialized with #{@tools.size} tools", level: :info
|
21
|
+
end
|
22
|
+
|
23
|
+
def start
|
24
|
+
@running = true
|
25
|
+
log_to_stderr 'Rails Active MCP Server started successfully', level: :info
|
26
|
+
|
27
|
+
# Send initial notification to stderr first, then stdout for MCP
|
28
|
+
send_notification('info', 'Rails Active MCP Server started successfully')
|
29
|
+
|
30
|
+
process_requests
|
31
|
+
end
|
32
|
+
|
33
|
+
def stop
|
34
|
+
@running = false
|
35
|
+
log_to_stderr 'Rails Active MCP Server stopped', level: :info
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def setup_logging
|
41
|
+
# Configure Rails Active MCP logger to use stderr
|
42
|
+
return unless RailsActiveMcp.respond_to?(:logger=)
|
43
|
+
|
44
|
+
stderr_logger = Logger.new($stderr)
|
45
|
+
stderr_logger.level = ENV['RAILS_MCP_DEBUG'] == '1' ? Logger::DEBUG : Logger::INFO
|
46
|
+
stderr_logger.formatter = proc do |severity, datetime, progname, msg|
|
47
|
+
"[#{datetime.strftime('%Y-%m-%d %H:%M:%S %z')}] [RAILS-MCP] #{severity}: #{msg}\n"
|
48
|
+
end
|
49
|
+
RailsActiveMcp.logger = stderr_logger
|
20
50
|
end
|
21
51
|
|
22
|
-
def
|
23
|
-
@
|
24
|
-
|
52
|
+
def process_requests
|
53
|
+
while @running
|
54
|
+
begin
|
55
|
+
line = $stdin.gets
|
56
|
+
break if line.nil?
|
57
|
+
|
58
|
+
line = line.strip
|
59
|
+
next if line.empty?
|
25
60
|
|
26
|
-
|
27
|
-
line = line.strip
|
28
|
-
next if line.empty?
|
61
|
+
log_to_stderr "Received request: #{line}", level: :debug
|
29
62
|
|
30
|
-
|
31
|
-
|
63
|
+
request = JSON.parse(line)
|
64
|
+
log_to_stderr "Processing method: #{request['method']}", level: :debug
|
32
65
|
|
33
|
-
|
34
|
-
response = handle_jsonrpc_request(data)
|
66
|
+
response = @mcp_server.handle_jsonrpc_request(request)
|
35
67
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
68
|
+
log_to_stderr "Sending response: #{response.to_json}", level: :debug
|
69
|
+
|
70
|
+
# Only JSON responses go to stdout
|
71
|
+
$stdout.puts response.to_json
|
72
|
+
$stdout.flush
|
73
|
+
rescue JSON::ParserError => e
|
74
|
+
log_to_stderr "JSON parse error: #{e.message}", level: :error
|
75
|
+
send_error_response(nil, -32_700, 'Parse error')
|
76
|
+
rescue StandardError => e
|
77
|
+
log_to_stderr "Server error: #{e.message}", level: :error
|
78
|
+
log_to_stderr e.backtrace.join("\n"), level: :debug
|
79
|
+
send_error_response(nil, -32_603, 'Internal error')
|
40
80
|
end
|
41
|
-
rescue JSON::ParserError => e
|
42
|
-
@logger.error "JSON Parse Error: #{e.message}"
|
43
|
-
send_log_notification('error', "JSON Parse Error: #{e.message}")
|
44
|
-
error_response = jsonrpc_error(nil, -32_700, 'Parse error')
|
45
|
-
puts error_response.to_json
|
46
|
-
STDOUT.flush
|
47
|
-
rescue StandardError => e
|
48
|
-
@logger.error "Unexpected error: #{e.message}"
|
49
|
-
@logger.error e.backtrace.join("\n")
|
50
|
-
send_log_notification('error', "Server error: #{e.message}")
|
51
|
-
error_response = jsonrpc_error(nil, -32_603, 'Internal error')
|
52
|
-
puts error_response.to_json
|
53
|
-
STDOUT.flush
|
54
81
|
end
|
55
82
|
end
|
56
83
|
|
57
|
-
|
84
|
+
def send_notification(level, message)
|
85
|
+
notification = {
|
86
|
+
jsonrpc: JSONRPC_VERSION,
|
87
|
+
method: 'notifications/message',
|
88
|
+
params: {
|
89
|
+
level: level,
|
90
|
+
data: message
|
91
|
+
}
|
92
|
+
}
|
93
|
+
|
94
|
+
# Notifications go to stdout for MCP protocol
|
95
|
+
$stdout.puts notification.to_json
|
96
|
+
$stdout.flush
|
97
|
+
end
|
98
|
+
|
99
|
+
def send_error_response(id, code, message)
|
100
|
+
error_response = {
|
101
|
+
jsonrpc: JSONRPC_VERSION,
|
102
|
+
id: id,
|
103
|
+
error: {
|
104
|
+
code: code,
|
105
|
+
message: message
|
106
|
+
}
|
107
|
+
}
|
108
|
+
|
109
|
+
$stdout.puts error_response.to_json
|
110
|
+
$stdout.flush
|
111
|
+
end
|
112
|
+
|
113
|
+
def log_to_stderr(message, level: :info)
|
114
|
+
return unless ENV['RAILS_MCP_DEBUG'] == '1' || level == :error || level == :info
|
115
|
+
|
116
|
+
timestamp = Time.now.strftime('%Y-%m-%d %H:%M:%S %z')
|
117
|
+
warn "[#{timestamp}] [RAILS-MCP] #{level.to_s.upcase}: #{message}"
|
118
|
+
$stderr.flush
|
119
|
+
end
|
58
120
|
|
59
121
|
def handle_jsonrpc_request(data)
|
60
122
|
case data['method']
|
@@ -121,16 +183,16 @@ module RailsActiveMcp
|
|
121
183
|
tool = @tools[tool_name]
|
122
184
|
return jsonrpc_error(data['id'], -32_602, "Tool '#{tool_name}' not found") unless tool
|
123
185
|
|
124
|
-
|
125
|
-
|
186
|
+
log_to_stderr "Executing tool: #{tool_name}", level: :info
|
187
|
+
send_notification('info', "Executing tool: #{tool_name}")
|
126
188
|
|
127
189
|
begin
|
128
190
|
start_time = Time.now
|
129
191
|
result = tool[:handler].call(arguments)
|
130
192
|
execution_time = Time.now - start_time
|
131
193
|
|
132
|
-
|
133
|
-
|
194
|
+
log_to_stderr "Tool #{tool_name} completed in #{execution_time}s", level: :info
|
195
|
+
send_notification('info', "Tool #{tool_name} completed successfully")
|
134
196
|
|
135
197
|
{
|
136
198
|
jsonrpc: JSONRPC_VERSION,
|
@@ -141,9 +203,9 @@ module RailsActiveMcp
|
|
141
203
|
}
|
142
204
|
}
|
143
205
|
rescue StandardError => e
|
144
|
-
|
145
|
-
|
146
|
-
|
206
|
+
log_to_stderr "Tool execution error: #{e.message}", level: :error
|
207
|
+
log_to_stderr e.backtrace.first(5).join("\n"), level: :debug if ENV['RAILS_MCP_DEBUG']
|
208
|
+
send_notification('error', "Tool #{tool_name} failed: #{e.message}")
|
147
209
|
|
148
210
|
{
|
149
211
|
jsonrpc: JSONRPC_VERSION,
|
@@ -316,8 +378,10 @@ module RailsActiveMcp
|
|
316
378
|
end
|
317
379
|
|
318
380
|
begin
|
319
|
-
#
|
320
|
-
|
381
|
+
# Check if Rails environment is available
|
382
|
+
unless defined?(Rails)
|
383
|
+
return 'Rails environment not loaded. Please ensure the MCP server is started from your Rails app directory.'
|
384
|
+
end
|
321
385
|
|
322
386
|
model_class = model_name.constantize
|
323
387
|
unless defined?(ActiveRecord) && model_class < ActiveRecord::Base
|
@@ -352,8 +416,10 @@ module RailsActiveMcp
|
|
352
416
|
end
|
353
417
|
|
354
418
|
begin
|
355
|
-
#
|
356
|
-
|
419
|
+
# Check if Rails environment is available
|
420
|
+
unless defined?(Rails)
|
421
|
+
return 'Rails environment not loaded. Please ensure the MCP server is started from your Rails app directory.'
|
422
|
+
end
|
357
423
|
|
358
424
|
model_class = args['model'].constantize
|
359
425
|
unless defined?(ActiveRecord) && model_class < ActiveRecord::Base
|
@@ -440,22 +506,6 @@ module RailsActiveMcp
|
|
440
506
|
output.join("\n")
|
441
507
|
end
|
442
508
|
|
443
|
-
def send_log_notification(level, message)
|
444
|
-
notification = {
|
445
|
-
jsonrpc: JSONRPC_VERSION,
|
446
|
-
method: 'notifications/message',
|
447
|
-
params: {
|
448
|
-
level: level,
|
449
|
-
data: message
|
450
|
-
}
|
451
|
-
}
|
452
|
-
|
453
|
-
puts notification.to_json
|
454
|
-
STDOUT.flush
|
455
|
-
rescue StandardError => e
|
456
|
-
@logger.error "Failed to send log notification: #{e.message}"
|
457
|
-
end
|
458
|
-
|
459
509
|
def jsonrpc_error(id, code, message)
|
460
510
|
{
|
461
511
|
jsonrpc: JSONRPC_VERSION,
|
data/lib/rails_active_mcp.rb
CHANGED
@@ -47,11 +47,16 @@ module RailsActiveMcp
|
|
47
47
|
@server ||= McpServer.new
|
48
48
|
end
|
49
49
|
|
50
|
-
#
|
50
|
+
# Logger accessor - configured by railtie or defaults to stderr
|
51
51
|
attr_accessor :logger
|
52
52
|
|
53
53
|
def logger
|
54
|
-
@logger ||=
|
54
|
+
@logger ||= Logger.new(STDERR).tap do |logger|
|
55
|
+
logger.level = Logger::INFO
|
56
|
+
logger.formatter = proc do |severity, datetime, progname, msg|
|
57
|
+
"[#{datetime}] #{severity} -- RailsActiveMcp: #{msg}\n"
|
58
|
+
end
|
59
|
+
end
|
55
60
|
end
|
56
61
|
end
|
57
62
|
end
|
data/rails_active_mcp.gemspec
CHANGED
@@ -30,12 +30,13 @@ Gem::Specification.new do |spec|
|
|
30
30
|
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
31
31
|
spec.require_paths = ['lib']
|
32
32
|
|
33
|
-
#
|
34
|
-
spec.add_runtime_dependency 'concurrent-ruby', '~> 1.3
|
35
|
-
spec.add_runtime_dependency 'rails', '
|
33
|
+
# Runtime dependencies - more flexible Rails version support
|
34
|
+
spec.add_runtime_dependency 'concurrent-ruby', '~> 1.3'
|
35
|
+
spec.add_runtime_dependency 'rails', '>= 6.1', '< 8.0'
|
36
36
|
|
37
|
+
# Core dependencies
|
37
38
|
spec.add_dependency 'json', '~> 2.0'
|
38
|
-
spec.add_dependency 'rack', '
|
39
|
+
spec.add_dependency 'rack', '>= 2.0', '< 4.0'
|
39
40
|
spec.add_dependency 'timeout', '~> 0.4'
|
40
41
|
spec.add_dependency 'webrick', '~> 1.8'
|
41
42
|
|
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.
|
4
|
+
version: 0.1.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brandyn Britton
|
8
8
|
bindir: exe
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-06-
|
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,34 @@ dependencies:
|
|
15
15
|
requirements:
|
16
16
|
- - "~>"
|
17
17
|
- !ruby/object:Gem::Version
|
18
|
-
version: 1.3
|
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
|
25
|
+
version: '1.3'
|
26
26
|
- !ruby/object:Gem::Dependency
|
27
27
|
name: rails
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
29
29
|
requirements:
|
30
|
-
- - "
|
30
|
+
- - ">="
|
31
31
|
- !ruby/object:Gem::Version
|
32
|
-
version: '
|
32
|
+
version: '6.1'
|
33
|
+
- - "<"
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: '8.0'
|
33
36
|
type: :runtime
|
34
37
|
prerelease: false
|
35
38
|
version_requirements: !ruby/object:Gem::Requirement
|
36
39
|
requirements:
|
37
|
-
- - "
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '6.1'
|
43
|
+
- - "<"
|
38
44
|
- !ruby/object:Gem::Version
|
39
|
-
version: '
|
45
|
+
version: '8.0'
|
40
46
|
- !ruby/object:Gem::Dependency
|
41
47
|
name: json
|
42
48
|
requirement: !ruby/object:Gem::Requirement
|
@@ -55,16 +61,22 @@ dependencies:
|
|
55
61
|
name: rack
|
56
62
|
requirement: !ruby/object:Gem::Requirement
|
57
63
|
requirements:
|
58
|
-
- - "
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '2.0'
|
67
|
+
- - "<"
|
59
68
|
- !ruby/object:Gem::Version
|
60
|
-
version: '
|
69
|
+
version: '4.0'
|
61
70
|
type: :runtime
|
62
71
|
prerelease: false
|
63
72
|
version_requirements: !ruby/object:Gem::Requirement
|
64
73
|
requirements:
|
65
|
-
- - "
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '2.0'
|
77
|
+
- - "<"
|
66
78
|
- !ruby/object:Gem::Version
|
67
|
-
version: '
|
79
|
+
version: '4.0'
|
68
80
|
- !ruby/object:Gem::Dependency
|
69
81
|
name: timeout
|
70
82
|
requirement: !ruby/object:Gem::Requirement
|
@@ -206,8 +218,11 @@ files:
|
|
206
218
|
- ".idea/rails-active-mcp-gem.iml"
|
207
219
|
- ".idea/vcs.xml"
|
208
220
|
- README.md
|
221
|
+
- app/controllers/rails_active_mcp/mcp_controller.rb
|
209
222
|
- changelog.md
|
223
|
+
- claude_desktop_config.json
|
210
224
|
- docs/DEBUGGING.md
|
225
|
+
- docs/GENERATOR_TESTING.md
|
211
226
|
- docs/README.md
|
212
227
|
- exe/rails-active-mcp-server
|
213
228
|
- lib/generators/rails_active_mcp/install/install_generator.rb
|