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
@@ -1,183 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module ActionMCP
|
4
|
-
module OAuth
|
5
|
-
# ActiveRecord storage for OAuth tokens and codes
|
6
|
-
# This is suitable for production multi-server environments
|
7
|
-
class ActiveRecordStorage
|
8
|
-
# Authorization code storage
|
9
|
-
def store_authorization_code(code, data)
|
10
|
-
OAuthToken.create!(
|
11
|
-
token: code,
|
12
|
-
token_type: OAuthToken::AUTHORIZATION_CODE,
|
13
|
-
client_id: data[:client_id],
|
14
|
-
user_id: data[:user_id],
|
15
|
-
redirect_uri: data[:redirect_uri],
|
16
|
-
scope: data[:scope],
|
17
|
-
code_challenge: data[:code_challenge],
|
18
|
-
code_challenge_method: data[:code_challenge_method],
|
19
|
-
expires_at: data[:expires_at],
|
20
|
-
metadata: data.except(:client_id, :user_id, :redirect_uri, :scope,
|
21
|
-
:code_challenge, :code_challenge_method, :expires_at)
|
22
|
-
)
|
23
|
-
end
|
24
|
-
|
25
|
-
def retrieve_authorization_code(code)
|
26
|
-
token = OAuthToken.authorization_codes.active.find_by(token: code)
|
27
|
-
return nil unless token
|
28
|
-
|
29
|
-
{
|
30
|
-
client_id: token.client_id,
|
31
|
-
user_id: token.user_id,
|
32
|
-
redirect_uri: token.redirect_uri,
|
33
|
-
scope: token.scope,
|
34
|
-
code_challenge: token.code_challenge,
|
35
|
-
code_challenge_method: token.code_challenge_method,
|
36
|
-
expires_at: token.expires_at,
|
37
|
-
created_at: token.created_at
|
38
|
-
}.merge(token.metadata || {})
|
39
|
-
end
|
40
|
-
|
41
|
-
def remove_authorization_code(code)
|
42
|
-
OAuthToken.authorization_codes.where(token: code).destroy_all
|
43
|
-
end
|
44
|
-
|
45
|
-
# Access token storage
|
46
|
-
def store_access_token(token, data)
|
47
|
-
OAuthToken.create!(
|
48
|
-
token: token,
|
49
|
-
token_type: OAuthToken::ACCESS_TOKEN,
|
50
|
-
client_id: data[:client_id],
|
51
|
-
user_id: data[:user_id],
|
52
|
-
scope: data[:scope],
|
53
|
-
expires_at: data[:expires_at],
|
54
|
-
metadata: data.except(:client_id, :user_id, :scope, :expires_at)
|
55
|
-
)
|
56
|
-
end
|
57
|
-
|
58
|
-
def retrieve_access_token(token)
|
59
|
-
token_record = OAuthToken.access_tokens.find_by(token: token)
|
60
|
-
return nil unless token_record
|
61
|
-
|
62
|
-
{
|
63
|
-
client_id: token_record.client_id,
|
64
|
-
user_id: token_record.user_id,
|
65
|
-
scope: token_record.scope,
|
66
|
-
expires_at: token_record.expires_at,
|
67
|
-
created_at: token_record.created_at,
|
68
|
-
active: token_record.still_valid?
|
69
|
-
}.merge(token_record.metadata || {})
|
70
|
-
end
|
71
|
-
|
72
|
-
def remove_access_token(token)
|
73
|
-
OAuthToken.access_tokens.where(token: token).destroy_all
|
74
|
-
end
|
75
|
-
|
76
|
-
# Refresh token storage
|
77
|
-
def store_refresh_token(token, data)
|
78
|
-
OAuthToken.create!(
|
79
|
-
token: token,
|
80
|
-
token_type: OAuthToken::REFRESH_TOKEN,
|
81
|
-
client_id: data[:client_id],
|
82
|
-
user_id: data[:user_id],
|
83
|
-
scope: data[:scope],
|
84
|
-
access_token: data[:access_token],
|
85
|
-
expires_at: data[:expires_at],
|
86
|
-
metadata: data.except(:client_id, :user_id, :scope, :access_token, :expires_at)
|
87
|
-
)
|
88
|
-
end
|
89
|
-
|
90
|
-
def retrieve_refresh_token(token)
|
91
|
-
token_record = OAuthToken.refresh_tokens.active.find_by(token: token)
|
92
|
-
return nil unless token_record
|
93
|
-
|
94
|
-
{
|
95
|
-
client_id: token_record.client_id,
|
96
|
-
user_id: token_record.user_id,
|
97
|
-
scope: token_record.scope,
|
98
|
-
access_token: token_record.access_token,
|
99
|
-
expires_at: token_record.expires_at,
|
100
|
-
created_at: token_record.created_at
|
101
|
-
}.merge(token_record.metadata || {})
|
102
|
-
end
|
103
|
-
|
104
|
-
def update_refresh_token(token, new_access_token)
|
105
|
-
token_record = OAuthToken.refresh_tokens.find_by(token: token)
|
106
|
-
token_record&.update!(access_token: new_access_token)
|
107
|
-
end
|
108
|
-
|
109
|
-
def remove_refresh_token(token)
|
110
|
-
OAuthToken.refresh_tokens.where(token: token).destroy_all
|
111
|
-
end
|
112
|
-
|
113
|
-
# Client registration storage
|
114
|
-
def store_client_registration(client_id, data)
|
115
|
-
client = OAuthClient.new
|
116
|
-
|
117
|
-
# Map data fields to model attributes
|
118
|
-
client.client_id = client_id
|
119
|
-
client.client_secret = data[:client_secret]
|
120
|
-
client.client_id_issued_at = data[:client_id_issued_at]
|
121
|
-
client.registration_access_token = data[:registration_access_token]
|
122
|
-
|
123
|
-
# Handle client metadata
|
124
|
-
metadata = data[:client_metadata] || {}
|
125
|
-
%w[
|
126
|
-
client_name redirect_uris grant_types response_types
|
127
|
-
token_endpoint_auth_method scope
|
128
|
-
].each do |field|
|
129
|
-
client.send("#{field}=", metadata[field]) if metadata.key?(field)
|
130
|
-
end
|
131
|
-
|
132
|
-
# Store any additional metadata
|
133
|
-
known_fields = %w[
|
134
|
-
client_name redirect_uris grant_types response_types
|
135
|
-
token_endpoint_auth_method scope
|
136
|
-
]
|
137
|
-
additional_metadata = metadata.except(*known_fields)
|
138
|
-
client.metadata = additional_metadata if additional_metadata.present?
|
139
|
-
|
140
|
-
client.save!
|
141
|
-
data
|
142
|
-
end
|
143
|
-
|
144
|
-
def retrieve_client_registration(client_id)
|
145
|
-
client = OAuthClient.active.find_by(client_id: client_id)
|
146
|
-
return nil unless client
|
147
|
-
|
148
|
-
{
|
149
|
-
client_id: client.client_id,
|
150
|
-
client_secret: client.client_secret,
|
151
|
-
client_id_issued_at: client.client_id_issued_at,
|
152
|
-
registration_access_token: client.registration_access_token,
|
153
|
-
client_metadata: client.to_api_response
|
154
|
-
}
|
155
|
-
end
|
156
|
-
|
157
|
-
def remove_client_registration(client_id)
|
158
|
-
OAuthClient.where(client_id: client_id).destroy_all
|
159
|
-
end
|
160
|
-
|
161
|
-
# Cleanup expired tokens
|
162
|
-
def cleanup_expired
|
163
|
-
OAuthToken.cleanup_expired
|
164
|
-
end
|
165
|
-
|
166
|
-
# Statistics (for debugging/monitoring)
|
167
|
-
def stats
|
168
|
-
{
|
169
|
-
authorization_codes: OAuthToken.authorization_codes.active.count,
|
170
|
-
access_tokens: OAuthToken.access_tokens.active.count,
|
171
|
-
refresh_tokens: OAuthToken.refresh_tokens.active.count,
|
172
|
-
client_registrations: OAuthClient.active.count
|
173
|
-
}
|
174
|
-
end
|
175
|
-
|
176
|
-
# Clear all data (for testing)
|
177
|
-
def clear_all
|
178
|
-
OAuthToken.delete_all
|
179
|
-
OAuthClient.delete_all
|
180
|
-
end
|
181
|
-
end
|
182
|
-
end
|
183
|
-
end
|
@@ -1,79 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module ActionMCP
|
4
|
-
module OAuth
|
5
|
-
# Base OAuth error class
|
6
|
-
class Error < StandardError
|
7
|
-
attr_reader :oauth_error_code
|
8
|
-
|
9
|
-
def initialize(message, oauth_error_code = "invalid_request")
|
10
|
-
super(message)
|
11
|
-
@oauth_error_code = oauth_error_code
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
# OAuth 2.1 standard error types
|
16
|
-
class InvalidRequestError < Error
|
17
|
-
def initialize(message = "Invalid request")
|
18
|
-
super(message, "invalid_request")
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
class InvalidClientError < Error
|
23
|
-
def initialize(message = "Invalid client")
|
24
|
-
super(message, "invalid_client")
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
class InvalidGrantError < Error
|
29
|
-
def initialize(message = "Invalid grant")
|
30
|
-
super(message, "invalid_grant")
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
class UnauthorizedClientError < Error
|
35
|
-
def initialize(message = "Unauthorized client")
|
36
|
-
super(message, "unauthorized_client")
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
class UnsupportedGrantTypeError < Error
|
41
|
-
def initialize(message = "Unsupported grant type")
|
42
|
-
super(message, "unsupported_grant_type")
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
class InvalidScopeError < Error
|
47
|
-
def initialize(message = "Invalid scope")
|
48
|
-
super(message, "invalid_scope")
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
class InvalidTokenError < Error
|
53
|
-
def initialize(message = "Invalid token")
|
54
|
-
super(message, "invalid_token")
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
class InsufficientScopeError < Error
|
59
|
-
attr_reader :required_scope
|
60
|
-
|
61
|
-
def initialize(message = "Insufficient scope", required_scope = nil)
|
62
|
-
super(message, "insufficient_scope")
|
63
|
-
@required_scope = required_scope
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
class ServerError < Error
|
68
|
-
def initialize(message = "Server error")
|
69
|
-
super(message, "server_error")
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
class TemporarilyUnavailableError < Error
|
74
|
-
def initialize(message = "Temporarily unavailable")
|
75
|
-
super(message, "temporarily_unavailable")
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|
@@ -1,132 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module ActionMCP
|
4
|
-
module OAuth
|
5
|
-
# In-memory storage for OAuth tokens and codes
|
6
|
-
# This is suitable for development and testing, but not for production
|
7
|
-
class MemoryStorage
|
8
|
-
def initialize
|
9
|
-
@authorization_codes = {}
|
10
|
-
@access_tokens = {}
|
11
|
-
@refresh_tokens = {}
|
12
|
-
@client_registrations = {}
|
13
|
-
@mutex = Mutex.new
|
14
|
-
end
|
15
|
-
|
16
|
-
# Authorization code storage
|
17
|
-
def store_authorization_code(code, data)
|
18
|
-
@mutex.synchronize do
|
19
|
-
@authorization_codes[code] = data
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
def retrieve_authorization_code(code)
|
24
|
-
@mutex.synchronize do
|
25
|
-
@authorization_codes[code]
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
def remove_authorization_code(code)
|
30
|
-
@mutex.synchronize do
|
31
|
-
@authorization_codes.delete(code)
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
# Access token storage
|
36
|
-
def store_access_token(token, data)
|
37
|
-
@mutex.synchronize do
|
38
|
-
@access_tokens[token] = data
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
def retrieve_access_token(token)
|
43
|
-
@mutex.synchronize do
|
44
|
-
@access_tokens[token]
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
def remove_access_token(token)
|
49
|
-
@mutex.synchronize do
|
50
|
-
@access_tokens.delete(token)
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
# Refresh token storage
|
55
|
-
def store_refresh_token(token, data)
|
56
|
-
@mutex.synchronize do
|
57
|
-
@refresh_tokens[token] = data
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
def retrieve_refresh_token(token)
|
62
|
-
@mutex.synchronize do
|
63
|
-
@refresh_tokens[token]
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
def update_refresh_token(token, new_access_token)
|
68
|
-
@mutex.synchronize do
|
69
|
-
@refresh_tokens[token][:access_token] = new_access_token if @refresh_tokens[token]
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
def remove_refresh_token(token)
|
74
|
-
@mutex.synchronize do
|
75
|
-
@refresh_tokens.delete(token)
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
# Client registration storage
|
80
|
-
def store_client_registration(client_id, data)
|
81
|
-
@mutex.synchronize do
|
82
|
-
@client_registrations[client_id] = data
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
def retrieve_client_registration(client_id)
|
87
|
-
@mutex.synchronize do
|
88
|
-
@client_registrations[client_id]
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
def remove_client_registration(client_id)
|
93
|
-
@mutex.synchronize do
|
94
|
-
@client_registrations.delete(client_id)
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
# Cleanup expired tokens (optional utility method)
|
99
|
-
def cleanup_expired
|
100
|
-
current_time = Time.current
|
101
|
-
|
102
|
-
@mutex.synchronize do
|
103
|
-
@authorization_codes.reject! { |_, data| data[:expires_at] < current_time }
|
104
|
-
@access_tokens.reject! { |_, data| data[:expires_at] < current_time }
|
105
|
-
@refresh_tokens.reject! { |_, data| data[:expires_at] < current_time }
|
106
|
-
end
|
107
|
-
end
|
108
|
-
|
109
|
-
# Statistics (for debugging/monitoring)
|
110
|
-
def stats
|
111
|
-
@mutex.synchronize do
|
112
|
-
{
|
113
|
-
authorization_codes: @authorization_codes.size,
|
114
|
-
access_tokens: @access_tokens.size,
|
115
|
-
refresh_tokens: @refresh_tokens.size,
|
116
|
-
client_registrations: @client_registrations.size
|
117
|
-
}
|
118
|
-
end
|
119
|
-
end
|
120
|
-
|
121
|
-
# Clear all data (for testing)
|
122
|
-
def clear_all
|
123
|
-
@mutex.synchronize do
|
124
|
-
@authorization_codes.clear
|
125
|
-
@access_tokens.clear
|
126
|
-
@refresh_tokens.clear
|
127
|
-
@client_registrations.clear
|
128
|
-
end
|
129
|
-
end
|
130
|
-
end
|
131
|
-
end
|
132
|
-
end
|
@@ -1,128 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative "error"
|
4
|
-
|
5
|
-
module ActionMCP
|
6
|
-
module OAuth
|
7
|
-
# OAuth middleware that integrates with Omniauth for request authentication
|
8
|
-
# Handles Bearer token validation for API requests
|
9
|
-
class Middleware
|
10
|
-
def initialize(app)
|
11
|
-
@app = app
|
12
|
-
end
|
13
|
-
|
14
|
-
def call(env)
|
15
|
-
request = ActionDispatch::Request.new(env)
|
16
|
-
|
17
|
-
# Skip OAuth processing for non-MCP requests or if OAuth not configured
|
18
|
-
return @app.call(env) unless should_process_oauth?(request)
|
19
|
-
|
20
|
-
# Skip OAuth processing for metadata endpoints
|
21
|
-
return @app.call(env) if request.path.start_with?("/.well-known/") || request.path.start_with?("/oauth/")
|
22
|
-
|
23
|
-
# Skip OAuth processing for initialization-related requests
|
24
|
-
return @app.call(env) if initialization_related_request?(request)
|
25
|
-
|
26
|
-
# Validate Bearer token for API requests
|
27
|
-
if (bearer_token = extract_bearer_token(request))
|
28
|
-
validate_oauth_token(request, bearer_token)
|
29
|
-
end
|
30
|
-
|
31
|
-
@app.call(env)
|
32
|
-
rescue ActionMCP::OAuth::Error => e
|
33
|
-
oauth_error_response(e)
|
34
|
-
end
|
35
|
-
|
36
|
-
private
|
37
|
-
|
38
|
-
def should_process_oauth?(_request)
|
39
|
-
# Check if OAuth is enabled in configuration
|
40
|
-
auth_methods = ActionMCP.configuration.authentication_methods
|
41
|
-
return false unless auth_methods&.include?("oauth")
|
42
|
-
|
43
|
-
# Process all MCP requests (ActionMCP serves at root "/") and OAuth-related paths
|
44
|
-
true
|
45
|
-
end
|
46
|
-
|
47
|
-
def initialization_related_request?(request)
|
48
|
-
# Only check JSON-RPC POST requests to MCP endpoints
|
49
|
-
# The path might include the mount path (e.g., /action_mcp/ or just /)
|
50
|
-
return false unless request.post? && request.content_type&.include?("application/json")
|
51
|
-
|
52
|
-
# Check if this is an MCP endpoint (ends with / or is the root)
|
53
|
-
path = request.path
|
54
|
-
return false unless path == "/" || path.match?(%r{/action_mcp/?$})
|
55
|
-
|
56
|
-
# Read and parse the request body
|
57
|
-
body = request.body.read
|
58
|
-
request.body.rewind # Reset for subsequent reads
|
59
|
-
|
60
|
-
json = JSON.parse(body)
|
61
|
-
method = json["method"]
|
62
|
-
|
63
|
-
# Check if it's an initialization-related method
|
64
|
-
%w[initialize notifications/initialized].include?(method)
|
65
|
-
rescue JSON::ParserError, StandardError
|
66
|
-
false
|
67
|
-
end
|
68
|
-
|
69
|
-
def extract_bearer_token(request)
|
70
|
-
auth_header = request.headers["Authorization"] || request.headers["authorization"]
|
71
|
-
return nil unless auth_header&.start_with?("Bearer ")
|
72
|
-
|
73
|
-
auth_header.split(" ", 2).last
|
74
|
-
end
|
75
|
-
|
76
|
-
def validate_oauth_token(request, token)
|
77
|
-
# Use the OAuth provider for token introspection
|
78
|
-
token_info = ActionMCP::OAuth::Provider.introspect_token(token)
|
79
|
-
|
80
|
-
unless token_info && token_info[:active]
|
81
|
-
raise ActionMCP::OAuth::InvalidTokenError, "Invalid or expired OAuth token"
|
82
|
-
end
|
83
|
-
|
84
|
-
# Store OAuth token info in request environment for Gateway
|
85
|
-
request.env["action_mcp.oauth_token_info"] = token_info
|
86
|
-
request.env["action_mcp.oauth_token"] = token
|
87
|
-
end
|
88
|
-
|
89
|
-
def oauth_error_response(error)
|
90
|
-
status = case error
|
91
|
-
when ActionMCP::OAuth::InvalidTokenError
|
92
|
-
401
|
93
|
-
when ActionMCP::OAuth::InsufficientScopeError
|
94
|
-
403
|
95
|
-
else
|
96
|
-
400
|
97
|
-
end
|
98
|
-
|
99
|
-
headers = {
|
100
|
-
"Content-Type" => "application/json",
|
101
|
-
"WWW-Authenticate" => www_authenticate_header(error)
|
102
|
-
}
|
103
|
-
|
104
|
-
body = {
|
105
|
-
error: error.oauth_error_code,
|
106
|
-
error_description: error.message
|
107
|
-
}.to_json
|
108
|
-
|
109
|
-
[ status, headers, [ body ] ]
|
110
|
-
end
|
111
|
-
|
112
|
-
def www_authenticate_header(error)
|
113
|
-
params = []
|
114
|
-
params << 'realm="MCP API"'
|
115
|
-
|
116
|
-
case error
|
117
|
-
when ActionMCP::OAuth::InvalidTokenError
|
118
|
-
params << 'error="invalid_token"'
|
119
|
-
when ActionMCP::OAuth::InsufficientScopeError
|
120
|
-
params << 'error="insufficient_scope"'
|
121
|
-
params << "scope=\"#{error.required_scope}\"" if error.required_scope
|
122
|
-
end
|
123
|
-
|
124
|
-
"Bearer #{params.join(', ')}"
|
125
|
-
end
|
126
|
-
end
|
127
|
-
end
|
128
|
-
end
|