actionmcp 0.72.0 → 0.80.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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/app/controllers/action_mcp/application_controller.rb +20 -12
  4. data/app/models/action_mcp/session/message.rb +31 -20
  5. data/app/models/action_mcp/session/resource.rb +35 -20
  6. data/app/models/action_mcp/session/sse_event.rb +23 -17
  7. data/app/models/action_mcp/session/subscription.rb +22 -15
  8. data/app/models/action_mcp/session.rb +42 -119
  9. data/app/models/concerns/{mcp_console_helpers.rb → action_mcp/mcp_console_helpers.rb} +4 -3
  10. data/app/models/concerns/{mcp_message_inspect.rb → action_mcp/mcp_message_inspect.rb} +4 -3
  11. data/config/routes.rb +0 -13
  12. data/db/migrate/20250727000001_remove_oauth_support.rb +59 -0
  13. data/lib/action_mcp/client/streamable_http_transport.rb +1 -46
  14. data/lib/action_mcp/client.rb +2 -25
  15. data/lib/action_mcp/configuration.rb +51 -24
  16. data/lib/action_mcp/engine.rb +0 -7
  17. data/lib/action_mcp/filtered_logger.rb +2 -6
  18. data/lib/action_mcp/gateway_identifier.rb +187 -3
  19. data/lib/action_mcp/gateway_identifiers/api_key_identifier.rb +56 -0
  20. data/lib/action_mcp/gateway_identifiers/devise_identifier.rb +34 -0
  21. data/lib/action_mcp/gateway_identifiers/request_env_identifier.rb +58 -0
  22. data/lib/action_mcp/gateway_identifiers/warden_identifier.rb +38 -0
  23. data/lib/action_mcp/gateway_identifiers.rb +26 -0
  24. data/lib/action_mcp/resource_template.rb +1 -0
  25. data/lib/action_mcp/server/base_session.rb +2 -0
  26. data/lib/action_mcp/server/resources.rb +8 -7
  27. data/lib/action_mcp/version.rb +1 -1
  28. data/lib/action_mcp.rb +1 -6
  29. data/lib/generators/action_mcp/identifier/identifier_generator.rb +189 -0
  30. data/lib/generators/action_mcp/identifier/templates/identifier.rb.erb +35 -0
  31. data/lib/generators/action_mcp/install/install_generator.rb +1 -1
  32. data/lib/generators/action_mcp/install/templates/application_gateway.rb +80 -31
  33. data/lib/generators/action_mcp/install/templates/mcp.yml +4 -21
  34. metadata +15 -99
  35. data/app/controllers/action_mcp/oauth/endpoints_controller.rb +0 -265
  36. data/app/controllers/action_mcp/oauth/metadata_controller.rb +0 -125
  37. data/app/controllers/action_mcp/oauth/registration_controller.rb +0 -201
  38. data/app/models/action_mcp/oauth_client.rb +0 -159
  39. data/app/models/action_mcp/oauth_token.rb +0 -142
  40. data/db/migrate/20250608112101_add_oauth_to_sessions.rb +0 -28
  41. data/db/migrate/20250708105124_create_action_mcp_oauth_clients.rb +0 -44
  42. data/db/migrate/20250708105226_create_action_mcp_oauth_tokens.rb +0 -39
  43. data/lib/action_mcp/client/jwt_client_provider.rb +0 -135
  44. data/lib/action_mcp/client/oauth_client_provider/memory_storage.rb +0 -47
  45. data/lib/action_mcp/client/oauth_client_provider.rb +0 -234
  46. data/lib/action_mcp/jwt_decoder.rb +0 -28
  47. data/lib/action_mcp/jwt_identifier.rb +0 -28
  48. data/lib/action_mcp/none_identifier.rb +0 -19
  49. data/lib/action_mcp/o_auth_identifier.rb +0 -34
  50. data/lib/action_mcp/oauth/active_record_storage.rb +0 -183
  51. data/lib/action_mcp/oauth/error.rb +0 -79
  52. data/lib/action_mcp/oauth/memory_storage.rb +0 -132
  53. data/lib/action_mcp/oauth/middleware.rb +0 -128
  54. data/lib/action_mcp/oauth/provider.rb +0 -406
  55. data/lib/action_mcp/oauth.rb +0 -12
  56. data/lib/action_mcp/omniauth/mcp_strategy.rb +0 -162
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionMCP
4
+ module GatewayIdentifiers
5
+ # Example Gateway identifier for custom request environment-based authentication.
6
+ #
7
+ # This identifier looks for user information in custom request headers or environment
8
+ # variables. Useful for authentication set up by upstream proxies, API gateways,
9
+ # or custom middleware.
10
+ #
11
+ # @example Usage in ApplicationGateway
12
+ # class ApplicationGateway < ActionMCP::Gateway
13
+ # identified_by ActionMCP::GatewayIdentifiers::RequestEnvIdentifier
14
+ # end
15
+ #
16
+ # @example Configuration
17
+ # # config/mcp.yml
18
+ # authentication_methods: ["request_env"]
19
+ #
20
+ # @example Nginx/Proxy setup
21
+ # # Your proxy/gateway might set headers like:
22
+ # # X-User-ID: 123
23
+ # # X-User-Email: user@example.com
24
+ # # X-User-Roles: admin,user
25
+ class RequestEnvIdentifier < ActionMCP::GatewayIdentifier
26
+ identifier :user
27
+ authenticates :request_env
28
+
29
+ def resolve
30
+ user_id = @request.env["HTTP_X_USER_ID"]
31
+ raise Unauthorized, "User ID header missing" unless user_id
32
+
33
+ # You might also want to get additional user info from headers
34
+ email = @request.env["HTTP_X_USER_EMAIL"]
35
+ roles = @request.env["HTTP_X_USER_ROLES"]&.split(",") || []
36
+
37
+ # Option 1: Find user in database
38
+ begin
39
+ user = User.find(user_id)
40
+ # Optional: verify email matches if provided
41
+ if email && user.email != email
42
+ raise Unauthorized, "User email mismatch"
43
+ end
44
+ user
45
+ rescue ActiveRecord::RecordNotFound
46
+ raise Unauthorized, "Invalid user"
47
+ end
48
+
49
+ # Option 2: Create a simple user object from headers (if you don't want DB lookup)
50
+ # OpenStruct.new(
51
+ # id: user_id,
52
+ # email: email,
53
+ # roles: roles
54
+ # )
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionMCP
4
+ module GatewayIdentifiers
5
+ # Example Gateway identifier for Warden-based authentication.
6
+ #
7
+ # This identifier works with Warden middleware which is commonly used by Devise.
8
+ # Warden sets the authenticated user in request.env['warden.user'] after successful authentication.
9
+ #
10
+ # @example Usage in ApplicationGateway
11
+ # class ApplicationGateway < ActionMCP::Gateway
12
+ # identified_by ActionMCP::GatewayIdentifiers::WardenIdentifier
13
+ # end
14
+ #
15
+ # @example Configuration
16
+ # # config/mcp.yml
17
+ # authentication_methods: ["warden"]
18
+ #
19
+ # @example With Devise
20
+ # # In your controller or middleware, ensure Warden is configured:
21
+ # # devise_for :users
22
+ # # authenticate_user! # This sets up Warden env
23
+ class WardenIdentifier < ActionMCP::GatewayIdentifier
24
+ identifier :user
25
+ authenticates :warden
26
+
27
+ def resolve
28
+ warden = @request.env["warden"]
29
+ raise Unauthorized, "Warden not available" unless warden
30
+
31
+ user = warden.user
32
+ raise Unauthorized, "Not authenticated" unless user
33
+
34
+ user
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionMCP
4
+ # Example Gateway identifiers for common authentication patterns.
5
+ #
6
+ # These identifiers provide ready-to-use implementations for popular
7
+ # Rails authentication patterns. You can use them directly or as
8
+ # templates for your own custom identifiers.
9
+ #
10
+ # @example Using in ApplicationGateway
11
+ # class ApplicationGateway < ActionMCP::Gateway
12
+ # # Use multiple identifiers (tried in order)
13
+ # identified_by ActionMCP::GatewayIdentifiers::WardenIdentifier,
14
+ # ActionMCP::GatewayIdentifiers::ApiKeyIdentifier
15
+ # end
16
+ #
17
+ # @example Configuration
18
+ # # config/mcp.yml
19
+ # authentication_methods: ["warden", "api_key"]
20
+ module GatewayIdentifiers
21
+ autoload :WardenIdentifier, "action_mcp/gateway_identifiers/warden_identifier"
22
+ autoload :DeviseIdentifier, "action_mcp/gateway_identifiers/devise_identifier"
23
+ autoload :RequestEnvIdentifier, "action_mcp/gateway_identifiers/request_env_identifier"
24
+ autoload :ApiKeyIdentifier, "action_mcp/gateway_identifiers/api_key_identifier"
25
+ end
26
+ end
@@ -274,6 +274,7 @@ module ActionMCP
274
274
  else
275
275
  @response.add_content(result)
276
276
  end
277
+ @response
277
278
  end
278
279
  rescue StandardError => e
279
280
  @response.mark_as_resolution_failed!("template://#{self.class.name}", e.message)
@@ -147,7 +147,9 @@ module ActionMCP
147
147
 
148
148
  def cleanup_old_sse_events(max_age = 15.minutes)
149
149
  cutoff_time = Time.current - max_age
150
+ original_size = @sse_events.size
150
151
  @sse_events.delete_if { |e| e[:created_at] < cutoff_time }
152
+ original_size - @sse_events.size
151
153
  end
152
154
 
153
155
  def max_stored_sse_events
@@ -45,10 +45,11 @@ module ActionMCP
45
45
  # @example Output:
46
46
  # # Sends: {"jsonrpc":"2.0","id":"req-789","result":{"contents":[{"uri":"file:///example.txt","text":"Example content"}]}}
47
47
  def send_resource_read(id, params)
48
- template = ResourceTemplatesRegistry.find_template_for_uri(params[:uri])
48
+ uri = params["uri"]
49
+ template = ResourceTemplatesRegistry.find_template_for_uri(uri)
49
50
 
50
51
  unless template
51
- send_jsonrpc_error(id, :resource_not_found, "No resource template found for URI: #{params[:uri]}")
52
+ send_jsonrpc_error(id, :method_not_found, "No resource template found for URI: '#{uri}'")
52
53
  return
53
54
  end
54
55
 
@@ -65,21 +66,21 @@ module ActionMCP
65
66
 
66
67
  begin
67
68
  # Create template instance and set execution context
68
- record = template.process(params[:uri])
69
+ record = template.process(uri)
69
70
  record.with_context({ session: session })
70
71
 
71
72
  response = record.call
72
73
 
73
74
  if response.error?
74
- # Convert ResourceResponse errors to JSON-RPC errors
75
- error_info = response.to_h
76
- send_jsonrpc_error(id, error_info[:code], error_info[:message], error_info[:data])
75
+ # response.to_h works properly when error? is true:
76
+ send_jsonrpc_response(id, error: response.to_h)
77
77
  else
78
78
  # Handle successful response - ResourceResponse.contents is already an array
79
79
  send_jsonrpc_response(id, result: { contents: response.contents.map(&:to_h) })
80
80
  end
81
81
  rescue StandardError => e
82
- log_error(e, { resource_uri: params[:uri], template: template.name })
82
+ # Should rather `ErrorHandling` module be included here? Then we could use `log_error(e)` directly.
83
+ Rails.logger.error "[MCP Error] #{e.class}: #{e.message}"
83
84
  send_jsonrpc_error(id, :internal_error, "Failed to read resource: #{e.message}")
84
85
  end
85
86
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  require_relative "gem_version"
4
4
  module ActionMCP
5
- VERSION = "0.72.0"
5
+ VERSION = "0.80.1"
6
6
 
7
7
  class << self
8
8
  alias version gem_version
data/lib/action_mcp.rb CHANGED
@@ -13,9 +13,6 @@ require "action_mcp/log_subscriber"
13
13
  require "action_mcp/engine"
14
14
  require "zeitwerk"
15
15
 
16
- # OAuth 2.1 support via Omniauth
17
- require "omniauth"
18
- require "omniauth-oauth2"
19
16
 
20
17
  lib = File.dirname(__FILE__)
21
18
 
@@ -29,8 +26,6 @@ Zeitwerk::Loader.for_gem.tap do |loader|
29
26
 
30
27
  loader.inflector.inflect("action_mcp" => "ActionMCP")
31
28
  loader.inflector.inflect("sse_listener" => "SSEListener")
32
- loader.inflector.inflect("oauth" => "OAuth")
33
- loader.inflector.inflect("mcp_strategy" => "MCPStrategy")
34
29
  end.setup
35
30
 
36
31
  module ActionMCP
@@ -40,7 +35,7 @@ module ActionMCP
40
35
 
41
36
  # Protocol version constants
42
37
  SUPPORTED_VERSIONS = [
43
- "2025-06-18", # Dr. Identity McBouncer - OAuth 2.1, elicitation, structured output, resource links
38
+ "2025-06-18", # Dr. Identity McBouncer - elicitation, structured output, resource links
44
39
  "2025-03-26" # The Persistent Negotiator - StreamableHTTP, resumability, audio support
45
40
  ].freeze
46
41
 
@@ -0,0 +1,189 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionMCP
4
+ module Generators
5
+ class IdentifierGenerator < Rails::Generators::Base
6
+ namespace "action_mcp:identifier"
7
+ source_root File.expand_path("templates", __dir__)
8
+ desc "Creates a Gateway Identifier for authentication patterns"
9
+
10
+ argument :name, type: :string, required: true, banner: "IdentifierName"
11
+
12
+ class_option :auth_method, type: :string, required: true,
13
+ desc: "Authentication method name (e.g., 'api_key', 'session', 'custom')"
14
+ class_option :identity, type: :string, default: "user",
15
+ desc: "Identity type this identifier provides (e.g., 'user', 'admin')"
16
+ class_option :lookup_method, type: :string, default: "database",
17
+ desc: "How to resolve identity: 'database', 'middleware', 'headers', 'custom'"
18
+
19
+ def create_identifier_file
20
+ template "identifier.rb.erb", "app/mcp/identifiers/#{file_name}.rb"
21
+ end
22
+
23
+ def show_usage_instructions
24
+ say "\nIdentifier generated successfully!", :green
25
+ say "\nNext steps:", :blue
26
+ say "1. Configure authentication methods in config/mcp.yml:"
27
+ say " authentication_methods: [\"#{auth_method}\"]", :yellow
28
+ say "\n2. Register in ApplicationGateway:"
29
+ say " identified_by #{class_name}", :yellow
30
+ say "\n3. Customize the resolve method in app/mcp/identifiers/#{file_name}.rb"
31
+
32
+ if lookup_method == "database"
33
+ say "\n4. Ensure your #{identity.capitalize} model has the required fields/methods", :cyan
34
+ elsif lookup_method == "middleware"
35
+ say "\n4. Ensure your middleware sets the required request.env keys", :cyan
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def class_name
42
+ "#{name.camelize}#{name.camelize.end_with?('Identifier') ? '' : 'Identifier'}"
43
+ end
44
+
45
+ def file_name
46
+ base = name.underscore
47
+ base.end_with?("_identifier") ? base : "#{base}_identifier"
48
+ end
49
+
50
+ def auth_method
51
+ options[:auth_method]
52
+ end
53
+
54
+ def identity
55
+ options[:identity]
56
+ end
57
+
58
+ def lookup_method
59
+ options[:lookup_method]
60
+ end
61
+
62
+ def resolve_implementation
63
+ case lookup_method
64
+ when "database"
65
+ database_lookup_implementation
66
+ when "middleware"
67
+ middleware_lookup_implementation
68
+ when "headers"
69
+ headers_lookup_implementation
70
+ else
71
+ custom_lookup_implementation
72
+ end
73
+ end
74
+
75
+ def database_lookup_implementation
76
+ case auth_method
77
+ when /api_key|token/
78
+ api_key_database_lookup
79
+ when /session/
80
+ session_database_lookup
81
+ else
82
+ generic_database_lookup
83
+ end
84
+ end
85
+
86
+ def api_key_database_lookup
87
+ <<~RUBY.indent(4)
88
+ # Extract API key from various sources
89
+ api_key = extract_api_key
90
+ raise Unauthorized, "Missing API key" unless api_key
91
+
92
+ # Look up #{identity} by API key
93
+ #{identity} = #{identity.capitalize}.find_by(api_key: api_key)
94
+ raise Unauthorized, "Invalid API key" unless #{identity}
95
+
96
+ # Optional: Add additional validation
97
+ # raise Unauthorized, "#{identity.capitalize} account inactive" unless #{identity}.active?
98
+
99
+ #{identity}
100
+ RUBY
101
+ end
102
+
103
+ def session_database_lookup
104
+ <<~RUBY.indent(4)
105
+ # Get #{identity} ID from session
106
+ #{identity}_id = session&.[]('#{identity}_id')
107
+ raise Unauthorized, "No #{identity} session" unless #{identity}_id
108
+
109
+ # Look up #{identity} in database
110
+ #{identity} = #{identity.capitalize}.find_by(id: #{identity}_id)
111
+ raise Unauthorized, "Invalid session" unless #{identity}
112
+
113
+ #{identity}
114
+ RUBY
115
+ end
116
+
117
+ def generic_database_lookup
118
+ <<~RUBY.indent(4)
119
+ # TODO: Extract identifier from request (headers, params, etc.)
120
+ identifier = nil # Implement your extraction logic here
121
+ raise Unauthorized, "Missing authentication identifier" unless identifier
122
+
123
+ # Look up #{identity} in database
124
+ #{identity} = #{identity.capitalize}.find_by(some_field: identifier)
125
+ raise Unauthorized, "Authentication failed" unless #{identity}
126
+
127
+ #{identity}
128
+ RUBY
129
+ end
130
+
131
+ def middleware_lookup_implementation
132
+ <<~RUBY.indent(4)
133
+ # Get #{identity} from middleware (Warden, Devise, etc.)
134
+ #{identity} = user_from_middleware
135
+ raise Unauthorized, "No authenticated #{identity} found" unless #{identity}
136
+
137
+ # Optional: Add additional validation
138
+ # raise Unauthorized, "#{identity.capitalize} access denied" unless #{identity}.can_access_mcp?
139
+
140
+ #{identity}
141
+ RUBY
142
+ end
143
+
144
+ def headers_lookup_implementation
145
+ <<~RUBY.indent(4)
146
+ # Extract #{identity} info from request headers
147
+ #{identity}_id = @request.env['HTTP_X_#{identity.upcase}_ID']
148
+ raise Unauthorized, "#{identity.capitalize} ID header missing" unless #{identity}_id
149
+
150
+ # Optional: Get additional info from headers
151
+ email = @request.env['HTTP_X_#{identity.upcase}_EMAIL']
152
+ roles = @request.env['HTTP_X_#{identity.upcase}_ROLES']&.split(',') || []
153
+
154
+ # Option 1: Look up in database
155
+ #{identity} = #{identity.capitalize}.find(#{identity}_id)
156
+ #{' '}
157
+ # Option 2: Create simple object from headers (no DB lookup)
158
+ # #{identity} = OpenStruct.new(
159
+ # id: #{identity}_id,
160
+ # email: email,
161
+ # roles: roles
162
+ # )
163
+
164
+ #{identity}
165
+ rescue ActiveRecord::RecordNotFound
166
+ raise Unauthorized, "Invalid #{identity}"
167
+ RUBY
168
+ end
169
+
170
+ def custom_lookup_implementation
171
+ <<~RUBY.indent(4)
172
+ # TODO: Implement your custom authentication logic here
173
+
174
+ # Example patterns:
175
+ # 1. Extract credentials from request
176
+ # credentials = extract_credentials_from_request
177
+
178
+ # 2. Validate credentials (API call, database lookup, etc.)
179
+ # #{identity} = validate_credentials(credentials)
180
+
181
+ # 3. Return the authenticated #{identity} or raise Unauthorized
182
+ # raise Unauthorized, "Authentication failed" unless #{identity}
183
+
184
+ raise NotImplementedError, "Custom authentication logic not implemented"
185
+ RUBY
186
+ end
187
+ end
188
+ end
189
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ # <%= class_name %> - Gateway identifier for <%= auth_method %> authentication
4
+ #
5
+ # This identifier handles authentication using the "<%= auth_method %>" method
6
+ # and provides access to the authenticated <%= identity %> object.
7
+ #
8
+ # Configuration:
9
+ # # config/mcp.yml
10
+ # authentication_methods: ["<%= auth_method %>"]
11
+ #
12
+ # Usage in ApplicationGateway:
13
+ # identified_by <%= class_name %>
14
+ class <%= class_name %> < ActionMCP::GatewayIdentifier
15
+ identifier :<%= identity %>
16
+ authenticates :<%= auth_method %>
17
+
18
+ def resolve
19
+ <%= resolve_implementation %>
20
+ end
21
+
22
+ private
23
+
24
+ # Add any custom helper methods here
25
+ #
26
+ # Example helper methods:
27
+ #
28
+ # def extract_credentials_from_request
29
+ # # Custom extraction logic
30
+ # end
31
+ #
32
+ # def validate_credentials(credentials)
33
+ # # Custom validation logic
34
+ # end
35
+ end
@@ -44,7 +44,7 @@ module ActionMCP
44
44
  say ""
45
45
  say "Configuration:"
46
46
  say " The mcp.yml file contains authentication, profiles, and adapter settings."
47
- say " You can customize authentication methods, OAuth settings, and PubSub adapters."
47
+ say " You can customize authentication methods and PubSub adapters."
48
48
  say ""
49
49
  say "Available adapters:"
50
50
  say " - simple : In-memory adapter for development"
@@ -1,41 +1,90 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class ApplicationGateway < ActionMCP::Gateway
4
- # Specify what attributes identify a connection
5
- # Multiple identifiers can be used (e.g., user, account, organization)
6
- identified_by :user
7
-
8
- protected
3
+ # Application Gateway - configure your authentication identifiers here
4
+ #
5
+ # The Gateway reads from request.env keys set by upstream middleware
6
+ # (like Warden, Devise, or custom auth middleware) through identifier classes.
7
+ #
8
+ # ActionMCP provides ready-to-use identifier examples for common authentication patterns.
9
+ # You can use them directly or customize them for your needs.
9
10
 
10
- # Override this method to implement your authentication logic
11
- # Must return a hash with keys matching the identified_by attributes
12
- # or raise ActionMCP::UnauthorizedError
13
- def authenticate!
14
- # Example using JWT:
15
- token = extract_bearer_token
16
- raise ActionMCP::UnauthorizedError, "Missing token" unless token
11
+ class ApplicationGateway < ActionMCP::Gateway
12
+ # Register your identifier classes in order of preference
13
+ # The first successful identifier will be used
17
14
 
18
- payload = ActionMCP::JwtDecoder.decode(token)
19
- user = resolve_user(payload)
15
+ # Option 1: Use built-in identifiers (recommended for common patterns)
16
+ # identified_by ActionMCP::GatewayIdentifiers::WardenIdentifier # For Warden/Devise
17
+ # identified_by ActionMCP::GatewayIdentifiers::ApiKeyIdentifier # For API key auth
18
+ # identified_by ActionMCP::GatewayIdentifiers::RequestEnvIdentifier # For custom headers
20
19
 
21
- raise ActionMCP::UnauthorizedError, "Unauthorized" unless user
20
+ # Option 2: Use multiple auth methods (tries in order)
21
+ # identified_by ActionMCP::GatewayIdentifiers::WardenIdentifier,
22
+ # ActionMCP::GatewayIdentifiers::ApiKeyIdentifier
22
23
 
23
- # Return a hash with all identified_by attributes
24
- { user: user }
25
- rescue ActionMCP::JwtDecoder::DecodeError => e
26
- raise ActionMCP::UnauthorizedError, e.message
27
- end
24
+ # Option 3: Create custom identifiers (see examples below)
25
+ # identified_by CustomUserIdentifier, CustomAdminIdentifier
26
+ end
28
27
 
29
- private
28
+ # Custom identifier examples - uncomment and customize as needed:
30
29
 
31
- # Example method to resolve user from JWT payload
32
- def resolve_user(payload)
33
- return nil unless payload.is_a?(Hash)
30
+ # Example: Custom Warden/Devise identifier
31
+ # class CustomUserIdentifier < ActionMCP::GatewayIdentifier
32
+ # identifier :user
33
+ # authenticates :custom_warden
34
+ #
35
+ # def resolve
36
+ # user = user_from_middleware
37
+ # raise Unauthorized, "No authenticated user found" unless user
38
+ #
39
+ # # Add custom validation
40
+ # raise Unauthorized, "User account suspended" if user.suspended?
41
+ #
42
+ # user
43
+ # end
44
+ # end
34
45
 
35
- user_id = payload["user_id"] || payload["sub"]
36
- return nil unless user_id
46
+ # Example: Custom API Key identifier with rate limiting
47
+ # class CustomApiKeyIdentifier < ActionMCP::GatewayIdentifier
48
+ # identifier :user
49
+ # authenticates :custom_api_key
50
+ #
51
+ # def resolve
52
+ # api_key = extract_api_key
53
+ # raise Unauthorized, "Missing API key" unless api_key
54
+ #
55
+ # user = User.find_by(api_key: api_key)
56
+ # raise Unauthorized, "Invalid API key" unless user
57
+ #
58
+ # # Add rate limiting check
59
+ # if rate_limited?(user)
60
+ # raise Unauthorized, "Rate limit exceeded"
61
+ # end
62
+ #
63
+ # user
64
+ # end
65
+ #
66
+ # private
67
+ #
68
+ # def rate_limited?(user)
69
+ # # Implement your rate limiting logic
70
+ # false
71
+ # end
72
+ # end
37
73
 
38
- # Replace with your User model lookup
39
- User.find_by(id: user_id)
40
- end
41
- end
74
+ # Example: Admin-only identifier
75
+ # class AdminIdentifier < ActionMCP::GatewayIdentifier
76
+ # identifier :admin
77
+ # authenticates :admin_token
78
+ #
79
+ # def resolve
80
+ # token = extract_bearer_token
81
+ # raise Unauthorized, "Missing admin token" unless token
82
+ #
83
+ # admin = Admin.find_by(access_token: token)
84
+ # raise Unauthorized, "Invalid admin token" unless admin
85
+ #
86
+ # raise Unauthorized, "Admin access revoked" unless admin.active?
87
+ #
88
+ # admin
89
+ # end
90
+ # end
@@ -16,14 +16,6 @@ shared:
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
- # OAuth configuration (if using OAuth authentication)
20
- # oauth:
21
- # provider: "demo_oauth_provider"
22
- # scopes_supported: ["mcp:tools", "mcp:resources", "mcp:prompts"]
23
- # enable_dynamic_registration: true
24
- # enable_token_revocation: true
25
- # pkce_required: true
26
- # issuer_url: https://yourapp.com
27
19
 
28
20
  # MCP capability profiles
29
21
  profiles:
@@ -67,8 +59,8 @@ development:
67
59
 
68
60
  # Test environment
69
61
  test:
70
- # JWT authentication for testing environment
71
- authentication: ["jwt"]
62
+ # No authentication for testing environment
63
+ authentication: ["none"]
72
64
 
73
65
  # Test adapter for testing
74
66
  adapter: test
@@ -78,17 +70,8 @@ test:
78
70
 
79
71
  # Production environment
80
72
  production:
81
- # Multiple authentication methods - try OAuth first, fallback to JWT
82
- authentication: ["oauth", "jwt"]
83
-
84
- # OAuth configuration for production
85
- oauth:
86
- provider: "application_oauth_provider" # Your custom provider class
87
- scopes_supported: ["mcp:tools", "mcp:resources", "mcp:prompts"]
88
- enable_dynamic_registration: true
89
- enable_token_revocation: true
90
- pkce_required: true
91
- issuer_url: https://yourapp.com
73
+ # Configure authentication methods for production (customize as needed)
74
+ authentication: ["none"]
92
75
 
93
76
  # Additional production profiles for external clients
94
77
  profiles: