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.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/app/controllers/action_mcp/application_controller.rb +20 -12
- data/app/models/action_mcp/session/message.rb +31 -20
- data/app/models/action_mcp/session/resource.rb +35 -20
- data/app/models/action_mcp/session/sse_event.rb +23 -17
- data/app/models/action_mcp/session/subscription.rb +22 -15
- data/app/models/action_mcp/session.rb +42 -119
- data/app/models/concerns/{mcp_console_helpers.rb → action_mcp/mcp_console_helpers.rb} +4 -3
- data/app/models/concerns/{mcp_message_inspect.rb → action_mcp/mcp_message_inspect.rb} +4 -3
- data/config/routes.rb +0 -13
- data/db/migrate/20250727000001_remove_oauth_support.rb +59 -0
- data/lib/action_mcp/client/streamable_http_transport.rb +1 -46
- data/lib/action_mcp/client.rb +2 -25
- data/lib/action_mcp/configuration.rb +51 -24
- data/lib/action_mcp/engine.rb +0 -7
- data/lib/action_mcp/filtered_logger.rb +2 -6
- data/lib/action_mcp/gateway_identifier.rb +187 -3
- data/lib/action_mcp/gateway_identifiers/api_key_identifier.rb +56 -0
- data/lib/action_mcp/gateway_identifiers/devise_identifier.rb +34 -0
- data/lib/action_mcp/gateway_identifiers/request_env_identifier.rb +58 -0
- data/lib/action_mcp/gateway_identifiers/warden_identifier.rb +38 -0
- data/lib/action_mcp/gateway_identifiers.rb +26 -0
- data/lib/action_mcp/resource_template.rb +1 -0
- data/lib/action_mcp/server/base_session.rb +2 -0
- data/lib/action_mcp/server/resources.rb +8 -7
- data/lib/action_mcp/version.rb +1 -1
- data/lib/action_mcp.rb +1 -6
- data/lib/generators/action_mcp/identifier/identifier_generator.rb +189 -0
- data/lib/generators/action_mcp/identifier/templates/identifier.rb.erb +35 -0
- data/lib/generators/action_mcp/install/install_generator.rb +1 -1
- data/lib/generators/action_mcp/install/templates/application_gateway.rb +80 -31
- data/lib/generators/action_mcp/install/templates/mcp.yml +4 -21
- metadata +15 -99
- data/app/controllers/action_mcp/oauth/endpoints_controller.rb +0 -265
- data/app/controllers/action_mcp/oauth/metadata_controller.rb +0 -125
- data/app/controllers/action_mcp/oauth/registration_controller.rb +0 -201
- data/app/models/action_mcp/oauth_client.rb +0 -159
- data/app/models/action_mcp/oauth_token.rb +0 -142
- data/db/migrate/20250608112101_add_oauth_to_sessions.rb +0 -28
- data/db/migrate/20250708105124_create_action_mcp_oauth_clients.rb +0 -44
- data/db/migrate/20250708105226_create_action_mcp_oauth_tokens.rb +0 -39
- data/lib/action_mcp/client/jwt_client_provider.rb +0 -135
- data/lib/action_mcp/client/oauth_client_provider/memory_storage.rb +0 -47
- data/lib/action_mcp/client/oauth_client_provider.rb +0 -234
- data/lib/action_mcp/jwt_decoder.rb +0 -28
- data/lib/action_mcp/jwt_identifier.rb +0 -28
- data/lib/action_mcp/none_identifier.rb +0 -19
- data/lib/action_mcp/o_auth_identifier.rb +0 -34
- data/lib/action_mcp/oauth/active_record_storage.rb +0 -183
- data/lib/action_mcp/oauth/error.rb +0 -79
- data/lib/action_mcp/oauth/memory_storage.rb +0 -132
- data/lib/action_mcp/oauth/middleware.rb +0 -128
- data/lib/action_mcp/oauth/provider.rb +0 -406
- data/lib/action_mcp/oauth.rb +0 -12
- 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
|
@@ -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
|
-
|
48
|
+
uri = params["uri"]
|
49
|
+
template = ResourceTemplatesRegistry.find_template_for_uri(uri)
|
49
50
|
|
50
51
|
unless template
|
51
|
-
send_jsonrpc_error(id, :
|
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(
|
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
|
-
#
|
75
|
-
|
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
|
-
|
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
|
data/lib/action_mcp/version.rb
CHANGED
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 -
|
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
|
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
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
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
|
-
|
11
|
-
#
|
12
|
-
#
|
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
|
-
|
19
|
-
|
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
|
-
|
20
|
+
# Option 2: Use multiple auth methods (tries in order)
|
21
|
+
# identified_by ActionMCP::GatewayIdentifiers::WardenIdentifier,
|
22
|
+
# ActionMCP::GatewayIdentifiers::ApiKeyIdentifier
|
22
23
|
|
23
|
-
|
24
|
-
|
25
|
-
|
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
|
-
|
28
|
+
# Custom identifier examples - uncomment and customize as needed:
|
30
29
|
|
31
|
-
|
32
|
-
|
33
|
-
|
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
|
-
|
36
|
-
|
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
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
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
|
-
#
|
71
|
-
authentication: ["
|
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
|
-
#
|
82
|
-
authentication: ["
|
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:
|