Class: ActionMCP::Configuration

Inherits:
Object
  • Object
show all
Defined in:
lib/action_mcp/configuration.rb

Overview

Configuration class to hold settings for the ActionMCP server.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeConfiguration

Returns a new instance of Configuration.



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/action_mcp/configuration.rb', line 58

def initialize
  @logging_enabled = false
  @list_changed = true
  @logging_level = :warning
  @resources_subscribe = false
  @verbose_logging = false
  @active_profile = :primary
  @profiles = default_profiles

  # Authentication defaults - empty means all configured identifiers will be tried
  @authentication_methods = []

  @protocol_version = "2025-11-25"

  # Tasks defaults (MCP 2025-11-25)
  @tasks_enabled = false
  @tasks_list_enabled = true
  @tasks_cancel_enabled = true
  @tasks_result_poll_interval = 0.25

  # MCP Apps extension defaults to explicit opt-in.
  @mcp_apps_enabled = false
  # Directory holding compiled view bundles + manifest.json, relative to
  # Rails.root unless absolute.
  @mcp_apps_views_path = ".action_mcp/views"

  # Pagination - nil means off. Set a number to enable with that page size.
  # Most MCP clients (including Claude Code) don't follow nextCursor yet.
  @pagination_page_size = nil

  # Server instructions - empty by default
  @server_instructions = []

  # Gateway - resolved lazily to account for Zeitwerk autoloading
  @gateway_class_name = nil

  # Session Store
  @session_store_type = Rails.env.production? ? :active_record : :volatile
  @client_session_store_type = nil # defaults to session_store_type
  @server_session_store_type = nil # defaults to session_store_type

  # Whitelist of allowed identity attribute names to prevent method shadowing
  # and unauthorized attribute assignment. Extend this list if you use custom
  # identifier names in your GatewayIdentifier implementations.
  @allowed_identity_keys = %w[user api_key jwt bearer token account session].freeze

  # Path for JSON-RPC endpoint
  @base_path = "/"

  # Browser origins allowed to reach a local server. Remote deployments must
  # explicitly add their trusted origin hosts.
  @allowed_origins = %w[localhost 127.0.0.1 ::1].freeze
end

Instance Attribute Details

#active_profileObject

Get active profile (considering thread-local override)



181
182
183
# File 'lib/action_mcp/configuration.rb', line 181

def active_profile
  @active_profile
end

#allowed_identity_keysObject

Returns the value of attribute allowed_identity_keys.



21
22
23
# File 'lib/action_mcp/configuration.rb', line 21

def allowed_identity_keys
  @allowed_identity_keys
end

#allowed_originsObject

Returns the value of attribute allowed_origins.



21
22
23
# File 'lib/action_mcp/configuration.rb', line 21

def allowed_origins
  @allowed_origins
end

#authentication_methodsObject

Returns the value of attribute authentication_methods.



21
22
23
# File 'lib/action_mcp/configuration.rb', line 21

def authentication_methods
  @authentication_methods
end

#base_pathObject

Returns the value of attribute base_path.



21
22
23
# File 'lib/action_mcp/configuration.rb', line 21

def base_path
  @base_path
end

#client_session_store_typeObject

Get effective client session store type (falls back to global session_store_type)



321
322
323
# File 'lib/action_mcp/configuration.rb', line 321

def client_session_store_type
  @client_session_store_type
end

#connects_toObject

Returns the value of attribute connects_to.



21
22
23
# File 'lib/action_mcp/configuration.rb', line 21

def connects_to
  @connects_to
end

#gateway_classObject

Returns the value of attribute gateway_class.



21
22
23
# File 'lib/action_mcp/configuration.rb', line 21

def gateway_class
  @gateway_class
end

#list_changedBoolean

Returns Whether to send a listChanged notification for tools, prompts, and resources.

Returns:

  • (Boolean)

    Whether to send a listChanged notification for tools, prompts, and resources.



20
# File 'lib/action_mcp/configuration.rb', line 20

attr_writer :name, :version

#logging_enabledBoolean

Returns Whether logging is enabled.

Returns:

  • (Boolean)

    Whether logging is enabled.



20
# File 'lib/action_mcp/configuration.rb', line 20

attr_writer :name, :version

#logging_levelSymbol

Returns The logging level.

Returns:

  • (Symbol)

    The logging level.



20
# File 'lib/action_mcp/configuration.rb', line 20

attr_writer :name, :version

#max_queueObject

Returns the value of attribute max_queue.



21
22
23
# File 'lib/action_mcp/configuration.rb', line 21

def max_queue
  @max_queue
end

#max_threadsObject

Returns the value of attribute max_threads.



21
22
23
# File 'lib/action_mcp/configuration.rb', line 21

def max_threads
  @max_threads
end

#mcp_apps_enabledObject

Returns the value of attribute mcp_apps_enabled.



21
22
23
# File 'lib/action_mcp/configuration.rb', line 21

def mcp_apps_enabled
  @mcp_apps_enabled
end

#mcp_apps_views_pathObject

Returns the value of attribute mcp_apps_views_path.



21
22
23
# File 'lib/action_mcp/configuration.rb', line 21

def mcp_apps_views_path
  @mcp_apps_views_path
end

#min_threadsObject

Returns the value of attribute min_threads.



21
22
23
# File 'lib/action_mcp/configuration.rb', line 21

def min_threads
  @min_threads
end

#nameString

Returns The name of the MCP Server.

Returns:

  • (String)

    The name of the MCP Server.



20
# File 'lib/action_mcp/configuration.rb', line 20

attr_writer :name, :version

#pagination_page_sizeObject

Pagination page size. nil = pagination disabled, positive integer = enabled.



149
150
151
# File 'lib/action_mcp/configuration.rb', line 149

def pagination_page_size
  @pagination_page_size
end

#profilesObject

Returns the value of attribute profiles.



21
22
23
# File 'lib/action_mcp/configuration.rb', line 21

def profiles
  @profiles
end

#protocol_versionObject

Returns the value of attribute protocol_version.



21
22
23
# File 'lib/action_mcp/configuration.rb', line 21

def protocol_version
  @protocol_version
end

#resources_subscribeBoolean

Returns Whether to subscribe to resources.

Returns:

  • (Boolean)

    Whether to subscribe to resources.



20
# File 'lib/action_mcp/configuration.rb', line 20

attr_writer :name, :version

#server_session_store_typeObject

Get effective server session store type (falls back to global session_store_type)



326
327
328
# File 'lib/action_mcp/configuration.rb', line 326

def server_session_store_type
  @server_session_store_type
end

#session_store_typeObject

Returns the value of attribute session_store_type.



21
22
23
# File 'lib/action_mcp/configuration.rb', line 21

def session_store_type
  @session_store_type
end

#tasks_cancel_enabledObject

Returns the value of attribute tasks_cancel_enabled.



21
22
23
# File 'lib/action_mcp/configuration.rb', line 21

def tasks_cancel_enabled
  @tasks_cancel_enabled
end

#tasks_enabledObject

Returns the value of attribute tasks_enabled.



21
22
23
# File 'lib/action_mcp/configuration.rb', line 21

def tasks_enabled
  @tasks_enabled
end

#tasks_list_enabledObject

Returns the value of attribute tasks_list_enabled.



21
22
23
# File 'lib/action_mcp/configuration.rb', line 21

def tasks_list_enabled
  @tasks_list_enabled
end

#tasks_result_poll_intervalObject

Returns the value of attribute tasks_result_poll_interval.



21
22
23
# File 'lib/action_mcp/configuration.rb', line 21

def tasks_result_poll_interval
  @tasks_result_poll_interval
end

#verbose_loggingObject

Returns the value of attribute verbose_logging.



21
22
23
# File 'lib/action_mcp/configuration.rb', line 21

def verbose_logging
  @verbose_logging
end

#versionString

Returns The version of the MCP Server.

Returns:

  • (String)

    The version of the MCP Server.



20
# File 'lib/action_mcp/configuration.rb', line 20

attr_writer :name, :version

Instance Method Details

#apply_profile_optionsObject



330
331
332
333
334
335
336
337
338
339
340
# File 'lib/action_mcp/configuration.rb', line 330

def apply_profile_options
  profile = @profiles[active_profile]
  return unless profile && profile[:options]

  options = profile[:options]
  @list_changed = options[:list_changed] unless options[:list_changed].nil?
  @logging_enabled = options[:logging_enabled] unless options[:logging_enabled].nil?
  @logging_level = options[:logging_level] unless options[:logging_level].nil?
  @resources_subscribe = options[:resources_subscribe] unless options[:resources_subscribe].nil?
  @mcp_apps_enabled = boolean_config_value(options[:mcp_apps_enabled]) unless options[:mcp_apps_enabled].nil?
end

#capabilitiesObject

Returns capabilities based on active profile



280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
# File 'lib/action_mcp/configuration.rb', line 280

def capabilities
  capabilities = {}
  profile = @profiles[active_profile]

  # Check profile configuration instead of registry contents
  # If profile includes tools (either "all" or specific tools), advertise tools capability
  capabilities[:tools] = { listChanged: @list_changed } if profile && profile[:tools]&.any?

  # If profile includes prompts, advertise prompts capability
  capabilities[:prompts] = { listChanged: @list_changed } if profile && profile[:prompts]&.any?

  capabilities[:logging] = {} if @logging_enabled
  capabilities[:completions] = {}

  # If profile includes resources, advertise resources capability
  if profile && profile[:resources]&.any?
    capabilities[:resources] = { subscribe: @resources_subscribe, listChanged: @list_changed }
  end


  # Tasks capability (MCP 2025-11-25)
  if @tasks_enabled
    tasks_cap = {
      requests: {
        tools: { call: {} }
      }
    }
    tasks_cap[:list] = {} if @tasks_list_enabled
    tasks_cap[:cancel] = {} if @tasks_cancel_enabled
    capabilities[:tasks] = tasks_cap
  end

  if @mcp_apps_enabled
    capabilities[:extensions] ||= {}
    capabilities[:extensions][Apps::EXTENSION_KEY] = Apps.extension_settings
  end

  capabilities
end

#eager_load_if_neededObject



342
343
344
345
346
347
348
349
350
351
352
353
354
# File 'lib/action_mcp/configuration.rb', line 342

def eager_load_if_needed
  profile = @profiles[active_profile]
  return unless profile

  # Check if any component type includes "all"
  needs_eager_load = profile[:tools]&.include?("all") ||
                     profile[:prompts]&.include?("all") ||
                     profile[:resources]&.include?("all")

  return unless needs_eager_load

  ensure_mcp_components_loaded
end

#filtered_promptsObject

Filter prompts based on active profile



264
265
266
267
268
269
# File 'lib/action_mcp/configuration.rb', line 264

def filtered_prompts
  return PromptsRegistry.non_abstract if should_include_all?(:prompts)

  prompt_names = @profiles[active_profile][:prompts] || []
  PromptsRegistry.non_abstract.select { |prompt| prompt_names.include?(prompt.name) }
end

#filtered_resourcesObject

Filter resources based on active profile



272
273
274
275
276
277
# File 'lib/action_mcp/configuration.rb', line 272

def filtered_resources
  return ResourceTemplatesRegistry.non_abstract if should_include_all?(:resources)

  resource_names = @profiles[active_profile][:resources] || []
  ResourceTemplatesRegistry.non_abstract.select { |resource| resource_names.include?(resource.name) }
end

#filtered_toolsObject

Filter tools based on active profile



254
255
256
257
258
259
260
261
# File 'lib/action_mcp/configuration.rb', line 254

def filtered_tools
  return ToolsRegistry.non_abstract if should_include_all?(:tools)

  tool_names = @profiles[active_profile][:tools] || []
  # Convert tool names to underscored format
  tool_names = tool_names.map { |name| name.to_s.underscore }
  ToolsRegistry.non_abstract.select { |tool| tool_names.include?(tool.name.underscore) }
end

#instructionsObject

Instructions for LLMs about the server's purpose (joined as string for MCP payload)



129
130
131
132
133
# File 'lib/action_mcp/configuration.rb', line 129

def instructions
  return nil if server_instructions.nil? || server_instructions.empty?

  server_instructions.join("\n")
end

#load_profilesObject

Load custom configuration from Rails configuration



186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
# File 'lib/action_mcp/configuration.rb', line 186

def load_profiles
  # First load defaults from the gem
  @profiles = default_profiles

  # Preserve any settings that were already set via Rails config
  preserved_name = @name

  # Try to load from config/mcp.yml in the Rails app using Rails.config_for
  begin
    app_config = Rails.application.config_for(:mcp)

    raise "Invalid MCP config file" unless app_config.is_a?(Hash)

    # Extract authentication configuration if present
    # Handle both symbol and string keys
    @authentication_methods = Array(app_config[:authentication] || app_config["authentication"]) if app_config[:authentication] || app_config["authentication"]

    # Extract other top-level configuration settings
    extract_top_level_settings(app_config)

    # Extract profiles configuration - merge with defaults instead of replacing
    # Rails.config_for returns OrderedOptions which uses symbol keys
    if app_config[:profiles] || app_config["profiles"]
      # Get profiles with either symbol or string key
      app_profiles = app_config[:profiles] || app_config["profiles"]

      # Convert to regular hash and deep symbolize keys
      if app_profiles.is_a?(ActiveSupport::OrderedOptions)
        app_profiles = app_profiles.to_h.deep_symbolize_keys
      elsif app_profiles.respond_to?(:deep_symbolize_keys)
        app_profiles = app_profiles.deep_symbolize_keys
      end

      Rails.logger.debug "[Configuration] Merging profiles: #{app_profiles.inspect}" if @verbose_logging
      @profiles = @profiles.deep_merge(app_profiles)
    end
  rescue StandardError => e
    # If the config file doesn't exist in the Rails app, just use the defaults
    Rails.logger.warn "[Configuration] Failed to load MCP config: #{e.class} - #{e.message}"
    # No MCP config found in Rails app, using defaults from gem
  end

  # Apply the active profile
  Rails.logger.info "[ActionMCP] Loaded profiles: #{@profiles.keys.join(', ')}" if @verbose_logging
  Rails.logger.info "[ActionMCP] Using profile: #{@active_profile}" if @verbose_logging
  use_profile(@active_profile)

  # Restore preserved settings
  @name = preserved_name if preserved_name

  self
end

#server_infoObject

Server information (name and version only)



121
122
123
124
125
126
# File 'lib/action_mcp/configuration.rb', line 121

def server_info
  {
    name: name,
    version: version
  }
end

#server_instructionsObject

Custom getter/setter to ensure array elements are strings



136
137
138
# File 'lib/action_mcp/configuration.rb', line 136

def server_instructions
  @server_instructions
end

#server_instructions=(value) ⇒ Object



140
141
142
# File 'lib/action_mcp/configuration.rb', line 140

def server_instructions=(value)
  @server_instructions = parse_instructions(value)
end

#use_profile(profile_name) ⇒ Object

Switch to a specific profile



240
241
242
243
244
245
246
247
248
249
250
251
# File 'lib/action_mcp/configuration.rb', line 240

def use_profile(profile_name)
  profile_name = profile_name.to_sym
  unless @profiles.key?(profile_name)
    Rails.logger.warn "Profile '#{profile_name}' not found, using primary"
    profile_name = :primary
  end

  @active_profile = profile_name
  apply_profile_options

  self
end