actionmcp 0.70.0 → 0.71.1

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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +46 -41
  3. data/app/controllers/action_mcp/application_controller.rb +67 -15
  4. data/app/controllers/action_mcp/oauth/metadata_controller.rb +13 -13
  5. data/app/controllers/action_mcp/oauth/registration_controller.rb +206 -0
  6. data/app/models/action_mcp/oauth_client.rb +157 -0
  7. data/app/models/action_mcp/oauth_token.rb +141 -0
  8. data/app/models/action_mcp/session/message.rb +12 -12
  9. data/app/models/action_mcp/session/resource.rb +2 -2
  10. data/app/models/action_mcp/session/sse_event.rb +2 -2
  11. data/app/models/action_mcp/session/subscription.rb +2 -2
  12. data/app/models/action_mcp/session.rb +22 -22
  13. data/config/routes.rb +1 -0
  14. data/db/migrate/20250708105124_create_action_mcp_oauth_clients.rb +42 -0
  15. data/db/migrate/20250708105226_create_action_mcp_oauth_tokens.rb +37 -0
  16. data/lib/action_mcp/client/base.rb +3 -2
  17. data/lib/action_mcp/client/collection.rb +3 -3
  18. data/lib/action_mcp/client/jwt_client_provider.rb +134 -0
  19. data/lib/action_mcp/client/streamable_http_transport.rb +56 -10
  20. data/lib/action_mcp/client.rb +16 -4
  21. data/lib/action_mcp/configuration.rb +27 -4
  22. data/lib/action_mcp/engine.rb +7 -1
  23. data/lib/action_mcp/filtered_logger.rb +32 -0
  24. data/lib/action_mcp/gateway.rb +47 -133
  25. data/lib/action_mcp/gateway_identifier.rb +29 -0
  26. data/lib/action_mcp/jwt_identifier.rb +28 -0
  27. data/lib/action_mcp/none_identifier.rb +19 -0
  28. data/lib/action_mcp/o_auth_identifier.rb +34 -0
  29. data/lib/action_mcp/oauth/active_record_storage.rb +183 -0
  30. data/lib/action_mcp/oauth/memory_storage.rb +23 -1
  31. data/lib/action_mcp/oauth/middleware.rb +33 -0
  32. data/lib/action_mcp/oauth/provider.rb +49 -13
  33. data/lib/action_mcp/oauth.rb +12 -0
  34. data/lib/action_mcp/server/capabilities.rb +0 -3
  35. data/lib/action_mcp/server/resources.rb +1 -1
  36. data/lib/action_mcp/server/tools.rb +36 -24
  37. data/lib/action_mcp/sse_listener.rb +0 -7
  38. data/lib/action_mcp/test_helper.rb +5 -0
  39. data/lib/action_mcp/tool.rb +94 -4
  40. data/lib/action_mcp/tools_registry.rb +3 -0
  41. data/lib/action_mcp/version.rb +1 -1
  42. data/lib/generators/action_mcp/install/templates/mcp.yml +16 -16
  43. metadata +14 -1
@@ -79,7 +79,7 @@ module ActionMCP
79
79
 
80
80
  # Generate refresh token if enabled
81
81
  refresh_token = nil
82
- if oauth_config["enable_refresh_tokens"]
82
+ if oauth_config[:enable_refresh_tokens]
83
83
  refresh_token = generate_refresh_token(
84
84
  client_id: client_id,
85
85
  scope: code_data[:scope],
@@ -206,13 +206,29 @@ module ActionMCP
206
206
  revoked
207
207
  end
208
208
 
209
+ # Register a new OAuth client (Dynamic Client Registration)
210
+ # @param client_info [Hash] Client registration information
211
+ # @return [Hash] Registered client information
212
+ def register_client(client_info)
213
+ # Store client registration
214
+ storage.store_client_registration(client_info[:client_id], client_info)
215
+ client_info
216
+ end
217
+
218
+ # Retrieve registered client information
219
+ # @param client_id [String] OAuth client identifier
220
+ # @return [Hash, nil] Client information or nil if not found
221
+ def get_client(client_id)
222
+ storage.retrieve_client_registration(client_id)
223
+ end
224
+
209
225
  # Client Credentials Grant (for server-to-server)
210
226
  # @param client_id [String] OAuth client identifier
211
227
  # @param client_secret [String] OAuth client secret
212
228
  # @param scope [String] Requested scope
213
229
  # @return [Hash] Token response
214
230
  def client_credentials_grant(client_id:, client_secret:, scope: nil)
215
- unless oauth_config["enable_client_credentials"]
231
+ unless oauth_config[:enable_client_credentials]
216
232
  raise UnsupportedGrantTypeError, "Client credentials grant not supported"
217
233
  end
218
234
 
@@ -244,19 +260,37 @@ module ActionMCP
244
260
  private
245
261
 
246
262
  def oauth_config
247
- @oauth_config ||= ActionMCP.configuration.oauth_config || {}
263
+ @oauth_config ||= HashWithIndifferentAccess.new(ActionMCP.configuration.oauth_config || {})
248
264
  end
249
265
 
250
266
  def validate_client(client_id, client_secret, require_secret: false)
251
- # This should be implemented by the application
252
- # For now, we'll use a simple validation approach
253
- provider_class = oauth_config["provider"]
267
+ # First check if client is registered via dynamic registration
268
+ client_info = get_client(client_id)
269
+ if client_info
270
+ # Validate client secret for confidential clients
271
+ if client_info[:client_secret]
272
+ unless client_secret == client_info[:client_secret]
273
+ raise InvalidClientError, "Invalid client credentials"
274
+ end
275
+ elsif require_secret
276
+ raise InvalidClientError, "Client authentication required"
277
+ end
278
+ return true
279
+ end
280
+
281
+ # Fall back to custom provider validation
282
+ provider_class = oauth_config[:provider]
254
283
  if provider_class && provider_class.respond_to?(:validate_client)
255
284
  provider_class.validate_client(client_id, client_secret)
256
285
  elsif require_secret && client_secret.nil?
257
286
  raise InvalidClientError, "Client authentication required"
287
+ else
288
+ # In development, allow unregistered clients if configured
289
+ if Rails.env.development? && oauth_config[:allow_unregistered_clients] != false
290
+ return true
291
+ end
292
+ raise InvalidClientError, "Unknown client"
258
293
  end
259
- # Default: allow any client for development
260
294
  end
261
295
 
262
296
  def validate_pkce(code_challenge, method, code_verifier)
@@ -271,7 +305,7 @@ module ActionMCP
271
305
  raise InvalidGrantError, "Invalid code verifier"
272
306
  end
273
307
  when "plain"
274
- unless oauth_config["allow_plain_pkce"]
308
+ unless oauth_config[:allow_plain_pkce]
275
309
  raise InvalidGrantError, "Plain PKCE not allowed"
276
310
  end
277
311
  unless code_challenge == code_verifier
@@ -283,7 +317,7 @@ module ActionMCP
283
317
  end
284
318
 
285
319
  def validate_scope(scope)
286
- supported_scopes = oauth_config["scopes_supported"] || [ "mcp:tools", "mcp:resources", "mcp:prompts" ]
320
+ supported_scopes = oauth_config.fetch(:scopes_supported, [ "mcp:tools", "mcp:resources", "mcp:prompts" ])
287
321
  requested_scopes = scope.split(" ")
288
322
  unsupported = requested_scopes - supported_scopes
289
323
  if unsupported.any?
@@ -292,7 +326,7 @@ module ActionMCP
292
326
  end
293
327
 
294
328
  def default_scope
295
- oauth_config["default_scope"] || "mcp:tools mcp:resources mcp:prompts"
329
+ oauth_config.fetch(:default_scope, "mcp:tools mcp:resources mcp:prompts")
296
330
  end
297
331
 
298
332
  def generate_access_token(client_id:, scope:, user_id:)
@@ -325,17 +359,19 @@ module ActionMCP
325
359
  end
326
360
 
327
361
  def token_expires_in
328
- oauth_config["access_token_expires_in"] || 3600 # 1 hour
362
+ oauth_config.fetch(:access_token_expires_in, 3600) # 1 hour
329
363
  end
330
364
 
331
365
  def refresh_token_expires_in
332
- oauth_config["refresh_token_expires_in"] || 7.days.to_i # 1 week
366
+ oauth_config.fetch(:refresh_token_expires_in, 7.days.to_i) # 1 week
333
367
  end
334
368
 
335
369
  # Storage methods - these delegate to a configurable storage backend
336
370
  def storage
337
371
  @storage ||= begin
338
- storage_class = oauth_config["storage"] || "ActionMCP::OAuth::MemoryStorage"
372
+ # Default to ActiveRecord storage for production, memory for test
373
+ default_storage = Rails.env.test? ? "ActionMCP::OAuth::MemoryStorage" : "ActionMCP::OAuth::ActiveRecordStorage"
374
+ storage_class = oauth_config.fetch(:storage, default_storage)
339
375
  storage_class = storage_class.constantize if storage_class.is_a?(String)
340
376
  storage_class.new
341
377
  end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionMCP
4
+ module OAuth
5
+ # Load OAuth components
6
+ autoload :Error, "action_mcp/oauth/error"
7
+ autoload :Provider, "action_mcp/oauth/provider"
8
+ autoload :Middleware, "action_mcp/oauth/middleware"
9
+ autoload :MemoryStorage, "action_mcp/oauth/memory_storage"
10
+ autoload :ActiveRecordStorage, "action_mcp/oauth/active_record_storage"
11
+ end
12
+ end
@@ -40,14 +40,11 @@ module ActionMCP
40
40
  if existing_session && existing_session.initialized?
41
41
  # Resume existing session - update transport reference
42
42
  transport.instance_variable_set(:@session, existing_session)
43
- Rails.logger.info("Resuming existing session: #{session_id}")
44
43
 
45
44
  # Return existing session info
46
45
  capabilities_payload = existing_session.server_capabilities_payload
47
46
  capabilities_payload[:protocolVersion] = client_protocol_version
48
47
  return send_jsonrpc_response(request_id, result: capabilities_payload)
49
- else
50
- Rails.logger.warn("Session #{session_id} not found or not initialized, creating new session")
51
48
  end
52
49
  end
53
50
 
@@ -75,7 +75,7 @@ module ActionMCP
75
75
  # @example Output:
76
76
  # # Logs: "Registered Resource Templates: ["db://{table}", "file://{path}"]"
77
77
  def log_resource_templates
78
- Rails.logger.info("Registered Resource Templates: #{ActionMCP::ResourceTemplatesRegistry.resource_templates.keys}")
78
+ # Resource templates: #{ActionMCP::ResourceTemplatesRegistry.resource_templates.keys}
79
79
  end
80
80
  end
81
81
  end
@@ -41,35 +41,47 @@ module ActionMCP
41
41
  tool_class = session.registered_tools.find { |t| t.tool_name == tool_name }
42
42
 
43
43
  if tool_class
44
- # Create tool and set execution context with request info
45
- tool = tool_class.new(arguments)
46
- tool.with_context({
47
- session: session,
48
- request: {
49
- params: {
50
- name: tool_name,
51
- arguments: arguments,
52
- _meta: _meta
44
+ begin
45
+ # Create tool and set execution context with request info
46
+ tool = tool_class.new(arguments)
47
+ tool.with_context({
48
+ session: session,
49
+ request: {
50
+ params: {
51
+ name: tool_name,
52
+ arguments: arguments,
53
+ _meta: _meta
54
+ }
53
55
  }
54
- }
55
- })
56
+ })
56
57
 
57
- # Wrap tool execution with Rails reloader for development
58
- result = if Rails.env.development? && defined?(Rails.application.reloader)
59
- Rails.application.reloader.wrap do
58
+ # Wrap tool execution with Rails reloader for development
59
+ result = if Rails.env.development?
60
+ # Preserve Current attributes across reloader boundary
61
+ current_user = ActionMCP::Current.user
62
+ current_gateway = ActionMCP::Current.gateway
63
+
64
+ Rails.application.reloader.wrap do
65
+ # Restore Current attributes inside reloader
66
+ ActionMCP::Current.user = current_user
67
+ ActionMCP::Current.gateway = current_gateway
68
+ tool.call
69
+ end
70
+ else
60
71
  tool.call
61
72
  end
62
- else
63
- tool.call
64
- end
65
73
 
66
- if result.is_error
67
- # Convert ToolResponse error to proper JSON-RPC error format
68
- # Pass the error hash directly - the Response class will handle it
69
- error_hash = result.to_h
70
- send_jsonrpc_response(request_id, error: error_hash)
71
- else
72
- send_jsonrpc_response(request_id, result: result)
74
+ if result.is_error
75
+ # Convert ToolResponse error to proper JSON-RPC error format
76
+ # Pass the error hash directly - the Response class will handle it
77
+ error_hash = result.to_h
78
+ send_jsonrpc_response(request_id, error: error_hash)
79
+ else
80
+ send_jsonrpc_response(request_id, result: result)
81
+ end
82
+ rescue ArgumentError => e
83
+ # Handle parameter validation errors
84
+ send_jsonrpc_error(request_id, :invalid_params, e.message)
73
85
  end
74
86
  else
75
87
  send_jsonrpc_error(request_id, :method_not_found, "Tool '#{tool_name}' not available in this session")
@@ -19,10 +19,7 @@ module ActionMCP
19
19
  # @yield [Hash] Yields parsed message received from the pub/sub channel
20
20
  # @return [Boolean] True if subscription was successful within timeout, false otherwise.
21
21
  def start(&callback)
22
- Rails.logger.debug "SSEListener: Starting for channel: #{session_key}"
23
-
24
22
  success_callback = lambda {
25
- Rails.logger.info "SSEListener: Successfully subscribed to channel: #{session_key}"
26
23
  @subscription_active.make_true
27
24
  }
28
25
 
@@ -41,7 +38,6 @@ module ActionMCP
41
38
  return if @stopped.true?
42
39
 
43
40
  @stopped.make_true
44
- Rails.logger.debug "SSEListener: Stopping listener for channel: #{session_key}"
45
41
  end
46
42
 
47
43
  private
@@ -50,8 +46,6 @@ module ActionMCP
50
46
  return if @stopped.true?
51
47
 
52
48
  begin
53
- Rails.logger.debug "SSEListener: Received raw message of type: #{raw_message.class}"
54
-
55
49
  # Check if the message is a valid JSON string or has a message attribute
56
50
  if raw_message.is_a?(String) && valid_json_format?(raw_message)
57
51
  message = MultiJson.load(raw_message)
@@ -80,7 +74,6 @@ module ActionMCP
80
74
  begin
81
75
  subscription_future.value(5) || @subscription_active.true?
82
76
  rescue Concurrent::TimeoutError
83
- Rails.logger.warn "SSEListener: Timed out waiting for subscription for #{session_key}"
84
77
  false
85
78
  end
86
79
  end
@@ -49,6 +49,11 @@ module ActionMCP
49
49
  end
50
50
  alias execute_tool execute_mcp_tool
51
51
 
52
+ def execute_mcp_tool_with_error(name, args = {})
53
+ ActionMCP::ToolsRegistry.tool_call(name, args)
54
+ end
55
+ alias execute_tool_with_error execute_mcp_tool_with_error
56
+
52
57
  def execute_mcp_prompt(name, args = {})
53
58
  resp = ActionMCP::PromptsRegistry.prompt_call(name, args)
54
59
  assert !resp.is_error, "Prompt #{name.inspect} returned error: #{resp.to_h[:message]}"
@@ -176,7 +176,7 @@ module ActionMCP
176
176
 
177
177
  return unless %w[number integer].include?(type)
178
178
 
179
- validates prop_name, numericality: true, allow_nil: true
179
+ validates prop_name, numericality: true, allow_nil: !required
180
180
  end
181
181
 
182
182
  # --------------------------------------------------------------------------
@@ -257,6 +257,13 @@ module ActionMCP
257
257
  # Instance Methods
258
258
  # --------------------------------------------------------------------------
259
259
 
260
+ # Override initialize to validate parameters before ActiveModel conversion
261
+ def initialize(attributes = {})
262
+ # Validate parameters before ActiveModel processes them
263
+ validate_parameter_types(attributes)
264
+ super
265
+ end
266
+
260
267
  # Public entry point for executing the tool
261
268
  # Returns an array of Content objects collected from render calls
262
269
  def call
@@ -273,14 +280,16 @@ module ActionMCP
273
280
  @response.mark_as_error!(:internal_error, message: e.message)
274
281
  end
275
282
  else
276
- @response.mark_as_error!(:invalid_request,
283
+ @response.mark_as_error!(:invalid_params,
277
284
  message: "Invalid input",
278
285
  data: errors.full_messages)
279
286
  end
280
287
 
281
288
  # If callbacks halted execution (`performed` still false) and
282
- # nothing else marked an error, surface it as invalid_request.
283
- @response.mark_as_error!(:invalid_request, message: "Aborted by callback") if !performed && !@response.error?
289
+ # nothing else marked an error, surface it as invalid_params.
290
+ if !performed && !@response.error?
291
+ @response.mark_as_error!(:invalid_params, message: "Tool execution was aborted")
292
+ end
284
293
 
285
294
  @response
286
295
  end
@@ -362,5 +371,86 @@ module ActionMCP
362
371
  end
363
372
 
364
373
  private_class_method :map_json_type_to_active_model_type
374
+
375
+ private
376
+
377
+ # Validates parameter types before ActiveModel conversion
378
+ def validate_parameter_types(attributes)
379
+ return unless attributes.is_a?(Hash)
380
+
381
+ attributes.each do |key, value|
382
+ key_str = key.to_s
383
+ property_schema = self.class._schema_properties[key_str]
384
+
385
+ next unless property_schema
386
+
387
+ expected_type = property_schema[:type]
388
+
389
+ # Skip validation if value is nil and property is not required
390
+ next if value.nil? && !self.class._required_properties.include?(key_str)
391
+
392
+ # Validate based on expected JSON Schema type
393
+ case expected_type
394
+ when "number"
395
+ validate_number_parameter(key_str, value)
396
+ when "integer"
397
+ validate_integer_parameter(key_str, value)
398
+ when "string"
399
+ validate_string_parameter(key_str, value)
400
+ when "boolean"
401
+ validate_boolean_parameter(key_str, value)
402
+ when "array"
403
+ validate_array_parameter(key_str, value, property_schema)
404
+ end
405
+ end
406
+ end
407
+
408
+ def validate_number_parameter(key, value)
409
+ return if value.is_a?(Numeric)
410
+
411
+ if value.is_a?(String)
412
+ # Check if string can be converted to a valid number
413
+ begin
414
+ Float(value)
415
+ rescue ArgumentError, TypeError
416
+ raise ArgumentError, "Parameter '#{key}' must be a valid number, got: #{value.inspect}"
417
+ end
418
+ else
419
+ raise ArgumentError, "Parameter '#{key}' must be a number, got: #{value.class}"
420
+ end
421
+ end
422
+
423
+ def validate_integer_parameter(key, value)
424
+ return if value.is_a?(Integer)
425
+
426
+ if value.is_a?(String)
427
+ # Check if string can be converted to a valid integer
428
+ begin
429
+ Integer(value)
430
+ rescue ArgumentError, TypeError
431
+ raise ArgumentError, "Parameter '#{key}' must be a valid integer, got: #{value.inspect}"
432
+ end
433
+ else
434
+ raise ArgumentError, "Parameter '#{key}' must be an integer, got: #{value.class}"
435
+ end
436
+ end
437
+
438
+ def validate_string_parameter(key, value)
439
+ return if value.is_a?(String)
440
+
441
+ raise ArgumentError, "Parameter '#{key}' must be a string, got: #{value.class}"
442
+ end
443
+
444
+ def validate_boolean_parameter(key, value)
445
+ return if value.is_a?(TrueClass) || value.is_a?(FalseClass)
446
+
447
+ raise ArgumentError, "Parameter '#{key}' must be a boolean, got: #{value.class}"
448
+ end
449
+
450
+ def validate_array_parameter(key, value, property_schema)
451
+ return if value.is_a?(Array)
452
+
453
+ raise ArgumentError, "Parameter '#{key}' must be an array, got: #{value.class}"
454
+ end
365
455
  end
366
456
  end
@@ -22,6 +22,9 @@ module ActionMCP
22
22
  tool.call
23
23
  rescue RegistryBase::NotFound
24
24
  error_response(:invalid_params, message: "Tool not found: #{tool_name}")
25
+ rescue ArgumentError => e
26
+ # Handle parameter validation errors
27
+ error_response(:invalid_params, message: e.message)
25
28
  rescue StandardError => e
26
29
  # FIXME, we should maybe not return the error message to the user
27
30
  error_response(:invalid_params, message: "Tool execution failed: #{e.message}")
@@ -2,7 +2,7 @@
2
2
 
3
3
  require_relative "gem_version"
4
4
  module ActionMCP
5
- VERSION = "0.70.0"
5
+ VERSION = "0.71.1"
6
6
 
7
7
  class << self
8
8
  alias version gem_version
@@ -5,17 +5,17 @@
5
5
  shared:
6
6
  # Authentication configuration - array of methods to try in order
7
7
  authentication: ["none"] # No authentication required by default
8
-
8
+
9
9
  # Session store configuration
10
10
  # Global session store type used by both client and server (default: volatile in dev/test, active_record in production)
11
11
  # session_store_type: volatile
12
-
12
+
13
13
  # Client-specific session store type (falls back to session_store_type if not specified)
14
14
  # client_session_store_type: volatile
15
-
15
+
16
16
  # Server-specific session store type (falls back to session_store_type if not specified)
17
17
  # server_session_store_type: active_record
18
-
18
+
19
19
  # OAuth configuration (if using OAuth authentication)
20
20
  # oauth:
21
21
  # provider: "demo_oauth_provider"
@@ -24,7 +24,7 @@ shared:
24
24
  # enable_token_revocation: true
25
25
  # pkce_required: true
26
26
  # issuer_url: https://yourapp.com
27
-
27
+
28
28
  # MCP capability profiles
29
29
  profiles:
30
30
  primary:
@@ -38,7 +38,7 @@ shared:
38
38
  list_changed: false
39
39
  logging_enabled: true
40
40
  resources_subscribe: false
41
-
41
+
42
42
  minimal:
43
43
  tools: []
44
44
  prompts: []
@@ -53,13 +53,13 @@ shared:
53
53
  development:
54
54
  # Use simple pub/sub adapter for development
55
55
  adapter: simple
56
-
56
+
57
57
  # Session store examples for development
58
58
  # Use volatile client sessions for faster development
59
59
  # client_session_store_type: volatile
60
60
  # Use persistent server sessions to maintain state across restarts
61
61
  # server_session_store_type: active_record
62
-
62
+
63
63
  # Thread pool configuration (optional)
64
64
  # min_threads: 5 # Minimum number of threads in the pool
65
65
  # max_threads: 10 # Maximum number of threads in the pool
@@ -69,10 +69,10 @@ development:
69
69
  test:
70
70
  # JWT authentication for testing environment
71
71
  authentication: ["jwt"]
72
-
72
+
73
73
  # Test adapter for testing
74
74
  adapter: test
75
-
75
+
76
76
  # Use volatile sessions for testing (fast cleanup)
77
77
  # session_store_type: volatile
78
78
 
@@ -80,7 +80,7 @@ test:
80
80
  production:
81
81
  # Multiple authentication methods - try OAuth first, fallback to JWT
82
82
  authentication: ["oauth", "jwt"]
83
-
83
+
84
84
  # OAuth configuration for production
85
85
  oauth:
86
86
  provider: "application_oauth_provider" # Your custom provider class
@@ -89,29 +89,29 @@ production:
89
89
  enable_token_revocation: true
90
90
  pkce_required: true
91
91
  issuer_url: https://yourapp.com
92
-
92
+
93
93
  # Additional production profiles for external clients
94
94
  profiles:
95
95
  external_clients:
96
96
  tools: ["WeatherForecastTool"] # Limited tool access for external clients
97
97
  prompts: []
98
98
  resources: []
99
-
99
+
100
100
  # Production session store configuration
101
101
  # Use persistent storage for production reliability
102
102
  # session_store_type: active_record
103
103
  # Or configure separately:
104
104
  # client_session_store_type: active_record # Client connections persist across restarts
105
105
  # server_session_store_type: active_record # Server state persists across deployments
106
-
106
+
107
107
  # Choose one of the following adapters:
108
-
108
+
109
109
  # 1. Database-backed adapter (recommended)
110
110
  adapter: solid_mcp
111
111
  polling_interval: 0.5.seconds
112
112
  batch_size: 200 # Number of messages to write in a single batch
113
113
  flush_interval: 0.05 # Seconds between batch flushes
114
-
114
+
115
115
  # Thread pool configuration (optional)
116
116
  min_threads: 10 # Minimum number of threads in the pool
117
117
  max_threads: 20 # Maximum number of threads in the pool
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: actionmcp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.70.0
4
+ version: 0.71.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Abdelkader Boudih
@@ -206,8 +206,11 @@ files:
206
206
  - app/controllers/action_mcp/application_controller.rb
207
207
  - app/controllers/action_mcp/oauth/endpoints_controller.rb
208
208
  - app/controllers/action_mcp/oauth/metadata_controller.rb
209
+ - app/controllers/action_mcp/oauth/registration_controller.rb
209
210
  - app/models/action_mcp.rb
210
211
  - app/models/action_mcp/application_record.rb
212
+ - app/models/action_mcp/oauth_client.rb
213
+ - app/models/action_mcp/oauth_token.rb
211
214
  - app/models/action_mcp/session.rb
212
215
  - app/models/action_mcp/session/message.rb
213
216
  - app/models/action_mcp/session/resource.rb
@@ -218,6 +221,8 @@ files:
218
221
  - config/routes.rb
219
222
  - db/migrate/20250512154359_consolidated_migration.rb
220
223
  - db/migrate/20250608112101_add_oauth_to_sessions.rb
224
+ - db/migrate/20250708105124_create_action_mcp_oauth_clients.rb
225
+ - db/migrate/20250708105226_create_action_mcp_oauth_tokens.rb
221
226
  - exe/actionmcp_cli
222
227
  - lib/action_mcp.rb
223
228
  - lib/action_mcp/base_response.rb
@@ -231,6 +236,7 @@ files:
231
236
  - lib/action_mcp/client/collection.rb
232
237
  - lib/action_mcp/client/elicitation.rb
233
238
  - lib/action_mcp/client/json_rpc_handler.rb
239
+ - lib/action_mcp/client/jwt_client_provider.rb
234
240
  - lib/action_mcp/client/logging.rb
235
241
  - lib/action_mcp/client/messaging.rb
236
242
  - lib/action_mcp/client/oauth_client_provider.rb
@@ -262,7 +268,9 @@ files:
262
268
  - lib/action_mcp/current.rb
263
269
  - lib/action_mcp/current_helpers.rb
264
270
  - lib/action_mcp/engine.rb
271
+ - lib/action_mcp/filtered_logger.rb
265
272
  - lib/action_mcp/gateway.rb
273
+ - lib/action_mcp/gateway_identifier.rb
266
274
  - lib/action_mcp/gem_version.rb
267
275
  - lib/action_mcp/instrumentation/controller_runtime.rb
268
276
  - lib/action_mcp/instrumentation/instrumentation.rb
@@ -270,8 +278,13 @@ files:
270
278
  - lib/action_mcp/integer_array.rb
271
279
  - lib/action_mcp/json_rpc_handler_base.rb
272
280
  - lib/action_mcp/jwt_decoder.rb
281
+ - lib/action_mcp/jwt_identifier.rb
273
282
  - lib/action_mcp/log_subscriber.rb
274
283
  - lib/action_mcp/logging.rb
284
+ - lib/action_mcp/none_identifier.rb
285
+ - lib/action_mcp/o_auth_identifier.rb
286
+ - lib/action_mcp/oauth.rb
287
+ - lib/action_mcp/oauth/active_record_storage.rb
275
288
  - lib/action_mcp/oauth/error.rb
276
289
  - lib/action_mcp/oauth/memory_storage.rb
277
290
  - lib/action_mcp/oauth/middleware.rb