actionmcp 0.71.1 → 0.80.0
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 +187 -16
- data/app/controllers/action_mcp/application_controller.rb +64 -49
- 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 +71 -113
- data/config/routes.rb +0 -11
- data/db/migrate/20250512154359_consolidated_migration.rb +3 -3
- data/db/migrate/20250715070713_add_consents_to_action_mcp_sess.rb +7 -0
- data/db/migrate/20250727000001_remove_oauth_support.rb +59 -0
- data/lib/action_mcp/base_response.rb +1 -1
- data/lib/action_mcp/client/base.rb +9 -11
- data/lib/action_mcp/client/elicitation.rb +4 -4
- data/lib/action_mcp/client/json_rpc_handler.rb +11 -13
- data/lib/action_mcp/client/streamable_http_transport.rb +19 -74
- data/lib/action_mcp/client.rb +6 -26
- data/lib/action_mcp/configuration.rb +65 -63
- data/lib/action_mcp/engine.rb +1 -10
- data/lib/action_mcp/filtered_logger.rb +3 -7
- data/lib/action_mcp/gateway.rb +7 -11
- 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/json_rpc_handler_base.rb +0 -2
- data/lib/action_mcp/prompt.rb +2 -0
- data/lib/action_mcp/renderable.rb +1 -1
- data/lib/action_mcp/resource_template.rb +6 -2
- data/lib/action_mcp/server/{memory_session.rb → base_session.rb} +41 -26
- data/lib/action_mcp/server/base_session_store.rb +86 -0
- data/lib/action_mcp/server/capabilities.rb +2 -1
- data/lib/action_mcp/server/elicitation.rb +3 -9
- data/lib/action_mcp/server/error_handling.rb +14 -1
- data/lib/action_mcp/server/handlers/router.rb +31 -0
- data/lib/action_mcp/server/json_rpc_handler.rb +2 -5
- data/lib/action_mcp/server/{messaging.rb → messaging_service.rb} +38 -14
- data/lib/action_mcp/server/prompts.rb +4 -4
- data/lib/action_mcp/server/resources.rb +23 -4
- data/lib/action_mcp/server/session_store_factory.rb +1 -1
- data/lib/action_mcp/server/solid_mcp_adapter.rb +9 -10
- data/lib/action_mcp/server/tools.rb +62 -43
- data/lib/action_mcp/server/transport_handler.rb +2 -4
- data/lib/action_mcp/server/volatile_session_store.rb +1 -93
- data/lib/action_mcp/tagged_stream_logging.rb +2 -2
- data/lib/action_mcp/test_helper/progress_notification_assertions.rb +4 -4
- data/lib/action_mcp/test_helper/session_store_assertions.rb +5 -1
- data/lib/action_mcp/tool.rb +48 -37
- data/lib/action_mcp/types/float_array_type.rb +5 -3
- data/lib/action_mcp/version.rb +1 -1
- data/lib/action_mcp.rb +2 -7
- 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 +86 -36
- data/lib/generators/action_mcp/install/templates/mcp.yml +4 -21
- data/lib/tasks/action_mcp_tasks.rake +7 -5
- metadata +18 -100
- data/app/controllers/action_mcp/oauth/endpoints_controller.rb +0 -264
- data/app/controllers/action_mcp/oauth/metadata_controller.rb +0 -129
- data/app/controllers/action_mcp/oauth/registration_controller.rb +0 -206
- data/app/models/action_mcp/oauth_client.rb +0 -157
- data/app/models/action_mcp/oauth_token.rb +0 -141
- data/db/migrate/20250608112101_add_oauth_to_sessions.rb +0 -19
- data/db/migrate/20250708105124_create_action_mcp_oauth_clients.rb +0 -42
- data/db/migrate/20250708105226_create_action_mcp_oauth_tokens.rb +0 -37
- data/lib/action_mcp/client/jwt_client_provider.rb +0 -134
- 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 -26
- 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 -134
- data/lib/action_mcp/oauth/middleware.rb +0 -133
- data/lib/action_mcp/oauth/provider.rb +0 -426
- data/lib/action_mcp/oauth.rb +0 -12
- data/lib/action_mcp/omniauth/mcp_strategy.rb +0 -176
- data/lib/action_mcp/server/notifications.rb +0 -58
@@ -1,40 +1,90 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
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.
|
10
|
+
|
3
11
|
class ApplicationGateway < ActionMCP::Gateway
|
4
|
-
#
|
5
|
-
#
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
#
|
11
|
-
|
12
|
-
#
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
payload = ActionMCP::JwtDecoder.decode(token)
|
19
|
-
user = resolve_user(payload)
|
20
|
-
|
21
|
-
raise ActionMCP::UnauthorizedError, "Unauthorized" unless user
|
22
|
-
|
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
|
28
|
-
|
29
|
-
private
|
30
|
-
|
31
|
-
# Example method to resolve user from JWT payload
|
32
|
-
def resolve_user(payload)
|
33
|
-
return nil unless payload.is_a?(Hash)
|
34
|
-
user_id = payload["user_id"] || payload["sub"]
|
35
|
-
return nil unless user_id
|
36
|
-
|
37
|
-
# Replace with your User model lookup
|
38
|
-
User.find_by(id: user_id)
|
39
|
-
end
|
12
|
+
# Register your identifier classes in order of preference
|
13
|
+
# The first successful identifier will be used
|
14
|
+
|
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
|
19
|
+
|
20
|
+
# Option 2: Use multiple auth methods (tries in order)
|
21
|
+
# identified_by ActionMCP::GatewayIdentifiers::WardenIdentifier,
|
22
|
+
# ActionMCP::GatewayIdentifiers::ApiKeyIdentifier
|
23
|
+
|
24
|
+
# Option 3: Create custom identifiers (see examples below)
|
25
|
+
# identified_by CustomUserIdentifier, CustomAdminIdentifier
|
40
26
|
end
|
27
|
+
|
28
|
+
# Custom identifier examples - uncomment and customize as needed:
|
29
|
+
|
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
|
45
|
+
|
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
|
73
|
+
|
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:
|
@@ -175,7 +175,7 @@ namespace :action_mcp do
|
|
175
175
|
# Authentication
|
176
176
|
puts "\n\e[36mAuthentication:\e[0m"
|
177
177
|
puts " Methods: #{config.authentication_methods.join(', ')}"
|
178
|
-
if config.oauth_config
|
178
|
+
if config.oauth_config&.any?
|
179
179
|
puts " OAuth Provider: #{config.oauth_config['provider']}"
|
180
180
|
puts " OAuth Scopes: #{config.oauth_config['scopes_supported']&.join(', ')}"
|
181
181
|
end
|
@@ -236,7 +236,7 @@ namespace :action_mcp do
|
|
236
236
|
total_sessions = ActionMCP::Session.count
|
237
237
|
puts " Total Sessions: #{total_sessions}"
|
238
238
|
|
239
|
-
if total_sessions
|
239
|
+
if total_sessions.positive?
|
240
240
|
# Sessions by status
|
241
241
|
sessions_by_status = ActionMCP::Session.group(:status).count
|
242
242
|
puts " Sessions by Status:"
|
@@ -282,7 +282,7 @@ namespace :action_mcp do
|
|
282
282
|
total_messages = ActionMCP::Session::Message.count
|
283
283
|
puts " Total Messages: #{total_messages}"
|
284
284
|
|
285
|
-
if total_messages
|
285
|
+
if total_messages.positive?
|
286
286
|
# Messages by direction
|
287
287
|
messages_by_direction = ActionMCP::Session::Message.group(:direction).count
|
288
288
|
puts " Messages by Direction:"
|
@@ -291,7 +291,9 @@ namespace :action_mcp do
|
|
291
291
|
end
|
292
292
|
|
293
293
|
# Messages by type
|
294
|
-
messages_by_type = ActionMCP::Session::Message.group(:message_type).count.sort_by
|
294
|
+
messages_by_type = ActionMCP::Session::Message.group(:message_type).count.sort_by do |_type, count|
|
295
|
+
-count
|
296
|
+
end.first(10)
|
295
297
|
puts " Top Message Types:"
|
296
298
|
messages_by_type.each do |type, count|
|
297
299
|
puts " #{type}: #{count}"
|
@@ -316,7 +318,7 @@ namespace :action_mcp do
|
|
316
318
|
total_events = ActionMCP::Session::SSEEvent.count
|
317
319
|
puts " Total SSE Events: #{total_events}"
|
318
320
|
|
319
|
-
if total_events
|
321
|
+
if total_events.positive?
|
320
322
|
# Recent events
|
321
323
|
recent_events = ActionMCP::Session::SSEEvent.where("created_at > ?", 1.hour.ago).count
|
322
324
|
puts " SSE Events (Last Hour): #{recent_events}"
|
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.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Abdelkader Boudih
|
@@ -93,48 +93,6 @@ dependencies:
|
|
93
93
|
- - "~>"
|
94
94
|
- !ruby/object:Gem::Version
|
95
95
|
version: '2.6'
|
96
|
-
- !ruby/object:Gem::Dependency
|
97
|
-
name: jwt
|
98
|
-
requirement: !ruby/object:Gem::Requirement
|
99
|
-
requirements:
|
100
|
-
- - "~>"
|
101
|
-
- !ruby/object:Gem::Version
|
102
|
-
version: '2.10'
|
103
|
-
type: :runtime
|
104
|
-
prerelease: false
|
105
|
-
version_requirements: !ruby/object:Gem::Requirement
|
106
|
-
requirements:
|
107
|
-
- - "~>"
|
108
|
-
- !ruby/object:Gem::Version
|
109
|
-
version: '2.10'
|
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,13 +135,8 @@ 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
|
@@ -220,9 +146,8 @@ files:
|
|
220
146
|
- app/models/concerns/mcp_message_inspect.rb
|
221
147
|
- config/routes.rb
|
222
148
|
- db/migrate/20250512154359_consolidated_migration.rb
|
223
|
-
- db/migrate/
|
224
|
-
- db/migrate/
|
225
|
-
- db/migrate/20250708105226_create_action_mcp_oauth_tokens.rb
|
149
|
+
- db/migrate/20250715070713_add_consents_to_action_mcp_sess.rb
|
150
|
+
- db/migrate/20250727000001_remove_oauth_support.rb
|
226
151
|
- exe/actionmcp_cli
|
227
152
|
- lib/action_mcp.rb
|
228
153
|
- lib/action_mcp/base_response.rb
|
@@ -236,11 +161,8 @@ files:
|
|
236
161
|
- lib/action_mcp/client/collection.rb
|
237
162
|
- lib/action_mcp/client/elicitation.rb
|
238
163
|
- lib/action_mcp/client/json_rpc_handler.rb
|
239
|
-
- lib/action_mcp/client/jwt_client_provider.rb
|
240
164
|
- lib/action_mcp/client/logging.rb
|
241
165
|
- lib/action_mcp/client/messaging.rb
|
242
|
-
- lib/action_mcp/client/oauth_client_provider.rb
|
243
|
-
- lib/action_mcp/client/oauth_client_provider/memory_storage.rb
|
244
166
|
- lib/action_mcp/client/prompt_book.rb
|
245
167
|
- lib/action_mcp/client/prompts.rb
|
246
168
|
- lib/action_mcp/client/request_timeouts.rb
|
@@ -271,25 +193,19 @@ files:
|
|
271
193
|
- lib/action_mcp/filtered_logger.rb
|
272
194
|
- lib/action_mcp/gateway.rb
|
273
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
|
274
201
|
- lib/action_mcp/gem_version.rb
|
275
202
|
- lib/action_mcp/instrumentation/controller_runtime.rb
|
276
203
|
- lib/action_mcp/instrumentation/instrumentation.rb
|
277
204
|
- lib/action_mcp/instrumentation/resource_instrumentation.rb
|
278
205
|
- lib/action_mcp/integer_array.rb
|
279
206
|
- lib/action_mcp/json_rpc_handler_base.rb
|
280
|
-
- lib/action_mcp/jwt_decoder.rb
|
281
|
-
- lib/action_mcp/jwt_identifier.rb
|
282
207
|
- lib/action_mcp/log_subscriber.rb
|
283
208
|
- lib/action_mcp/logging.rb
|
284
|
-
- lib/action_mcp/none_identifier.rb
|
285
|
-
- lib/action_mcp/o_auth_identifier.rb
|
286
|
-
- lib/action_mcp/oauth.rb
|
287
|
-
- lib/action_mcp/oauth/active_record_storage.rb
|
288
|
-
- lib/action_mcp/oauth/error.rb
|
289
|
-
- lib/action_mcp/oauth/memory_storage.rb
|
290
|
-
- lib/action_mcp/oauth/middleware.rb
|
291
|
-
- lib/action_mcp/oauth/provider.rb
|
292
|
-
- lib/action_mcp/omniauth/mcp_strategy.rb
|
293
209
|
- lib/action_mcp/prompt.rb
|
294
210
|
- lib/action_mcp/prompt_response.rb
|
295
211
|
- lib/action_mcp/prompts_registry.rb
|
@@ -303,6 +219,8 @@ files:
|
|
303
219
|
- lib/action_mcp/server.rb
|
304
220
|
- lib/action_mcp/server/active_record_session_store.rb
|
305
221
|
- lib/action_mcp/server/base_messaging.rb
|
222
|
+
- lib/action_mcp/server/base_session.rb
|
223
|
+
- lib/action_mcp/server/base_session_store.rb
|
306
224
|
- lib/action_mcp/server/capabilities.rb
|
307
225
|
- lib/action_mcp/server/configuration.rb
|
308
226
|
- lib/action_mcp/server/elicitation.rb
|
@@ -310,11 +228,10 @@ files:
|
|
310
228
|
- lib/action_mcp/server/error_handling.rb
|
311
229
|
- lib/action_mcp/server/handlers/prompt_handler.rb
|
312
230
|
- lib/action_mcp/server/handlers/resource_handler.rb
|
231
|
+
- lib/action_mcp/server/handlers/router.rb
|
313
232
|
- lib/action_mcp/server/handlers/tool_handler.rb
|
314
233
|
- lib/action_mcp/server/json_rpc_handler.rb
|
315
|
-
- lib/action_mcp/server/
|
316
|
-
- lib/action_mcp/server/messaging.rb
|
317
|
-
- lib/action_mcp/server/notifications.rb
|
234
|
+
- lib/action_mcp/server/messaging_service.rb
|
318
235
|
- lib/action_mcp/server/prompts.rb
|
319
236
|
- lib/action_mcp/server/registry_management.rb
|
320
237
|
- lib/action_mcp/server/resources.rb
|
@@ -344,6 +261,8 @@ files:
|
|
344
261
|
- lib/action_mcp/uri_ambiguity_checker.rb
|
345
262
|
- lib/action_mcp/version.rb
|
346
263
|
- lib/actionmcp.rb
|
264
|
+
- lib/generators/action_mcp/identifier/identifier_generator.rb
|
265
|
+
- lib/generators/action_mcp/identifier/templates/identifier.rb.erb
|
347
266
|
- lib/generators/action_mcp/install/install_generator.rb
|
348
267
|
- lib/generators/action_mcp/install/templates/application_gateway.rb
|
349
268
|
- lib/generators/action_mcp/install/templates/application_mcp_prompt.rb
|
@@ -381,6 +300,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
381
300
|
requirements: []
|
382
301
|
rubygems_version: 3.6.9
|
383
302
|
specification_version: 4
|
384
|
-
summary:
|
385
|
-
servers
|
303
|
+
summary: Lightweight Model Context Protocol (MCP) server toolkit for Ruby/Rails
|
386
304
|
test_files: []
|
@@ -1,264 +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, client_secret = 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, client_secret = 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
|
-
unless auth_methods&.include?("oauth")
|
131
|
-
head :not_found
|
132
|
-
end
|
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
|
-
if client_id.blank?
|
148
|
-
client_id, client_secret = extract_client_credentials
|
149
|
-
end
|
150
|
-
|
151
|
-
return render_token_error("invalid_request", "Missing required parameters") if code.blank? || client_id.blank?
|
152
|
-
|
153
|
-
token_response = ActionMCP::OAuth::Provider.exchange_code_for_token(
|
154
|
-
code: code,
|
155
|
-
client_id: client_id,
|
156
|
-
client_secret: client_secret,
|
157
|
-
redirect_uri: redirect_uri,
|
158
|
-
code_verifier: code_verifier
|
159
|
-
)
|
160
|
-
|
161
|
-
render json: token_response
|
162
|
-
end
|
163
|
-
|
164
|
-
def handle_refresh_token_grant
|
165
|
-
refresh_token = params[:refresh_token]
|
166
|
-
scope = params[:scope]
|
167
|
-
|
168
|
-
# Extract client credentials
|
169
|
-
client_id, client_secret = extract_client_credentials
|
170
|
-
client_id ||= params[:client_id]
|
171
|
-
client_secret ||= params[:client_secret]
|
172
|
-
|
173
|
-
return render_token_error("invalid_request", "Missing required parameters") if refresh_token.blank? || client_id.blank?
|
174
|
-
|
175
|
-
token_response = ActionMCP::OAuth::Provider.refresh_access_token(
|
176
|
-
refresh_token: refresh_token,
|
177
|
-
client_id: client_id,
|
178
|
-
client_secret: client_secret,
|
179
|
-
scope: scope
|
180
|
-
)
|
181
|
-
|
182
|
-
render json: token_response
|
183
|
-
end
|
184
|
-
|
185
|
-
def handle_client_credentials_grant
|
186
|
-
scope = params[:scope]
|
187
|
-
|
188
|
-
# Extract client credentials
|
189
|
-
client_id, client_secret = extract_client_credentials
|
190
|
-
client_id ||= params[:client_id]
|
191
|
-
client_secret ||= params[:client_secret]
|
192
|
-
|
193
|
-
return render_token_error("invalid_request", "Missing client credentials") if client_id.blank?
|
194
|
-
|
195
|
-
token_response = ActionMCP::OAuth::Provider.client_credentials_grant(
|
196
|
-
client_id: client_id,
|
197
|
-
client_secret: client_secret,
|
198
|
-
scope: scope
|
199
|
-
)
|
200
|
-
|
201
|
-
render json: token_response
|
202
|
-
end
|
203
|
-
|
204
|
-
def extract_client_credentials
|
205
|
-
auth_header = request.headers["Authorization"]
|
206
|
-
if auth_header&.start_with?("Basic ")
|
207
|
-
encoded = auth_header.split(" ", 2).last
|
208
|
-
decoded = Base64.decode64(encoded)
|
209
|
-
decoded.split(":", 2)
|
210
|
-
else
|
211
|
-
[ nil, nil ]
|
212
|
-
end
|
213
|
-
end
|
214
|
-
|
215
|
-
def auto_approve_client?(client_id)
|
216
|
-
# In development/testing, auto-approve known clients
|
217
|
-
# In production, this should check a proper client registry
|
218
|
-
Rails.env.development? || Rails.env.test? || oauth_config["auto_approve_clients"]&.include?(client_id)
|
219
|
-
end
|
220
|
-
|
221
|
-
def current_user
|
222
|
-
# This should be implemented by the application
|
223
|
-
# For now, return a default user for development
|
224
|
-
if Rails.env.development? || Rails.env.test?
|
225
|
-
OpenStruct.new(id: "dev_user", email: "dev@example.com")
|
226
|
-
else
|
227
|
-
# In production, this should integrate with your authentication system
|
228
|
-
nil
|
229
|
-
end
|
230
|
-
end
|
231
|
-
|
232
|
-
def default_scope
|
233
|
-
oauth_config["default_scope"] || "mcp:tools mcp:resources mcp:prompts"
|
234
|
-
end
|
235
|
-
|
236
|
-
def render_error(error_code, description)
|
237
|
-
render json: {
|
238
|
-
error: error_code,
|
239
|
-
error_description: description
|
240
|
-
}, status: :bad_request
|
241
|
-
end
|
242
|
-
|
243
|
-
def render_token_error(error_code, description)
|
244
|
-
render json: {
|
245
|
-
error: error_code,
|
246
|
-
error_description: description
|
247
|
-
}, status: :bad_request
|
248
|
-
end
|
249
|
-
|
250
|
-
def render_introspection_error
|
251
|
-
render json: { active: false }, status: :bad_request
|
252
|
-
end
|
253
|
-
|
254
|
-
def render_consent_page(client_id, redirect_uri, scope, state, code_challenge, code_challenge_method)
|
255
|
-
# In production, this would render a proper consent page
|
256
|
-
# For now, just auto-deny unknown clients
|
257
|
-
render json: {
|
258
|
-
error: "access_denied",
|
259
|
-
error_description: "User denied authorization"
|
260
|
-
}, status: :forbidden
|
261
|
-
end
|
262
|
-
end
|
263
|
-
end
|
264
|
-
end
|