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.
Files changed (30) 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/exe/rails-active-mcp-server +153 -76
  7. data/lib/generators/rails_active_mcp/install/install_generator.rb +19 -39
  8. data/lib/generators/rails_active_mcp/install/templates/README.md +30 -164
  9. data/lib/generators/rails_active_mcp/install/templates/initializer.rb +37 -38
  10. data/lib/generators/rails_active_mcp/install/templates/mcp.ru +7 -3
  11. data/lib/rails_active_mcp/configuration.rb +37 -98
  12. data/lib/rails_active_mcp/console_executor.rb +13 -3
  13. data/lib/rails_active_mcp/engine.rb +36 -24
  14. data/lib/rails_active_mcp/sdk/server.rb +183 -0
  15. data/lib/rails_active_mcp/sdk/tools/console_execute_tool.rb +103 -0
  16. data/lib/rails_active_mcp/sdk/tools/dry_run_tool.rb +73 -0
  17. data/lib/rails_active_mcp/sdk/tools/model_info_tool.rb +106 -0
  18. data/lib/rails_active_mcp/sdk/tools/safe_query_tool.rb +77 -0
  19. data/lib/rails_active_mcp/version.rb +1 -1
  20. data/lib/rails_active_mcp.rb +5 -11
  21. data/rails_active_mcp.gemspec +4 -1
  22. metadata +22 -11
  23. data/app/controllers/rails_active_mcp/mcp_controller.rb +0 -80
  24. data/lib/rails_active_mcp/mcp_server.rb +0 -383
  25. data/lib/rails_active_mcp/railtie.rb +0 -70
  26. data/lib/rails_active_mcp/stdio_server.rb +0 -517
  27. data/lib/rails_active_mcp/tools/console_execute_tool.rb +0 -61
  28. data/lib/rails_active_mcp/tools/dry_run_tool.rb +0 -41
  29. data/lib/rails_active_mcp/tools/model_info_tool.rb +0 -70
  30. data/lib/rails_active_mcp/tools/safe_query_tool.rb +0 -41
@@ -6,6 +6,37 @@ module RailsActiveMcp
6
6
 
7
7
  config.rails_active_mcp = ActiveSupport::OrderedOptions.new
8
8
 
9
+ # Ensure configuration is available very early
10
+ initializer 'rails_active_mcp.early_configuration', before: :load_config_initializers do
11
+ RailsActiveMcp.configure unless RailsActiveMcp.configuration
12
+ end
13
+
14
+ # Configure logging with Rails 7.1+ compatibility
15
+ initializer 'rails_active_mcp.logger', after: :initialize_logger, before: :set_clear_dependencies_hook do
16
+ # Only set logger if Rails logger is available and responds to logging methods
17
+ RailsActiveMcp.logger = if defined?(Rails.logger) && Rails.logger.respond_to?(:info)
18
+ # Check if Rails logger is using semantic logger or other custom loggers
19
+ if Rails.logger.class.name.include?('SemanticLogger')
20
+ # For semantic logger, we need to create a tagged logger
21
+ Rails.logger.tagged('RailsActiveMcp')
22
+ else
23
+ # For standard Rails logger, use it directly
24
+ Rails.logger
25
+ end
26
+ else
27
+ # Fallback to our own logger if Rails logger is not available
28
+ Logger.new(STDERR).tap do |logger|
29
+ logger.level = Rails.env.production? ? Logger::WARN : Logger::INFO
30
+ logger.formatter = proc do |severity, datetime, progname, msg|
31
+ "[#{datetime}] #{severity} -- RailsActiveMcp: #{msg}\n"
32
+ end
33
+ end
34
+ end
35
+
36
+ # Log that the logger has been initialized
37
+ RailsActiveMcp.logger.info "Rails Active MCP logger initialized (#{RailsActiveMcp.logger.class.name})"
38
+ end
39
+
9
40
  # Add generators configuration
10
41
  config.generators do |g|
11
42
  g.test_framework :rspec, fixture: false
@@ -13,22 +44,6 @@ module RailsActiveMcp
13
44
  g.helper false
14
45
  end
15
46
 
16
- # Define routes for the engine
17
- routes do
18
- # Main MCP endpoint for HTTP clients
19
- post '/', to: 'mcp#handle'
20
- post '/messages', to: 'mcp#handle'
21
-
22
- # SSE endpoint for better MCP client compatibility
23
- get '/sse', to: 'mcp#sse'
24
-
25
- # Health check endpoint
26
- get '/health', to: 'mcp#health'
27
-
28
- # Root redirect
29
- root to: 'mcp#info'
30
- end
31
-
32
47
  initializer 'rails_active_mcp.configure' do |app|
33
48
  # Load configuration from Rails config if present
34
49
  if app.config.respond_to?(:rails_active_mcp)
@@ -39,18 +54,15 @@ module RailsActiveMcp
39
54
  end
40
55
  end
41
56
 
42
- # Set default audit file location
43
- RailsActiveMcp.config.audit_file ||= Rails.root.join('log', 'rails_active_mcp.log')
44
-
45
57
  # Validate configuration
46
- RailsActiveMcp.config.validate!
58
+ RailsActiveMcp.config.valid?
47
59
  end
48
60
 
49
- # Add our tools directory to the load path
50
- config.autoload_paths << root.join('lib', 'rails_active_mcp', 'tools')
61
+ # Add our SDK tools directory to the load path
62
+ config.autoload_paths << root.join('lib', 'rails_active_mcp', 'sdk', 'tools')
51
63
 
52
- # Ensure our tools are eager loaded in production
53
- config.eager_load_paths << root.join('lib', 'rails_active_mcp', 'tools')
64
+ # Ensure our SDK tools are eager loaded in production
65
+ config.eager_load_paths << root.join('lib', 'rails_active_mcp', 'sdk', 'tools')
54
66
 
55
67
  # Add rake tasks
56
68
  rake_tasks do
@@ -0,0 +1,183 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'mcp'
4
+ require 'stringio'
5
+ require 'fileutils'
6
+
7
+ # Require all SDK tools
8
+ require_relative 'tools/console_execute_tool'
9
+ require_relative 'tools/model_info_tool'
10
+ require_relative 'tools/safe_query_tool'
11
+ require_relative 'tools/dry_run_tool'
12
+
13
+ module RailsActiveMcp
14
+ module Sdk
15
+ class Server
16
+ attr_reader :mcp_server
17
+
18
+ def initialize
19
+ # Store original streams for restoration
20
+ @original_stdout = $stdout
21
+ @original_stderr = $stderr
22
+
23
+ # Set up output redirection BEFORE any Rails interaction
24
+ setup_output_redirection
25
+
26
+ # Configure MCP first
27
+ configure_mcp
28
+
29
+ # Create the MCP server with our tools
30
+ @mcp_server = MCP::Server.new(
31
+ name: 'rails-active-mcp',
32
+ version: RailsActiveMcp::VERSION,
33
+ tools: discover_tools,
34
+ server_context: server_context
35
+ )
36
+
37
+ # Set up server handlers
38
+ setup_server_handlers
39
+ end
40
+
41
+ def run_stdio
42
+ # Ensure output redirection is active for stdio mode
43
+ ensure_output_redirection_for_stdio
44
+
45
+ require 'mcp/transports/stdio'
46
+ transport = MCP::Transports::StdioTransport.new(@mcp_server)
47
+ transport.open
48
+ rescue StandardError => e
49
+ # Log to stderr (which is redirected to file) and re-raise
50
+ warn "[#{Time.now}] [RAILS-MCP] FATAL: SDK Server crashed: #{e.message}"
51
+ warn "[#{Time.now}] [RAILS-MCP] FATAL: #{e.backtrace.join("\n")}"
52
+ raise
53
+ ensure
54
+ restore_output_streams
55
+ end
56
+
57
+ def run_http(port: 3001)
58
+ # HTTP transport might not be available in the SDK yet
59
+ # For now, fall back to a basic implementation or error
60
+ raise NotImplementedError, 'HTTP transport not yet implemented with official MCP SDK'
61
+ end
62
+
63
+ private
64
+
65
+ def setup_output_redirection
66
+ # Skip redirection if in debug mode
67
+ return if ENV['RAILS_MCP_DEBUG'] == '1'
68
+
69
+ # Create log directory
70
+ log_dir = File.join(Dir.pwd, 'log')
71
+ FileUtils.mkdir_p(log_dir) unless Dir.exist?(log_dir)
72
+
73
+ # Redirect stderr to log file
74
+ stderr_log = File.join(log_dir, 'rails_mcp_stderr.log')
75
+ @stderr_file = File.open(stderr_log, 'a')
76
+ @stderr_file.sync = true
77
+ $stderr = @stderr_file
78
+
79
+ # Capture stdout during initialization to prevent interference
80
+ @stdout_buffer = StringIO.new
81
+ $stdout = @stdout_buffer
82
+
83
+ # Log redirection setup
84
+ warn "[#{Time.now}] [RAILS-MCP] INFO: Output redirection enabled. stderr -> #{stderr_log}"
85
+ end
86
+
87
+ def ensure_output_redirection_for_stdio
88
+ # Skip if in debug mode
89
+ return if ENV['RAILS_MCP_DEBUG'] == '1'
90
+
91
+ # Check if anything was captured during initialization
92
+ if @stdout_buffer && !@stdout_buffer.string.empty?
93
+ captured = @stdout_buffer.string
94
+ warn "[#{Time.now}] [RAILS-MCP] WARNING: Captured stdout during initialization: #{captured.inspect}"
95
+ end
96
+
97
+ # Restore original stdout for MCP communication, keep stderr redirected
98
+ $stdout = @original_stdout
99
+ warn "[#{Time.now}] [RAILS-MCP] INFO: stdout restored for MCP communication, stderr remains redirected"
100
+ end
101
+
102
+ def restore_output_streams
103
+ return if ENV['RAILS_MCP_DEBUG'] == '1'
104
+
105
+ begin
106
+ $stdout = @original_stdout if @original_stdout
107
+ $stderr = @original_stderr if @original_stderr
108
+ @stderr_file&.close
109
+ rescue StandardError => e
110
+ # Best effort cleanup
111
+ warn "Failed to restore output streams: #{e.message}" if @original_stderr
112
+ end
113
+ end
114
+
115
+ def discover_tools
116
+ [
117
+ RailsActiveMcp::Sdk::Tools::ConsoleExecuteTool,
118
+ RailsActiveMcp::Sdk::Tools::ModelInfoTool,
119
+ RailsActiveMcp::Sdk::Tools::SafeQueryTool,
120
+ RailsActiveMcp::Sdk::Tools::DryRunTool
121
+ ]
122
+ end
123
+
124
+ def configure_mcp
125
+ # Configure MCP SDK with Rails-specific handlers
126
+ MCP.configure do |config|
127
+ config.exception_reporter = method(:handle_rails_exception)
128
+ config.instrumentation_callback = method(:log_mcp_calls)
129
+ end
130
+ end
131
+
132
+ def setup_server_handlers
133
+ # Set up resource read handler (for future use)
134
+ @mcp_server.resources_read_handler do |params|
135
+ [
136
+ {
137
+ uri: params[:uri],
138
+ mimeType: 'text/plain',
139
+ text: "Rails Active MCP Resource: #{params[:uri]}"
140
+ }
141
+ ]
142
+ end
143
+ end
144
+
145
+ def handle_rails_exception(exception, context)
146
+ # Use stderr which is redirected to log file
147
+ warn "[#{Time.now}] [RAILS-MCP] ERROR: MCP Exception: #{exception.message}"
148
+ warn "[#{Time.now}] [RAILS-MCP] ERROR: #{exception.backtrace.join("\n")}" if ENV['RAILS_MCP_DEBUG'] == '1'
149
+
150
+ # Log context for debugging
151
+ return unless context && ENV['RAILS_MCP_DEBUG'] == '1'
152
+
153
+ warn "[#{Time.now}] [RAILS-MCP] DEBUG: MCP Context: #{context.inspect}"
154
+ end
155
+
156
+ def log_mcp_calls(data)
157
+ return unless ENV['RAILS_MCP_DEBUG'] == '1'
158
+
159
+ duration_ms = (data[:duration] * 1000).round(2)
160
+
161
+ log_message = "MCP #{data[:method]}"
162
+ log_message += " [#{data[:tool_name]}]" if data[:tool_name]
163
+ log_message += " (#{duration_ms}ms)"
164
+
165
+ warn "[#{Time.now}] [RAILS-MCP] DEBUG: #{log_message}"
166
+
167
+ # Log errors separately
168
+ return unless data[:error]
169
+
170
+ warn "[#{Time.now}] [RAILS-MCP] ERROR: MCP Call Error: #{data[:error]}"
171
+ end
172
+
173
+ def server_context
174
+ {
175
+ rails_env: defined?(Rails) ? Rails.env : 'unknown',
176
+ rails_root: defined?(Rails) && Rails.respond_to?(:root) ? Rails.root.to_s : Dir.pwd,
177
+ config: RailsActiveMcp.config,
178
+ gem_version: RailsActiveMcp::VERSION
179
+ }
180
+ end
181
+ end
182
+ end
183
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'mcp'
4
+
5
+ module RailsActiveMcp
6
+ module Sdk
7
+ module Tools
8
+ class ConsoleExecuteTool < MCP::Tool
9
+ description 'Execute Ruby code in Rails console with safety checks'
10
+
11
+ input_schema(
12
+ properties: {
13
+ code: {
14
+ type: 'string',
15
+ description: 'Ruby code to execute in Rails console'
16
+ },
17
+ safe_mode: {
18
+ type: 'boolean',
19
+ description: 'Enable safety checks (default: true)'
20
+ },
21
+ timeout: {
22
+ type: 'integer',
23
+ description: 'Timeout in seconds (default: 30)'
24
+ },
25
+ capture_output: {
26
+ type: 'boolean',
27
+ description: 'Capture console output (default: true)'
28
+ }
29
+ },
30
+ required: ['code']
31
+ )
32
+
33
+ annotations(
34
+ title: 'Rails Console Executor',
35
+ destructive_hint: true,
36
+ read_only_hint: false,
37
+ idempotent_hint: false,
38
+ open_world_hint: false
39
+ )
40
+
41
+ def self.call(code:, server_context:, safe_mode: true, timeout: 30, capture_output: true)
42
+ config = RailsActiveMcp.config
43
+
44
+ # Create executor with config
45
+ executor = RailsActiveMcp::ConsoleExecutor.new(config)
46
+
47
+ begin
48
+ result = executor.execute(
49
+ code,
50
+ timeout: timeout,
51
+ safe_mode: safe_mode,
52
+ capture_output: capture_output
53
+ )
54
+
55
+ if result[:success]
56
+ MCP::Tool::Response.new([
57
+ { type: 'text', text: format_success_result(result) }
58
+ ])
59
+ else
60
+ MCP::Tool::Response.new([
61
+ { type: 'text', text: format_error_result(result) }
62
+ ])
63
+ end
64
+ rescue RailsActiveMcp::SafetyError => e
65
+ error_response("Safety check failed: #{e.message}")
66
+ rescue RailsActiveMcp::TimeoutError => e
67
+ error_response("Execution timed out: #{e.message}")
68
+ rescue StandardError => e
69
+ error_response("Execution failed: #{e.message}")
70
+ end
71
+ end
72
+
73
+ private
74
+
75
+ def self.format_success_result(result)
76
+ output = []
77
+ output << "Code: #{result[:code]}"
78
+ output << "Result: #{result[:return_value_string] || result[:return_value]}"
79
+
80
+ output << "Output: #{result[:output]}" if result[:output].present?
81
+
82
+ output << "Execution time: #{result[:execution_time]}s" if result[:execution_time]
83
+
84
+ output << "Note: #{result[:note]}" if result[:note]
85
+
86
+ output.join("\n")
87
+ end
88
+
89
+ def self.format_error_result(result)
90
+ error_msg = "Error: #{result[:error]}"
91
+ error_msg += " (#{result[:error_class]})" if result[:error_class]
92
+ error_msg
93
+ end
94
+
95
+ def self.error_response(message)
96
+ MCP::Tool::Response.new([
97
+ { type: 'text', text: message }
98
+ ])
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'mcp'
4
+
5
+ module RailsActiveMcp
6
+ module Sdk
7
+ module Tools
8
+ class DryRunTool < MCP::Tool
9
+ description 'Analyze Ruby code for safety without executing it'
10
+
11
+ input_schema(
12
+ properties: {
13
+ code: {
14
+ type: 'string',
15
+ description: 'Ruby code to analyze for safety'
16
+ }
17
+ },
18
+ required: ['code']
19
+ )
20
+
21
+ annotations(
22
+ title: 'Code Safety Analyzer',
23
+ destructive_hint: false,
24
+ read_only_hint: true,
25
+ idempotent_hint: true,
26
+ open_world_hint: false
27
+ )
28
+
29
+ def self.call(code:, server_context:, check_safety: true, analyze_dependencies: true)
30
+ config = RailsActiveMcp.config
31
+
32
+ # Create safety checker
33
+
34
+ executor = RailsActiveMcp::ConsoleExecutor.new(config)
35
+ analysis = executor.dry_run(code)
36
+
37
+ output = []
38
+ output << "Code: #{analysis[:code]}"
39
+ output << "Safe: #{analysis[:safety_analysis][:safe] ? 'Yes' : 'No'}"
40
+ output << "Read-only: #{analysis[:safety_analysis][:read_only] ? 'Yes' : 'No'}"
41
+ output << "Risk level: #{analysis[:estimated_risk]}"
42
+ output << "Summary: #{analysis[:safety_analysis][:summary]}"
43
+
44
+ if analysis[:safety_analysis][:violations].any?
45
+ output << "\nViolations:"
46
+ analysis[:safety_analysis][:violations].each do |violation|
47
+ output << " - #{violation[:description]} (#{violation[:severity]})"
48
+ end
49
+ end
50
+
51
+ if analysis[:recommendations].any?
52
+ output << "\nRecommendations:"
53
+ analysis[:recommendations].each do |rec|
54
+ output << " - #{rec}"
55
+ end
56
+ end
57
+
58
+ MCP::Tool::Response.new([
59
+ { type: 'text', text: output.join("\n") }
60
+ ])
61
+ end
62
+
63
+ private
64
+
65
+ def self.error_response(message)
66
+ MCP::Tool::Response.new([
67
+ { type: 'text', text: message }
68
+ ])
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -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
  # frozen_string_literal: true
2
2
 
3
3
  module RailsActiveMcp
4
- VERSION = '0.1.7'
4
+ VERSION = '2.0.7'
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"
@@ -32,7 +32,10 @@ Gem::Specification.new do |spec|
32
32
 
33
33
  # Runtime dependencies - more flexible Rails version support
34
34
  spec.add_runtime_dependency 'concurrent-ruby', '~> 1.3'
35
- spec.add_runtime_dependency 'rails', '>= 6.1', '< 8.0'
35
+ spec.add_runtime_dependency 'rails', '>= 6.1', '< 9.0'
36
+
37
+ # MCP SDK - Core protocol implementation
38
+ spec.add_runtime_dependency 'mcp', '~> 0.1.0'
36
39
 
37
40
  # Core dependencies
38
41
  spec.add_dependency 'json', '~> 2.0'