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.
@@ -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
- @logger = Logger.new(STDERR) # Log to stderr to avoid interfering with stdout
14
- @logger.level = ENV['RAILS_MCP_DEBUG'] ? Logger::DEBUG : Logger::ERROR
15
- @logger.formatter = proc do |severity, datetime, progname, msg|
16
- "[#{datetime}] [RAILS-MCP] #{severity}: #{msg}\n"
17
- end
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
- @logger.info "Rails Active MCP Server initialized with #{@tools.size} tools"
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 run
23
- @logger.info 'Starting Rails Active MCP Stdio Server'
24
- send_log_notification('info', 'Rails Active MCP Server started successfully')
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
- STDIN.each_line do |line|
27
- line = line.strip
28
- next if line.empty?
61
+ log_to_stderr "Received request: #{line}", level: :debug
29
62
 
30
- @logger.debug "Received request: #{line}" if ENV['RAILS_MCP_DEBUG']
31
- data = JSON.parse(line)
63
+ request = JSON.parse(line)
64
+ log_to_stderr "Processing method: #{request['method']}", level: :debug
32
65
 
33
- @logger.debug "Processing method: #{data['method']}" if ENV['RAILS_MCP_DEBUG']
34
- response = handle_jsonrpc_request(data)
66
+ response = @mcp_server.handle_jsonrpc_request(request)
35
67
 
36
- if response
37
- @logger.debug "Sending response: #{response.to_json}" if ENV['RAILS_MCP_DEBUG']
38
- puts response.to_json
39
- STDOUT.flush
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
- private
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
- @logger.info "Executing tool: #{tool_name}"
125
- send_log_notification('info', "Executing tool: #{tool_name}")
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
- @logger.info "Tool #{tool_name} completed in #{execution_time}s"
133
- send_log_notification('info', "Tool #{tool_name} completed successfully")
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
- @logger.error "Tool execution error: #{e.message}"
145
- @logger.error e.backtrace.first(5).join("\n") if ENV['RAILS_MCP_DEBUG']
146
- send_log_notification('error', "Tool #{tool_name} failed: #{e.message}")
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
- # Try to load Rails environment if not already loaded
320
- require_relative '../../../config/environment' if !defined?(Rails) && File.exist?('config/environment.rb')
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
- # Try to load Rails environment if not already loaded
356
- require_relative '../../../config/environment' if !defined?(Rails) && File.exist?('config/environment.rb')
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,
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RailsActiveMcp
4
- VERSION = '0.1.6'
4
+ VERSION = '0.1.7'
5
5
  end
@@ -47,11 +47,16 @@ module RailsActiveMcp
47
47
  @server ||= McpServer.new
48
48
  end
49
49
 
50
- # Add logger accessor
50
+ # Logger accessor - configured by railtie or defaults to stderr
51
51
  attr_accessor :logger
52
52
 
53
53
  def logger
54
- @logger ||= defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger ? Rails.logger : Logger.new(STDOUT)
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
@@ -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
- # Dependencies
34
- spec.add_runtime_dependency 'concurrent-ruby', '~> 1.3.5'
35
- spec.add_runtime_dependency 'rails', '~> 7.0'
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', '~> 3.0'
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.6
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-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,34 @@ 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
28
  requirement: !ruby/object:Gem::Requirement
29
29
  requirements:
30
- - - "~>"
30
+ - - ">="
31
31
  - !ruby/object:Gem::Version
32
- version: '7.0'
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: '7.0'
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: '3.0'
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: '3.0'
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