actionmcp 0.50.1 → 0.50.3

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.
@@ -167,9 +167,7 @@ module ActionMCP
167
167
  )
168
168
 
169
169
  # Maintain cache limit by removing oldest events if needed
170
- if sse_events.count > max_events
171
- sse_events.order(event_id: :asc).limit(sse_events.count - max_events).destroy_all
172
- end
170
+ sse_events.order(event_id: :asc).limit(sse_events.count - max_events).destroy_all if sse_events.count > max_events
173
171
 
174
172
  event
175
173
  end
@@ -240,10 +238,10 @@ module ActionMCP
240
238
  tool_name = normalize_name(tool_class_or_name, :tool)
241
239
  self.tool_registry ||= []
242
240
 
243
- if self.tool_registry.delete(tool_name)
244
- save!
245
- send_tools_list_changed_notification
246
- end
241
+ return unless self.tool_registry.delete(tool_name)
242
+
243
+ save!
244
+ send_tools_list_changed_notification
247
245
  end
248
246
 
249
247
  def register_prompt(prompt_class_or_name)
@@ -263,10 +261,10 @@ module ActionMCP
263
261
  prompt_name = normalize_name(prompt_class_or_name, :prompt)
264
262
  self.prompt_registry ||= []
265
263
 
266
- if self.prompt_registry.delete(prompt_name)
267
- save!
268
- send_prompts_list_changed_notification
269
- end
264
+ return unless self.prompt_registry.delete(prompt_name)
265
+
266
+ save!
267
+ send_prompts_list_changed_notification
270
268
  end
271
269
 
272
270
  def register_resource_template(template_class_or_name)
@@ -286,28 +284,34 @@ module ActionMCP
286
284
  template_name = normalize_name(template_class_or_name, :resource_template)
287
285
  self.resource_registry ||= []
288
286
 
289
- if self.resource_registry.delete(template_name)
290
- save!
291
- send_resources_list_changed_notification
292
- end
287
+ return unless self.resource_registry.delete(template_name)
288
+
289
+ save!
290
+ send_resources_list_changed_notification
293
291
  end
294
292
 
295
293
  # Get registered items for this session
296
294
  def registered_tools
297
295
  (self.tool_registry || []).filter_map do |tool_name|
298
- ActionMCP::ToolsRegistry.find(tool_name) rescue nil
296
+ ActionMCP::ToolsRegistry.find(tool_name)
297
+ rescue StandardError
298
+ nil
299
299
  end
300
300
  end
301
301
 
302
302
  def registered_prompts
303
303
  (self.prompt_registry || []).filter_map do |prompt_name|
304
- ActionMCP::PromptsRegistry.find(prompt_name) rescue nil
304
+ ActionMCP::PromptsRegistry.find(prompt_name)
305
+ rescue StandardError
306
+ nil
305
307
  end
306
308
  end
307
309
 
308
310
  def registered_resource_templates
309
311
  (self.resource_registry || []).filter_map do |template_name|
310
- ActionMCP::ResourceTemplatesRegistry.find(template_name) rescue nil
312
+ ActionMCP::ResourceTemplatesRegistry.find(template_name)
313
+ rescue StandardError
314
+ nil
311
315
  end
312
316
  end
313
317
 
@@ -379,21 +383,21 @@ module ActionMCP
379
383
 
380
384
  def send_tools_list_changed_notification
381
385
  # Only send if server capabilities allow it
382
- if server_capabilities.dig("tools", "listChanged")
383
- write(JSON_RPC::Notification.new(method: "notifications/tools/list_changed"))
384
- end
386
+ return unless server_capabilities.dig("tools", "listChanged")
387
+
388
+ write(JSON_RPC::Notification.new(method: "notifications/tools/list_changed"))
385
389
  end
386
390
 
387
391
  def send_prompts_list_changed_notification
388
- if server_capabilities.dig("prompts", "listChanged")
389
- write(JSON_RPC::Notification.new(method: "notifications/prompts/list_changed"))
390
- end
392
+ return unless server_capabilities.dig("prompts", "listChanged")
393
+
394
+ write(JSON_RPC::Notification.new(method: "notifications/prompts/list_changed"))
391
395
  end
392
396
 
393
397
  def send_resources_list_changed_notification
394
- if server_capabilities.dig("resources", "listChanged")
395
- write(JSON_RPC::Notification.new(method: "notifications/resources/list_changed"))
396
- end
398
+ return unless server_capabilities.dig("resources", "listChanged")
399
+
400
+ write(JSON_RPC::Notification.new(method: "notifications/resources/list_changed"))
397
401
  end
398
402
  end
399
403
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # app/models/concerns/mcp_console_helpers.rb
2
4
  module MCPConsoleHelpers
3
5
  extend ActiveSupport::Concern
@@ -12,9 +14,7 @@ module MCPConsoleHelpers
12
14
 
13
15
  messages.each do |msg|
14
16
  puts msg.inspect
15
- if msg.data&.dig("method")
16
- puts " └─ #{msg.data['method']}"
17
- end
17
+ puts " └─ #{msg.data['method']}" if msg.data&.dig("method")
18
18
  puts
19
19
  end
20
20
  end
@@ -1,10 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # app/models/concerns/mcp_message_inspect.rb
2
4
  module MCPMessageInspect
3
5
  extend ActiveSupport::Concern
4
6
 
5
7
  def inspect(show_data: false)
6
8
  if show_data
7
- super() # Rails default inspect
9
+ super() # Rails default inspect
8
10
  else
9
11
  build_summary_inspect
10
12
  end
@@ -54,9 +56,7 @@ module MCPMessageInspect
54
56
 
55
57
  def console?
56
58
  # Check if we're in a Rails console environment
57
- defined?(Rails::Console) ||
58
- defined?(::Rails.application) && Rails.application.console? ||
59
- (defined?(IRB) && IRB.CurrentContext.kind_of?(IRB::ExtendCommandBundle))
59
+ defined?(Rails::Console)
60
60
  end
61
61
 
62
62
  def colorize(text, color)
data/config/routes.rb CHANGED
@@ -4,7 +4,7 @@ ActionMCP::Engine.routes.draw do
4
4
  get "/up", to: "/rails/health#show", as: :action_mcp_health_check
5
5
 
6
6
  # MCP 2025-03-26 Spec routes
7
- get "/", to: "unified#show", as: :mcp_get
8
- post "/", to: "unified#create", as: :mcp_post
9
- delete "/", to: "unified#destroy", as: :mcp_delete
7
+ get "/", to: "application#show", as: :mcp_get
8
+ post "/", to: "application#create", as: :mcp_post
9
+ delete "/", to: "application#destroy", as: :mcp_delete
10
10
  end
@@ -29,10 +29,10 @@ class ConsolidatedMigration < ActiveRecord::Migration[8.0]
29
29
  unless table_exists?(:action_mcp_session_messages)
30
30
  create_table :action_mcp_session_messages do |t|
31
31
  t.references :session, null: false,
32
- foreign_key: { to_table: :action_mcp_sessions,
33
- on_delete: :cascade,
34
- on_update: :cascade,
35
- name: 'fk_action_mcp_session_messages_session_id' }, type: :string
32
+ foreign_key: { to_table: :action_mcp_sessions,
33
+ on_delete: :cascade,
34
+ on_update: :cascade,
35
+ name: 'fk_action_mcp_session_messages_session_id' }, type: :string
36
36
  t.string :direction, null: false, comment: 'The message recipient', default: 'client'
37
37
  t.string :message_type, null: false, comment: 'The type of the message'
38
38
  t.string :jsonrpc_id
@@ -48,9 +48,9 @@ class ConsolidatedMigration < ActiveRecord::Migration[8.0]
48
48
  unless table_exists?(:action_mcp_session_subscriptions)
49
49
  create_table :action_mcp_session_subscriptions do |t|
50
50
  t.references :session,
51
- null: false,
52
- foreign_key: { to_table: :action_mcp_sessions, on_delete: :cascade },
53
- type: :string
51
+ null: false,
52
+ foreign_key: { to_table: :action_mcp_sessions, on_delete: :cascade },
53
+ type: :string
54
54
  t.string :uri, null: false
55
55
  t.datetime :last_notification_at
56
56
  t.timestamps
@@ -61,9 +61,9 @@ class ConsolidatedMigration < ActiveRecord::Migration[8.0]
61
61
  unless table_exists?(:action_mcp_session_resources)
62
62
  create_table :action_mcp_session_resources do |t|
63
63
  t.references :session,
64
- null: false,
65
- foreign_key: { to_table: :action_mcp_sessions, on_delete: :cascade },
66
- type: :string
64
+ null: false,
65
+ foreign_key: { to_table: :action_mcp_sessions, on_delete: :cascade },
66
+ type: :string
67
67
  t.string :uri, null: false
68
68
  t.string :name
69
69
  t.text :description
@@ -84,7 +84,7 @@ class ConsolidatedMigration < ActiveRecord::Migration[8.0]
84
84
  t.timestamps
85
85
 
86
86
  # Index for efficiently retrieving events after a given ID for a specific session
87
- t.index [ :session_id, :event_id ], unique: true
87
+ t.index %i[session_id event_id], unique: true
88
88
  t.index :created_at # For cleanup of old events
89
89
  end
90
90
  end
@@ -111,27 +111,28 @@ class ConsolidatedMigration < ActiveRecord::Migration[8.0]
111
111
  end
112
112
 
113
113
  # For action_mcp_session_messages
114
- if table_exists?(:action_mcp_session_messages)
115
- unless column_exists?(:action_mcp_session_messages, :is_ping)
116
- add_column :action_mcp_session_messages, :is_ping, :boolean, default: false, null: false, comment: 'Whether the message is a ping'
117
- end
114
+ return unless table_exists?(:action_mcp_session_messages)
118
115
 
119
- unless column_exists?(:action_mcp_session_messages, :request_acknowledged)
120
- add_column :action_mcp_session_messages, :request_acknowledged, :boolean, default: false, null: false
121
- end
116
+ unless column_exists?(:action_mcp_session_messages, :is_ping)
117
+ add_column :action_mcp_session_messages, :is_ping, :boolean, default: false, null: false,
118
+ comment: 'Whether the message is a ping'
119
+ end
122
120
 
123
- unless column_exists?(:action_mcp_session_messages, :request_cancelled)
124
- add_column :action_mcp_session_messages, :request_cancelled, :boolean, null: false, default: false
125
- end
121
+ unless column_exists?(:action_mcp_session_messages, :request_acknowledged)
122
+ add_column :action_mcp_session_messages, :request_acknowledged, :boolean, default: false, null: false
123
+ end
126
124
 
127
- if column_exists?(:action_mcp_session_messages, :message_text)
128
- remove_column :action_mcp_session_messages, :message_text
129
- end
125
+ unless column_exists?(:action_mcp_session_messages, :request_cancelled)
126
+ add_column :action_mcp_session_messages, :request_cancelled, :boolean, null: false, default: false
127
+ end
130
128
 
131
- if column_exists?(:action_mcp_session_messages, :direction)
132
- change_column_comment :action_mcp_session_messages, :direction, 'The message recipient'
133
- end
129
+ if column_exists?(:action_mcp_session_messages, :message_text)
130
+ remove_column :action_mcp_session_messages, :message_text
134
131
  end
132
+
133
+ return unless column_exists?(:action_mcp_session_messages, :direction)
134
+
135
+ change_column_comment :action_mcp_session_messages, :direction, 'The message recipient'
135
136
  end
136
137
 
137
138
  private
data/exe/actionmcp_cli CHANGED
@@ -56,7 +56,7 @@ if endpoint.nil?
56
56
  end
57
57
 
58
58
  unless endpoint =~ %r{\Ahttps?://}
59
- puts "Error: Only HTTP(S) endpoints are supported. STDIO/command endpoints are not allowed."
59
+ puts 'Error: Only HTTP(S) endpoints are supported. STDIO/command endpoints are not allowed.'
60
60
  exit 1
61
61
  end
62
62
 
@@ -13,12 +13,12 @@ module ActionMCP
13
13
  # client = ActionMCP.create_client("http://127.0.0.1:3001/action_mcp")
14
14
  # client.connect
15
15
  def self.create_client(endpoint, logger: Logger.new($stdout), **options)
16
- if endpoint =~ %r{\Ahttps?://}
17
- logger.info("Creating SSE client for endpoint: #{endpoint}")
18
- Client::SSEClient.new(endpoint, logger: logger, **options)
19
- else
16
+ unless endpoint =~ %r{\Ahttps?://}
20
17
  raise ArgumentError, "Only HTTP(S) endpoints are supported. STDIO and other transports are not supported."
21
18
  end
19
+
20
+ logger.info("Creating SSE client for endpoint: #{endpoint}")
21
+ Client::SSEClient.new(endpoint, logger: logger, **options)
22
22
  end
23
23
 
24
24
  module Client
@@ -47,5 +47,32 @@ module ActionMCP
47
47
  initializer "action_mcp.logger" do
48
48
  ActionMCP.logger = ::Rails.logger
49
49
  end
50
+
51
+ # Add metrics instrumentation
52
+ initializer "action_mcp.metrics" do
53
+ ActiveSupport::Notifications.subscribe "process_action.action_controller" do |name, start, finish, id, payload|
54
+ if payload[:controller].to_s.start_with?("ActionMCP::")
55
+ # Process action through our log subscriber
56
+ ActionMCP::LogSubscriber.new.process_action(
57
+ ActiveSupport::Notifications::Event.new(name, start, finish, id, payload)
58
+ )
59
+ end
60
+ end
61
+
62
+ # Set up default event metrics
63
+ # SQL queries
64
+ ActionMCP::LogSubscriber.subscribe_event "sql.active_record", :db_queries, accumulate: true
65
+
66
+ # Query runtime
67
+ ActionMCP::LogSubscriber.subscribe_event "sql.active_record", :sql_runtime,
68
+ duration: true, accumulate: true
69
+
70
+ # View rendering
71
+ ActionMCP::LogSubscriber.subscribe_event "render_template.action_view", :view_runtime,
72
+ duration: true, accumulate: true
73
+
74
+ # Cache operations
75
+ ActionMCP::LogSubscriber.subscribe_event "cache_*.*", :cache_operations, accumulate: true
76
+ end
50
77
  end
51
78
  end
@@ -28,7 +28,7 @@ module ActionMCP
28
28
  def log_process_action(payload)
29
29
  messages = super
30
30
  mcp_runtime = payload[:mcp_runtime]
31
- messages << (format("MCP: %.1fms", mcp_runtime.to_f)) if mcp_runtime
31
+ messages << format("MCP: %.1fms", mcp_runtime.to_f) if mcp_runtime
32
32
  messages
33
33
  end
34
34
  end
@@ -75,6 +75,7 @@ module ActionMCP
75
75
  # @param request [Hash]
76
76
  def process_request(request)
77
77
  return unless valid_request?(request)
78
+
78
79
  request = request.with_indifferent_access
79
80
 
80
81
  read(request)
@@ -2,6 +2,11 @@
2
2
 
3
3
  module ActionMCP
4
4
  class LogSubscriber < ActiveSupport::LogSubscriber
5
+ # Thread-local storage for additional metrics
6
+ class << self
7
+ attr_accessor :custom_metrics, :subscribed_events, :formatters, :metric_groups
8
+ end
9
+
5
10
  def self.reset_runtime
6
11
  # Get the combined runtime from both tool and prompt operations
7
12
  tool_rt = Thread.current[:mcp_tool_runtime] || 0
@@ -26,6 +31,161 @@ module ActionMCP
26
31
  Thread.current[:mcp_prompt_runtime] += event.duration
27
32
  end
28
33
 
34
+ # Add a custom metric to be included in logs
35
+ def self.add_metric(name, value)
36
+ self.custom_metrics ||= {}
37
+ self.custom_metrics[name] = value
38
+ end
39
+
40
+ # Measure execution time of a block and add as metric
41
+ def self.measure_metric(name)
42
+ start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
43
+ result = yield
44
+ duration = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time) * 1000.0
45
+
46
+ add_metric(name, duration)
47
+ result
48
+ end
49
+
50
+ # Reset all custom metrics
51
+ def self.reset_metrics
52
+ self.custom_metrics = nil
53
+ end
54
+
55
+ # Subscribe to a Rails event to capture metrics
56
+ # @param pattern [String] Event name pattern (e.g., "sql.active_record")
57
+ # @param metric_name [Symbol] Name to use for the metric
58
+ # @param options [Hash] Options for capturing the metric
59
+ def self.subscribe_event(pattern, metric_name, options = {})
60
+ self.subscribed_events ||= {}
61
+
62
+ # Store subscription info
63
+ self.subscribed_events[pattern] = {
64
+ metric_name: metric_name,
65
+ options: options
66
+ }
67
+
68
+ # Create the actual subscription
69
+ ActiveSupport::Notifications.subscribe(pattern) do |*args|
70
+ event = ActiveSupport::Notifications::Event.new(*args)
71
+
72
+ # Extract value based on options
73
+ value = if options[:duration]
74
+ event.duration
75
+ elsif options[:extract_value].respond_to?(:call)
76
+ options[:extract_value].call(event)
77
+ else
78
+ 1 # Default to count
79
+ end
80
+
81
+ # Accumulate or set the metric
82
+ if options[:accumulate]
83
+ self.custom_metrics ||= {}
84
+ self.custom_metrics[metric_name] ||= 0
85
+ self.custom_metrics[metric_name] += value
86
+ else
87
+ add_metric(metric_name, value)
88
+ end
89
+ end
90
+ end
91
+
92
+ # Format metrics for display in logs
93
+ def self.format_metrics
94
+ return nil if custom_metrics.nil? || custom_metrics.empty?
95
+
96
+ # If grouping is enabled, organize metrics by groups
97
+ if metric_groups.present?
98
+ grouped_metrics = {}
99
+
100
+ # Initialize groups with empty arrays
101
+ metric_groups.each_key do |group_name|
102
+ grouped_metrics[group_name] = []
103
+ end
104
+
105
+ # Add "other" group for ungrouped metrics
106
+ grouped_metrics[:other] = []
107
+
108
+ # Assign metrics to their groups
109
+ custom_metrics.each do |key, value|
110
+ group = nil
111
+
112
+ # Find which group this metric belongs to
113
+ metric_groups.each do |group_name, metrics|
114
+ if metrics.include?(key)
115
+ group = group_name
116
+ break
117
+ end
118
+ end
119
+
120
+ # Format the metric
121
+ formatter = formatters&.dig(key)
122
+ formatted_value = if formatter.respond_to?(:call)
123
+ formatter.call(value)
124
+ elsif value.is_a?(Float)
125
+ format("%.1fms", value)
126
+ else
127
+ value.to_s
128
+ end
129
+
130
+ formatted_metric = "#{key}: #{formatted_value}"
131
+
132
+ # Add to appropriate group (or "other")
133
+ if group
134
+ grouped_metrics[group] << formatted_metric
135
+ else
136
+ grouped_metrics[:other] << formatted_metric
137
+ end
138
+ end
139
+
140
+ # Join metrics within groups, then join groups
141
+ grouped_metrics.map do |_group, metrics|
142
+ next if metrics.empty?
143
+
144
+ metrics.join(" | ")
145
+ end.compact.join(" | ")
146
+ else
147
+ # No grouping, just format all metrics
148
+ custom_metrics.map do |key, value|
149
+ formatter = formatters&.dig(key)
150
+ formatted_value = if formatter.respond_to?(:call)
151
+ formatter.call(value)
152
+ elsif value.is_a?(Float)
153
+ format("%.1fms", value)
154
+ else
155
+ value.to_s
156
+ end
157
+ "#{key}: #{formatted_value}"
158
+ end.join(" | ")
159
+ end
160
+ end
161
+
162
+ # Register a custom formatter for a specific metric
163
+ # @param metric_name [Symbol] The name of the metric
164
+ # @param block [Proc] The formatter block that takes the value and returns a string
165
+ def self.register_formatter(metric_name, &block)
166
+ self.formatters ||= {}
167
+ self.formatters[metric_name] = block
168
+ end
169
+
170
+ # Define a group of related metrics
171
+ # @param group_name [Symbol] The name of the metric group
172
+ # @param metrics [Array<Symbol>] The metrics that belong to this group
173
+ def self.define_metric_group(group_name, metrics)
174
+ self.metric_groups ||= {}
175
+ self.metric_groups[group_name] = metrics
176
+ end
177
+
178
+ # Enhance process_action to include our custom metrics
179
+ def process_action(event)
180
+ return unless logger.info?
181
+
182
+ return unless self.class.custom_metrics.present?
183
+
184
+ metrics_msg = self.class.format_metrics
185
+ event.payload[:message] = "#{event.payload[:message]} | #{metrics_msg}" if metrics_msg
186
+ self.class.reset_metrics
187
+ end
188
+
29
189
  attach_to :action_mcp
30
190
  end
31
191
  end
@@ -13,7 +13,7 @@ module ActionMCP
13
13
 
14
14
  # Track all registered templates
15
15
  @registered_templates = []
16
- attr_reader :execution_context
16
+ attr_reader :execution_context, :description, :uri_template, :mime_type
17
17
 
18
18
  class << self
19
19
  attr_reader :registered_templates, :description, :uri_template,
@@ -227,8 +227,6 @@ module ActionMCP
227
227
  end
228
228
  end
229
229
 
230
- attr_reader :description, :uri_template, :mime_type
231
-
232
230
  def call
233
231
  run_callbacks :resolve do
234
232
  resolve
@@ -16,7 +16,8 @@ module ActionMCP
16
16
 
17
17
  unless client_protocol_version.is_a?(String) && client_protocol_version.present?
18
18
  send_jsonrpc_error(request_id, :invalid_params, "Missing or invalid 'protocolVersion'")
19
- return { type: :error, id: request_id, payload: { jsonrpc: "2.0", id: request_id, error: { code: -32602, message: "Missing or invalid 'protocolVersion'" } } }
19
+ return { type: :error, id: request_id,
20
+ payload: { jsonrpc: "2.0", id: request_id, error: { code: -32_602, message: "Missing or invalid 'protocolVersion'" } } }
20
21
  end
21
22
  # Check if the protocol version is supported
22
23
  unless ActionMCP::SUPPORTED_VERSIONS.include?(client_protocol_version)
@@ -25,20 +26,21 @@ module ActionMCP
25
26
  requested: client_protocol_version
26
27
  }
27
28
  send_jsonrpc_error(request_id, :invalid_params, "Unsupported protocol version", error_data)
28
- return { type: :error, id: request_id, payload: { jsonrpc: "2.0", id: request_id, error: { code: -32602, message: "Unsupported protocol version", data: error_data } } }
29
+ return { type: :error, id: request_id,
30
+ payload: { jsonrpc: "2.0", id: request_id, error: { code: -32_602, message: "Unsupported protocol version", data: error_data } } }
29
31
  end
30
32
 
31
33
  unless client_info.is_a?(Hash)
32
34
  send_jsonrpc_error(request_id, :invalid_params, "Missing or invalid 'clientInfo'")
33
- return { type: :error, id: request_id, payload: { jsonrpc: "2.0", id: request_id, error: { code: -32602, message: "Missing or invalid 'clientInfo'" } } }
35
+ return { type: :error, id: request_id,
36
+ payload: { jsonrpc: "2.0", id: request_id, error: { code: -32_602, message: "Missing or invalid 'clientInfo'" } } }
34
37
  end
35
38
  unless client_capabilities.is_a?(Hash)
36
39
  send_jsonrpc_error(request_id, :invalid_params, "Missing or invalid 'capabilities'")
37
- return { type: :error, id: request_id, payload: { jsonrpc: "2.0", id: request_id, error: { code: -32602, message: "Missing or invalid 'capabilities'" } } }
40
+ return { type: :error, id: request_id,
41
+ payload: { jsonrpc: "2.0", id: request_id, error: { code: -32_602, message: "Missing or invalid 'capabilities'" } } }
38
42
  end
39
43
 
40
-
41
-
42
44
  # Store client information
43
45
  session.store_client_info(client_info)
44
46
  session.store_client_capabilities(client_capabilities)
@@ -47,12 +49,13 @@ module ActionMCP
47
49
  # Initialize the session
48
50
  unless session.initialize!
49
51
  send_jsonrpc_error(request_id, :internal_error, "Failed to initialize session")
50
- return { type: :error, id: request_id, payload: { jsonrpc: "2.0", id: request_id, error: { code: -32603, message: "Failed to initialize session" } } }
52
+ return { type: :error, id: request_id,
53
+ payload: { jsonrpc: "2.0", id: request_id, error: { code: -32_603, message: "Failed to initialize session" } } }
51
54
  end
52
55
 
53
56
  # Send the successful response with the protocol version the client requested
54
57
  capabilities_payload = session.server_capabilities_payload
55
- capabilities_payload[:protocolVersion] = client_protocol_version # Use the client's requested version
58
+ capabilities_payload[:protocolVersion] = client_protocol_version # Use the client's requested version
56
59
 
57
60
  send_jsonrpc_response(request_id, result: capabilities_payload)
58
61
  { type: :responses, id: request_id, payload: { jsonrpc: "2.0", id: request_id, result: capabilities_payload } }
@@ -39,8 +39,10 @@ module ActionMCP
39
39
 
40
40
  yaml = ERB.new(File.read(@config_path)).result
41
41
  YAML.safe_load(yaml, aliases: true) || {}
42
- rescue => e
43
- Rails.logger.error("Error loading ActionMCP config: #{e.message}") if defined?(Rails) && Rails.respond_to?(:logger)
42
+ rescue StandardError => e
43
+ if defined?(Rails) && Rails.respond_to?(:logger)
44
+ Rails.logger.error("Error loading ActionMCP config: #{e.message}")
45
+ end
44
46
  {}
45
47
  end
46
48
 
@@ -52,6 +54,7 @@ module ActionMCP
52
54
  while path != "/"
53
55
  config_path = File.join(path, "config", "mcp.yml")
54
56
  return config_path if File.exist?(config_path)
57
+
55
58
  path = File.dirname(path)
56
59
  end
57
60