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.



55
56
57
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
# File 'lib/action_mcp/configuration.rb', line 55

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

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

Instance Attribute Details

#active_profileObject

Get active profile (considering thread-local override)



164
165
166
# File 'lib/action_mcp/configuration.rb', line 164

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

#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)



298
299
300
# File 'lib/action_mcp/configuration.rb', line 298

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.



138
139
140
# File 'lib/action_mcp/configuration.rb', line 138

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)



303
304
305
# File 'lib/action_mcp/configuration.rb', line 303

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

#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



307
308
309
310
311
312
313
314
315
316
# File 'lib/action_mcp/configuration.rb', line 307

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



263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
# File 'lib/action_mcp/configuration.rb', line 263

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



318
319
320
321
322
323
324
325
326
327
328
329
330
# File 'lib/action_mcp/configuration.rb', line 318

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



247
248
249
250
251
252
# File 'lib/action_mcp/configuration.rb', line 247

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



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

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



237
238
239
240
241
242
243
244
# File 'lib/action_mcp/configuration.rb', line 237

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)



118
119
120
121
122
# File 'lib/action_mcp/configuration.rb', line 118

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

  server_instructions.join("\n")
end

#load_profilesObject

Load custom configuration from Rails configuration



169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
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
# File 'lib/action_mcp/configuration.rb', line 169

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)



110
111
112
113
114
115
# File 'lib/action_mcp/configuration.rb', line 110

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

#server_instructionsObject

Custom getter/setter to ensure array elements are strings



125
126
127
# File 'lib/action_mcp/configuration.rb', line 125

def server_instructions
  @server_instructions
end

#server_instructions=(value) ⇒ Object



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

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

#use_profile(profile_name) ⇒ Object

Switch to a specific profile



223
224
225
226
227
228
229
230
231
232
233
234
# File 'lib/action_mcp/configuration.rb', line 223

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