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
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.
|
4
|
+
version: 0.80.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Abdelkader Boudih
|
@@ -51,20 +51,6 @@ dependencies:
|
|
51
51
|
- - ">="
|
52
52
|
- !ruby/object:Gem::Version
|
53
53
|
version: 0.5.3
|
54
|
-
- !ruby/object:Gem::Dependency
|
55
|
-
name: jwt
|
56
|
-
requirement: !ruby/object:Gem::Requirement
|
57
|
-
requirements:
|
58
|
-
- - "~>"
|
59
|
-
- !ruby/object:Gem::Version
|
60
|
-
version: '2.10'
|
61
|
-
type: :runtime
|
62
|
-
prerelease: false
|
63
|
-
version_requirements: !ruby/object:Gem::Requirement
|
64
|
-
requirements:
|
65
|
-
- - "~>"
|
66
|
-
- !ruby/object:Gem::Version
|
67
|
-
version: '2.10'
|
68
54
|
- !ruby/object:Gem::Dependency
|
69
55
|
name: multi_json
|
70
56
|
requirement: !ruby/object:Gem::Requirement
|
@@ -107,34 +93,6 @@ dependencies:
|
|
107
93
|
- - "~>"
|
108
94
|
- !ruby/object:Gem::Version
|
109
95
|
version: '2.6'
|
110
|
-
- !ruby/object:Gem::Dependency
|
111
|
-
name: omniauth
|
112
|
-
requirement: !ruby/object:Gem::Requirement
|
113
|
-
requirements:
|
114
|
-
- - "~>"
|
115
|
-
- !ruby/object:Gem::Version
|
116
|
-
version: '2.1'
|
117
|
-
type: :runtime
|
118
|
-
prerelease: false
|
119
|
-
version_requirements: !ruby/object:Gem::Requirement
|
120
|
-
requirements:
|
121
|
-
- - "~>"
|
122
|
-
- !ruby/object:Gem::Version
|
123
|
-
version: '2.1'
|
124
|
-
- !ruby/object:Gem::Dependency
|
125
|
-
name: omniauth-oauth2
|
126
|
-
requirement: !ruby/object:Gem::Requirement
|
127
|
-
requirements:
|
128
|
-
- - "~>"
|
129
|
-
- !ruby/object:Gem::Version
|
130
|
-
version: '1.7'
|
131
|
-
type: :runtime
|
132
|
-
prerelease: false
|
133
|
-
version_requirements: !ruby/object:Gem::Requirement
|
134
|
-
requirements:
|
135
|
-
- - "~>"
|
136
|
-
- !ruby/object:Gem::Version
|
137
|
-
version: '1.7'
|
138
96
|
- !ruby/object:Gem::Dependency
|
139
97
|
name: ostruct
|
140
98
|
requirement: !ruby/object:Gem::Requirement
|
@@ -149,34 +107,6 @@ dependencies:
|
|
149
107
|
- - ">="
|
150
108
|
- !ruby/object:Gem::Version
|
151
109
|
version: '0'
|
152
|
-
- !ruby/object:Gem::Dependency
|
153
|
-
name: faraday
|
154
|
-
requirement: !ruby/object:Gem::Requirement
|
155
|
-
requirements:
|
156
|
-
- - "~>"
|
157
|
-
- !ruby/object:Gem::Version
|
158
|
-
version: '2.7'
|
159
|
-
type: :runtime
|
160
|
-
prerelease: false
|
161
|
-
version_requirements: !ruby/object:Gem::Requirement
|
162
|
-
requirements:
|
163
|
-
- - "~>"
|
164
|
-
- !ruby/object:Gem::Version
|
165
|
-
version: '2.7'
|
166
|
-
- !ruby/object:Gem::Dependency
|
167
|
-
name: pkce_challenge
|
168
|
-
requirement: !ruby/object:Gem::Requirement
|
169
|
-
requirements:
|
170
|
-
- - "~>"
|
171
|
-
- !ruby/object:Gem::Version
|
172
|
-
version: '1.0'
|
173
|
-
type: :runtime
|
174
|
-
prerelease: false
|
175
|
-
version_requirements: !ruby/object:Gem::Requirement
|
176
|
-
requirements:
|
177
|
-
- - "~>"
|
178
|
-
- !ruby/object:Gem::Version
|
179
|
-
version: '1.0'
|
180
110
|
- !ruby/object:Gem::Dependency
|
181
111
|
name: json_schemer
|
182
112
|
requirement: !ruby/object:Gem::Requirement
|
@@ -191,8 +121,9 @@ dependencies:
|
|
191
121
|
- - "~>"
|
192
122
|
- !ruby/object:Gem::Version
|
193
123
|
version: '2.0'
|
194
|
-
description:
|
195
|
-
|
124
|
+
description: A streamlined, production-focused toolkit for building MCP servers in
|
125
|
+
Rails applications. Provides essential base classes, authentication gateways, and
|
126
|
+
HTTP transport with minimal dependencies.
|
196
127
|
email:
|
197
128
|
- terminale@gmail.com
|
198
129
|
executables:
|
@@ -204,26 +135,19 @@ files:
|
|
204
135
|
- README.md
|
205
136
|
- Rakefile
|
206
137
|
- app/controllers/action_mcp/application_controller.rb
|
207
|
-
- app/controllers/action_mcp/oauth/endpoints_controller.rb
|
208
|
-
- app/controllers/action_mcp/oauth/metadata_controller.rb
|
209
|
-
- app/controllers/action_mcp/oauth/registration_controller.rb
|
210
138
|
- app/models/action_mcp.rb
|
211
139
|
- app/models/action_mcp/application_record.rb
|
212
|
-
- app/models/action_mcp/oauth_client.rb
|
213
|
-
- app/models/action_mcp/oauth_token.rb
|
214
140
|
- app/models/action_mcp/session.rb
|
215
141
|
- app/models/action_mcp/session/message.rb
|
216
142
|
- app/models/action_mcp/session/resource.rb
|
217
143
|
- app/models/action_mcp/session/sse_event.rb
|
218
144
|
- app/models/action_mcp/session/subscription.rb
|
219
|
-
- app/models/concerns/mcp_console_helpers.rb
|
220
|
-
- app/models/concerns/mcp_message_inspect.rb
|
145
|
+
- app/models/concerns/action_mcp/mcp_console_helpers.rb
|
146
|
+
- app/models/concerns/action_mcp/mcp_message_inspect.rb
|
221
147
|
- config/routes.rb
|
222
148
|
- db/migrate/20250512154359_consolidated_migration.rb
|
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
|
226
149
|
- db/migrate/20250715070713_add_consents_to_action_mcp_sess.rb
|
150
|
+
- db/migrate/20250727000001_remove_oauth_support.rb
|
227
151
|
- exe/actionmcp_cli
|
228
152
|
- lib/action_mcp.rb
|
229
153
|
- lib/action_mcp/base_response.rb
|
@@ -237,11 +161,8 @@ files:
|
|
237
161
|
- lib/action_mcp/client/collection.rb
|
238
162
|
- lib/action_mcp/client/elicitation.rb
|
239
163
|
- lib/action_mcp/client/json_rpc_handler.rb
|
240
|
-
- lib/action_mcp/client/jwt_client_provider.rb
|
241
164
|
- lib/action_mcp/client/logging.rb
|
242
165
|
- lib/action_mcp/client/messaging.rb
|
243
|
-
- lib/action_mcp/client/oauth_client_provider.rb
|
244
|
-
- lib/action_mcp/client/oauth_client_provider/memory_storage.rb
|
245
166
|
- lib/action_mcp/client/prompt_book.rb
|
246
167
|
- lib/action_mcp/client/prompts.rb
|
247
168
|
- lib/action_mcp/client/request_timeouts.rb
|
@@ -272,25 +193,19 @@ files:
|
|
272
193
|
- lib/action_mcp/filtered_logger.rb
|
273
194
|
- lib/action_mcp/gateway.rb
|
274
195
|
- lib/action_mcp/gateway_identifier.rb
|
196
|
+
- lib/action_mcp/gateway_identifiers.rb
|
197
|
+
- lib/action_mcp/gateway_identifiers/api_key_identifier.rb
|
198
|
+
- lib/action_mcp/gateway_identifiers/devise_identifier.rb
|
199
|
+
- lib/action_mcp/gateway_identifiers/request_env_identifier.rb
|
200
|
+
- lib/action_mcp/gateway_identifiers/warden_identifier.rb
|
275
201
|
- lib/action_mcp/gem_version.rb
|
276
202
|
- lib/action_mcp/instrumentation/controller_runtime.rb
|
277
203
|
- lib/action_mcp/instrumentation/instrumentation.rb
|
278
204
|
- lib/action_mcp/instrumentation/resource_instrumentation.rb
|
279
205
|
- lib/action_mcp/integer_array.rb
|
280
206
|
- lib/action_mcp/json_rpc_handler_base.rb
|
281
|
-
- lib/action_mcp/jwt_decoder.rb
|
282
|
-
- lib/action_mcp/jwt_identifier.rb
|
283
207
|
- lib/action_mcp/log_subscriber.rb
|
284
208
|
- lib/action_mcp/logging.rb
|
285
|
-
- lib/action_mcp/none_identifier.rb
|
286
|
-
- lib/action_mcp/o_auth_identifier.rb
|
287
|
-
- lib/action_mcp/oauth.rb
|
288
|
-
- lib/action_mcp/oauth/active_record_storage.rb
|
289
|
-
- lib/action_mcp/oauth/error.rb
|
290
|
-
- lib/action_mcp/oauth/memory_storage.rb
|
291
|
-
- lib/action_mcp/oauth/middleware.rb
|
292
|
-
- lib/action_mcp/oauth/provider.rb
|
293
|
-
- lib/action_mcp/omniauth/mcp_strategy.rb
|
294
209
|
- lib/action_mcp/prompt.rb
|
295
210
|
- lib/action_mcp/prompt_response.rb
|
296
211
|
- lib/action_mcp/prompts_registry.rb
|
@@ -346,6 +261,8 @@ files:
|
|
346
261
|
- lib/action_mcp/uri_ambiguity_checker.rb
|
347
262
|
- lib/action_mcp/version.rb
|
348
263
|
- lib/actionmcp.rb
|
264
|
+
- lib/generators/action_mcp/identifier/identifier_generator.rb
|
265
|
+
- lib/generators/action_mcp/identifier/templates/identifier.rb.erb
|
349
266
|
- lib/generators/action_mcp/install/install_generator.rb
|
350
267
|
- lib/generators/action_mcp/install/templates/application_gateway.rb
|
351
268
|
- lib/generators/action_mcp/install/templates/application_mcp_prompt.rb
|
@@ -383,6 +300,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
383
300
|
requirements: []
|
384
301
|
rubygems_version: 3.6.9
|
385
302
|
specification_version: 4
|
386
|
-
summary:
|
387
|
-
servers
|
303
|
+
summary: Lightweight Model Context Protocol (MCP) server toolkit for Ruby/Rails
|
388
304
|
test_files: []
|
@@ -1,265 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "ostruct"
|
4
|
-
|
5
|
-
module ActionMCP
|
6
|
-
module OAuth
|
7
|
-
# OAuth 2.1 endpoints controller
|
8
|
-
# Handles authorization, token, introspection, and revocation endpoints
|
9
|
-
class EndpointsController < ActionController::Base
|
10
|
-
protect_from_forgery with: :null_session
|
11
|
-
before_action :check_oauth_enabled
|
12
|
-
|
13
|
-
# GET /oauth/authorize
|
14
|
-
# Authorization endpoint for OAuth 2.1 authorization code flow
|
15
|
-
def authorize
|
16
|
-
# Extract parameters
|
17
|
-
client_id = params[:client_id]
|
18
|
-
redirect_uri = params[:redirect_uri]
|
19
|
-
response_type = params[:response_type]
|
20
|
-
scope = params[:scope]
|
21
|
-
state = params[:state]
|
22
|
-
code_challenge = params[:code_challenge]
|
23
|
-
code_challenge_method = params[:code_challenge_method]
|
24
|
-
|
25
|
-
# Validate required parameters
|
26
|
-
if client_id.blank? || redirect_uri.blank? || response_type.blank?
|
27
|
-
return render_error("invalid_request", "Missing required parameters")
|
28
|
-
end
|
29
|
-
|
30
|
-
# Validate response type
|
31
|
-
unless response_type == "code"
|
32
|
-
return render_error("unsupported_response_type", "Only authorization code flow supported")
|
33
|
-
end
|
34
|
-
|
35
|
-
# Validate PKCE if required
|
36
|
-
if oauth_config["pkce_required"] && code_challenge.blank?
|
37
|
-
return render_error("invalid_request", "PKCE required")
|
38
|
-
end
|
39
|
-
|
40
|
-
# In a real implementation, this would show a consent page
|
41
|
-
# For now, we'll auto-approve for configured clients
|
42
|
-
if auto_approve_client?(client_id)
|
43
|
-
# Generate authorization code
|
44
|
-
user_id = current_user&.id || "anonymous"
|
45
|
-
|
46
|
-
begin
|
47
|
-
code = ActionMCP::OAuth::Provider.generate_authorization_code(
|
48
|
-
client_id: client_id,
|
49
|
-
redirect_uri: redirect_uri,
|
50
|
-
scope: scope || default_scope,
|
51
|
-
code_challenge: code_challenge,
|
52
|
-
code_challenge_method: code_challenge_method,
|
53
|
-
user_id: user_id
|
54
|
-
)
|
55
|
-
|
56
|
-
# Redirect back to client with authorization code
|
57
|
-
redirect_params = { code: code }
|
58
|
-
redirect_params[:state] = state if state
|
59
|
-
redirect_to "#{redirect_uri}?#{redirect_params.to_query}", allow_other_host: true
|
60
|
-
rescue ActionMCP::OAuth::Error => e
|
61
|
-
render_error(e.oauth_error_code, e.message)
|
62
|
-
end
|
63
|
-
else
|
64
|
-
# In production, show consent page
|
65
|
-
render_consent_page(client_id, redirect_uri, scope, state, code_challenge, code_challenge_method)
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
# POST /oauth/token
|
70
|
-
# Token endpoint for exchanging authorization codes and refreshing tokens
|
71
|
-
def token
|
72
|
-
grant_type = params[:grant_type]
|
73
|
-
|
74
|
-
case grant_type
|
75
|
-
when "authorization_code"
|
76
|
-
handle_authorization_code_grant
|
77
|
-
when "refresh_token"
|
78
|
-
handle_refresh_token_grant
|
79
|
-
when "client_credentials"
|
80
|
-
handle_client_credentials_grant
|
81
|
-
else
|
82
|
-
render_token_error("unsupported_grant_type", "Unsupported grant type")
|
83
|
-
end
|
84
|
-
rescue ActionMCP::OAuth::Error => e
|
85
|
-
render_token_error(e.oauth_error_code, e.message)
|
86
|
-
end
|
87
|
-
|
88
|
-
# POST /oauth/introspect
|
89
|
-
# Token introspection endpoint (RFC 7662)
|
90
|
-
def introspect
|
91
|
-
token = params[:token]
|
92
|
-
return render_introspection_error unless token
|
93
|
-
|
94
|
-
# Authenticate client for introspection
|
95
|
-
client_id, = extract_client_credentials
|
96
|
-
return render_introspection_error unless client_id
|
97
|
-
|
98
|
-
begin
|
99
|
-
token_info = ActionMCP::OAuth::Provider.introspect_token(token)
|
100
|
-
render json: token_info
|
101
|
-
rescue ActionMCP::OAuth::Error
|
102
|
-
render json: { active: false }
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
# POST /oauth/revoke
|
107
|
-
# Token revocation endpoint (RFC 7009)
|
108
|
-
def revoke
|
109
|
-
token = params[:token]
|
110
|
-
token_type_hint = params[:token_type_hint]
|
111
|
-
|
112
|
-
return head :bad_request unless token
|
113
|
-
|
114
|
-
# Authenticate client
|
115
|
-
client_id, = extract_client_credentials
|
116
|
-
return head :unauthorized unless client_id
|
117
|
-
|
118
|
-
begin
|
119
|
-
ActionMCP::OAuth::Provider.revoke_token(token, token_type_hint: token_type_hint)
|
120
|
-
head :ok
|
121
|
-
rescue ActionMCP::OAuth::Error
|
122
|
-
head :bad_request
|
123
|
-
end
|
124
|
-
end
|
125
|
-
|
126
|
-
private
|
127
|
-
|
128
|
-
def check_oauth_enabled
|
129
|
-
auth_methods = ActionMCP.configuration.authentication_methods
|
130
|
-
return if auth_methods&.include?("oauth")
|
131
|
-
|
132
|
-
head :not_found
|
133
|
-
end
|
134
|
-
|
135
|
-
def oauth_config
|
136
|
-
@oauth_config ||= ActionMCP.configuration.oauth_config || {}
|
137
|
-
end
|
138
|
-
|
139
|
-
def handle_authorization_code_grant
|
140
|
-
code = params[:code]
|
141
|
-
client_id = params[:client_id]
|
142
|
-
client_secret = params[:client_secret]
|
143
|
-
redirect_uri = params[:redirect_uri]
|
144
|
-
code_verifier = params[:code_verifier]
|
145
|
-
|
146
|
-
# Extract client credentials from Authorization header if not in params
|
147
|
-
client_id, client_secret = extract_client_credentials if client_id.blank?
|
148
|
-
|
149
|
-
return render_token_error("invalid_request", "Missing required parameters") if code.blank? || client_id.blank?
|
150
|
-
|
151
|
-
token_response = ActionMCP::OAuth::Provider.exchange_code_for_token(
|
152
|
-
code: code,
|
153
|
-
client_id: client_id,
|
154
|
-
client_secret: client_secret,
|
155
|
-
redirect_uri: redirect_uri,
|
156
|
-
code_verifier: code_verifier
|
157
|
-
)
|
158
|
-
|
159
|
-
render json: token_response
|
160
|
-
end
|
161
|
-
|
162
|
-
def handle_refresh_token_grant
|
163
|
-
refresh_token = params[:refresh_token]
|
164
|
-
scope = params[:scope]
|
165
|
-
|
166
|
-
# Extract client credentials
|
167
|
-
client_id, client_secret = extract_client_credentials
|
168
|
-
client_id ||= params[:client_id]
|
169
|
-
client_secret ||= params[:client_secret]
|
170
|
-
|
171
|
-
if refresh_token.blank? || client_id.blank?
|
172
|
-
return render_token_error("invalid_request",
|
173
|
-
"Missing required parameters")
|
174
|
-
end
|
175
|
-
|
176
|
-
token_response = ActionMCP::OAuth::Provider.refresh_access_token(
|
177
|
-
refresh_token: refresh_token,
|
178
|
-
client_id: client_id,
|
179
|
-
client_secret: client_secret,
|
180
|
-
scope: scope
|
181
|
-
)
|
182
|
-
|
183
|
-
render json: token_response
|
184
|
-
end
|
185
|
-
|
186
|
-
def handle_client_credentials_grant
|
187
|
-
scope = params[:scope]
|
188
|
-
|
189
|
-
# Extract client credentials
|
190
|
-
client_id, client_secret = extract_client_credentials
|
191
|
-
client_id ||= params[:client_id]
|
192
|
-
client_secret ||= params[:client_secret]
|
193
|
-
|
194
|
-
return render_token_error("invalid_request", "Missing client credentials") if client_id.blank?
|
195
|
-
|
196
|
-
token_response = ActionMCP::OAuth::Provider.client_credentials_grant(
|
197
|
-
client_id: client_id,
|
198
|
-
client_secret: client_secret,
|
199
|
-
scope: scope
|
200
|
-
)
|
201
|
-
|
202
|
-
render json: token_response
|
203
|
-
end
|
204
|
-
|
205
|
-
def extract_client_credentials
|
206
|
-
auth_header = request.headers["Authorization"]
|
207
|
-
if auth_header&.start_with?("Basic ")
|
208
|
-
encoded = auth_header.split(" ", 2).last
|
209
|
-
decoded = Base64.decode64(encoded)
|
210
|
-
decoded.split(":", 2)
|
211
|
-
else
|
212
|
-
[ nil, nil ]
|
213
|
-
end
|
214
|
-
end
|
215
|
-
|
216
|
-
def auto_approve_client?(client_id)
|
217
|
-
# In development/testing, auto-approve known clients
|
218
|
-
# In production, this should check a proper client registry
|
219
|
-
Rails.env.development? || Rails.env.test? || oauth_config["auto_approve_clients"]&.include?(client_id)
|
220
|
-
end
|
221
|
-
|
222
|
-
def current_user
|
223
|
-
# This should be implemented by the application
|
224
|
-
# For now, return a default user for development
|
225
|
-
if Rails.env.development? || Rails.env.test?
|
226
|
-
OpenStruct.new(id: "dev_user", email: "dev@example.com")
|
227
|
-
else
|
228
|
-
# In production, this should integrate with your authentication system
|
229
|
-
nil
|
230
|
-
end
|
231
|
-
end
|
232
|
-
|
233
|
-
def default_scope
|
234
|
-
oauth_config["default_scope"] || "mcp:tools mcp:resources mcp:prompts"
|
235
|
-
end
|
236
|
-
|
237
|
-
def render_error(error_code, description)
|
238
|
-
render json: {
|
239
|
-
error: error_code,
|
240
|
-
error_description: description
|
241
|
-
}, status: :bad_request
|
242
|
-
end
|
243
|
-
|
244
|
-
def render_token_error(error_code, description)
|
245
|
-
render json: {
|
246
|
-
error: error_code,
|
247
|
-
error_description: description
|
248
|
-
}, status: :bad_request
|
249
|
-
end
|
250
|
-
|
251
|
-
def render_introspection_error
|
252
|
-
render json: { active: false }, status: :bad_request
|
253
|
-
end
|
254
|
-
|
255
|
-
def render_consent_page(_client_id, _redirect_uri, _scope, _state, _code_challenge, _code_challenge_method)
|
256
|
-
# In production, this would render a proper consent page
|
257
|
-
# For now, just auto-deny unknown clients
|
258
|
-
render json: {
|
259
|
-
error: "access_denied",
|
260
|
-
error_description: "User denied authorization"
|
261
|
-
}, status: :forbidden
|
262
|
-
end
|
263
|
-
end
|
264
|
-
end
|
265
|
-
end
|
@@ -1,125 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module ActionMCP
|
4
|
-
module OAuth
|
5
|
-
# Controller for OAuth 2.1 metadata endpoints
|
6
|
-
# Provides server discovery information as per RFC 8414
|
7
|
-
class MetadataController < ActionController::Base
|
8
|
-
before_action :check_oauth_enabled
|
9
|
-
|
10
|
-
# GET /.well-known/oauth-authorization-server
|
11
|
-
# Returns OAuth Authorization Server Metadata as per RFC 8414
|
12
|
-
def authorization_server
|
13
|
-
metadata = {
|
14
|
-
issuer: issuer_url,
|
15
|
-
authorization_endpoint: authorization_endpoint,
|
16
|
-
token_endpoint: token_endpoint,
|
17
|
-
introspection_endpoint: introspection_endpoint,
|
18
|
-
revocation_endpoint: revocation_endpoint,
|
19
|
-
response_types_supported: response_types_supported,
|
20
|
-
grant_types_supported: grant_types_supported,
|
21
|
-
token_endpoint_auth_methods_supported: token_endpoint_auth_methods_supported,
|
22
|
-
scopes_supported: scopes_supported,
|
23
|
-
code_challenge_methods_supported: code_challenge_methods_supported,
|
24
|
-
service_documentation: service_documentation
|
25
|
-
}
|
26
|
-
|
27
|
-
# Add optional fields based on configuration
|
28
|
-
metadata[:registration_endpoint] = registration_endpoint if oauth_config[:enable_dynamic_registration]
|
29
|
-
|
30
|
-
metadata[:jwks_uri] = oauth_config[:jwks_uri] if oauth_config[:jwks_uri]
|
31
|
-
|
32
|
-
render json: metadata
|
33
|
-
end
|
34
|
-
|
35
|
-
# GET /.well-known/oauth-protected-resource
|
36
|
-
# Returns Protected Resource Metadata as per RFC 8705
|
37
|
-
def protected_resource
|
38
|
-
metadata = {
|
39
|
-
resource: issuer_url,
|
40
|
-
authorization_servers: [ issuer_url ],
|
41
|
-
scopes_supported: scopes_supported,
|
42
|
-
bearer_methods_supported: [ "header" ],
|
43
|
-
resource_documentation: resource_documentation
|
44
|
-
}
|
45
|
-
|
46
|
-
render json: metadata
|
47
|
-
end
|
48
|
-
|
49
|
-
private
|
50
|
-
|
51
|
-
def check_oauth_enabled
|
52
|
-
auth_methods = ActionMCP.configuration.authentication_methods
|
53
|
-
return if auth_methods&.include?("oauth")
|
54
|
-
|
55
|
-
head :not_found
|
56
|
-
end
|
57
|
-
|
58
|
-
def oauth_config
|
59
|
-
@oauth_config ||= HashWithIndifferentAccess.new(ActionMCP.configuration.oauth_config || {})
|
60
|
-
end
|
61
|
-
|
62
|
-
def issuer_url
|
63
|
-
@issuer_url ||= oauth_config.fetch(:issuer_url, request.base_url)
|
64
|
-
end
|
65
|
-
|
66
|
-
def authorization_endpoint
|
67
|
-
"#{issuer_url}/oauth/authorize"
|
68
|
-
end
|
69
|
-
|
70
|
-
def token_endpoint
|
71
|
-
"#{issuer_url}/oauth/token"
|
72
|
-
end
|
73
|
-
|
74
|
-
def introspection_endpoint
|
75
|
-
"#{issuer_url}/oauth/introspect"
|
76
|
-
end
|
77
|
-
|
78
|
-
def revocation_endpoint
|
79
|
-
"#{issuer_url}/oauth/revoke"
|
80
|
-
end
|
81
|
-
|
82
|
-
def registration_endpoint
|
83
|
-
"#{issuer_url}/oauth/register"
|
84
|
-
end
|
85
|
-
|
86
|
-
def response_types_supported
|
87
|
-
[ "code" ]
|
88
|
-
end
|
89
|
-
|
90
|
-
def grant_types_supported
|
91
|
-
grants = [ "authorization_code" ]
|
92
|
-
grants << "refresh_token" if oauth_config[:enable_refresh_tokens]
|
93
|
-
grants << "client_credentials" if oauth_config[:enable_client_credentials]
|
94
|
-
grants
|
95
|
-
end
|
96
|
-
|
97
|
-
def token_endpoint_auth_methods_supported
|
98
|
-
methods = %w[client_secret_basic client_secret_post]
|
99
|
-
methods << "none" if oauth_config[:allow_public_clients]
|
100
|
-
methods
|
101
|
-
end
|
102
|
-
|
103
|
-
def scopes_supported
|
104
|
-
oauth_config.fetch(:scopes_supported, [ "mcp:tools", "mcp:resources", "mcp:prompts" ])
|
105
|
-
end
|
106
|
-
|
107
|
-
def code_challenge_methods_supported
|
108
|
-
methods = []
|
109
|
-
if oauth_config[:pkce_required] || oauth_config[:pkce_supported]
|
110
|
-
methods << "S256"
|
111
|
-
methods << "plain" if oauth_config[:allow_plain_pkce]
|
112
|
-
end
|
113
|
-
methods
|
114
|
-
end
|
115
|
-
|
116
|
-
def service_documentation
|
117
|
-
oauth_config.fetch(:service_documentation, "#{request.base_url}/docs")
|
118
|
-
end
|
119
|
-
|
120
|
-
def resource_documentation
|
121
|
-
oauth_config.fetch(:resource_documentation, "#{request.base_url}/docs/api")
|
122
|
-
end
|
123
|
-
end
|
124
|
-
end
|
125
|
-
end
|