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.



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 60

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-06-18"  # Default to stable version for backwards compatibility

  # Tasks defaults (MCP 2025-11-25)
  @tasks_enabled = false
  @tasks_list_enabled = true
  @tasks_cancel_enabled = true
  @tasks_result_strategy = :blocking_http
  @tasks_result_timeout = 30.seconds
  @tasks_result_poll_interval = 0.25

  # 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

  # Schema validation - disabled by default for backward compatibility
  @validate_structured_content = false

  # 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 = "/"

  # Allowed origins for DNS rebinding protection (nil = derive from request.host)
  @allowed_origins = nil
end

Instance Attribute Details

#active_profileObject

Get active profile (considering thread-local override)



192
193
194
# File 'lib/action_mcp/configuration.rb', line 192

def active_profile
  @active_profile
end

#allowed_identity_keysObject

Returns the value of attribute allowed_identity_keys.



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

def allowed_identity_keys
  @allowed_identity_keys
end

#allowed_originsObject

Returns the value of attribute allowed_origins.



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

def allowed_origins
  @allowed_origins
end

#authentication_methodsObject

Returns the value of attribute authentication_methods.



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

def authentication_methods
  @authentication_methods
end

#base_pathObject

Returns the value of attribute base_path.



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

def base_path
  @base_path
end

#client_session_store_typeObject

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



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

def client_session_store_type
  @client_session_store_type
end

#connects_toObject

Returns the value of attribute connects_to.



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

def connects_to
  @connects_to
end

#gateway_classObject

Returns the value of attribute gateway_class.



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

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.



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

attr_writer :name, :version

#logging_enabledBoolean

Returns Whether logging is enabled.

Returns:

  • (Boolean)

    Whether logging is enabled.



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

attr_writer :name, :version

#logging_levelSymbol

Returns The logging level.

Returns:

  • (Symbol)

    The logging level.



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

attr_writer :name, :version

#max_queueObject

Returns the value of attribute max_queue.



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

def max_queue
  @max_queue
end

#max_threadsObject

Returns the value of attribute max_threads.



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

def max_threads
  @max_threads
end

#min_threadsObject

Returns the value of attribute min_threads.



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

def min_threads
  @min_threads
end

#nameString

Returns The name of the MCP Server.

Returns:

  • (String)

    The name of the MCP Server.



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

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.



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

def profiles
  @profiles
end

#protocol_versionObject

Returns the value of attribute protocol_version.



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

def protocol_version
  @protocol_version
end

#resources_subscribeBoolean

Returns Whether to subscribe to resources.

Returns:

  • (Boolean)

    Whether to subscribe to resources.



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

attr_writer :name, :version

#server_session_store_typeObject

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



331
332
333
# File 'lib/action_mcp/configuration.rb', line 331

def server_session_store_type
  @server_session_store_type
end

#session_store_typeObject

Returns the value of attribute session_store_type.



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

def session_store_type
  @session_store_type
end

#tasks_cancel_enabledObject

Returns the value of attribute tasks_cancel_enabled.



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

def tasks_cancel_enabled
  @tasks_cancel_enabled
end

#tasks_enabledObject

Returns the value of attribute tasks_enabled.



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

def tasks_enabled
  @tasks_enabled
end

#tasks_list_enabledObject

Returns the value of attribute tasks_list_enabled.



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

def tasks_list_enabled
  @tasks_list_enabled
end

#tasks_result_poll_intervalObject

Returns the value of attribute tasks_result_poll_interval.



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

def tasks_result_poll_interval
  @tasks_result_poll_interval
end

#tasks_result_strategyObject

Returns the value of attribute tasks_result_strategy.



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

def tasks_result_strategy
  @tasks_result_strategy
end

#tasks_result_timeoutObject

Returns the value of attribute tasks_result_timeout.



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

def tasks_result_timeout
  @tasks_result_timeout
end

#validate_structured_contentObject

Returns the value of attribute validate_structured_content.



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

def validate_structured_content
  @validate_structured_content
end

#verbose_loggingObject

Returns the value of attribute verbose_logging.



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

def verbose_logging
  @verbose_logging
end

#versionString

Returns The version of the MCP Server.

Returns:

  • (String)

    The version of the MCP Server.



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

attr_writer :name, :version

Instance Method Details

#apply_profile_optionsObject



335
336
337
338
339
340
341
342
343
344
# File 'lib/action_mcp/configuration.rb', line 335

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?
end

#capabilitiesObject

Returns capabilities based on active profile



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
319
320
321
322
323
# File 'lib/action_mcp/configuration.rb', line 291

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

  # 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

  capabilities
end

#eager_load_if_neededObject



346
347
348
349
350
351
352
353
354
355
356
357
358
# File 'lib/action_mcp/configuration.rb', line 346

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



275
276
277
278
279
280
# File 'lib/action_mcp/configuration.rb', line 275

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



283
284
285
286
287
288
# File 'lib/action_mcp/configuration.rb', line 283

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



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

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



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
238
239
240
241
242
243
244
245
246
247
248
# File 'lib/action_mcp/configuration.rb', line 197

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



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

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