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.
Files changed (84) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +187 -16
  3. data/app/controllers/action_mcp/application_controller.rb +64 -49
  4. data/app/models/action_mcp/session/message.rb +31 -20
  5. data/app/models/action_mcp/session/resource.rb +35 -20
  6. data/app/models/action_mcp/session/sse_event.rb +23 -17
  7. data/app/models/action_mcp/session/subscription.rb +22 -15
  8. data/app/models/action_mcp/session.rb +71 -113
  9. data/config/routes.rb +0 -11
  10. data/db/migrate/20250512154359_consolidated_migration.rb +3 -3
  11. data/db/migrate/20250715070713_add_consents_to_action_mcp_sess.rb +7 -0
  12. data/db/migrate/20250727000001_remove_oauth_support.rb +59 -0
  13. data/lib/action_mcp/base_response.rb +1 -1
  14. data/lib/action_mcp/client/base.rb +9 -11
  15. data/lib/action_mcp/client/elicitation.rb +4 -4
  16. data/lib/action_mcp/client/json_rpc_handler.rb +11 -13
  17. data/lib/action_mcp/client/streamable_http_transport.rb +19 -74
  18. data/lib/action_mcp/client.rb +6 -26
  19. data/lib/action_mcp/configuration.rb +65 -63
  20. data/lib/action_mcp/engine.rb +1 -10
  21. data/lib/action_mcp/filtered_logger.rb +3 -7
  22. data/lib/action_mcp/gateway.rb +7 -11
  23. data/lib/action_mcp/gateway_identifier.rb +187 -3
  24. data/lib/action_mcp/gateway_identifiers/api_key_identifier.rb +56 -0
  25. data/lib/action_mcp/gateway_identifiers/devise_identifier.rb +34 -0
  26. data/lib/action_mcp/gateway_identifiers/request_env_identifier.rb +58 -0
  27. data/lib/action_mcp/gateway_identifiers/warden_identifier.rb +38 -0
  28. data/lib/action_mcp/gateway_identifiers.rb +26 -0
  29. data/lib/action_mcp/json_rpc_handler_base.rb +0 -2
  30. data/lib/action_mcp/prompt.rb +2 -0
  31. data/lib/action_mcp/renderable.rb +1 -1
  32. data/lib/action_mcp/resource_template.rb +6 -2
  33. data/lib/action_mcp/server/{memory_session.rb → base_session.rb} +41 -26
  34. data/lib/action_mcp/server/base_session_store.rb +86 -0
  35. data/lib/action_mcp/server/capabilities.rb +2 -1
  36. data/lib/action_mcp/server/elicitation.rb +3 -9
  37. data/lib/action_mcp/server/error_handling.rb +14 -1
  38. data/lib/action_mcp/server/handlers/router.rb +31 -0
  39. data/lib/action_mcp/server/json_rpc_handler.rb +2 -5
  40. data/lib/action_mcp/server/{messaging.rb → messaging_service.rb} +38 -14
  41. data/lib/action_mcp/server/prompts.rb +4 -4
  42. data/lib/action_mcp/server/resources.rb +23 -4
  43. data/lib/action_mcp/server/session_store_factory.rb +1 -1
  44. data/lib/action_mcp/server/solid_mcp_adapter.rb +9 -10
  45. data/lib/action_mcp/server/tools.rb +62 -43
  46. data/lib/action_mcp/server/transport_handler.rb +2 -4
  47. data/lib/action_mcp/server/volatile_session_store.rb +1 -93
  48. data/lib/action_mcp/tagged_stream_logging.rb +2 -2
  49. data/lib/action_mcp/test_helper/progress_notification_assertions.rb +4 -4
  50. data/lib/action_mcp/test_helper/session_store_assertions.rb +5 -1
  51. data/lib/action_mcp/tool.rb +48 -37
  52. data/lib/action_mcp/types/float_array_type.rb +5 -3
  53. data/lib/action_mcp/version.rb +1 -1
  54. data/lib/action_mcp.rb +2 -7
  55. data/lib/generators/action_mcp/identifier/identifier_generator.rb +189 -0
  56. data/lib/generators/action_mcp/identifier/templates/identifier.rb.erb +35 -0
  57. data/lib/generators/action_mcp/install/install_generator.rb +1 -1
  58. data/lib/generators/action_mcp/install/templates/application_gateway.rb +86 -36
  59. data/lib/generators/action_mcp/install/templates/mcp.yml +4 -21
  60. data/lib/tasks/action_mcp_tasks.rake +7 -5
  61. metadata +18 -100
  62. data/app/controllers/action_mcp/oauth/endpoints_controller.rb +0 -264
  63. data/app/controllers/action_mcp/oauth/metadata_controller.rb +0 -129
  64. data/app/controllers/action_mcp/oauth/registration_controller.rb +0 -206
  65. data/app/models/action_mcp/oauth_client.rb +0 -157
  66. data/app/models/action_mcp/oauth_token.rb +0 -141
  67. data/db/migrate/20250608112101_add_oauth_to_sessions.rb +0 -19
  68. data/db/migrate/20250708105124_create_action_mcp_oauth_clients.rb +0 -42
  69. data/db/migrate/20250708105226_create_action_mcp_oauth_tokens.rb +0 -37
  70. data/lib/action_mcp/client/jwt_client_provider.rb +0 -134
  71. data/lib/action_mcp/client/oauth_client_provider/memory_storage.rb +0 -47
  72. data/lib/action_mcp/client/oauth_client_provider.rb +0 -234
  73. data/lib/action_mcp/jwt_decoder.rb +0 -26
  74. data/lib/action_mcp/jwt_identifier.rb +0 -28
  75. data/lib/action_mcp/none_identifier.rb +0 -19
  76. data/lib/action_mcp/o_auth_identifier.rb +0 -34
  77. data/lib/action_mcp/oauth/active_record_storage.rb +0 -183
  78. data/lib/action_mcp/oauth/error.rb +0 -79
  79. data/lib/action_mcp/oauth/memory_storage.rb +0 -134
  80. data/lib/action_mcp/oauth/middleware.rb +0 -133
  81. data/lib/action_mcp/oauth/provider.rb +0 -426
  82. data/lib/action_mcp/oauth.rb +0 -12
  83. data/lib/action_mcp/omniauth/mcp_strategy.rb +0 -176
  84. 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
- # Specify what attributes identify a connection
5
- # Multiple identifiers can be used (e.g., user, account, organization)
6
- identified_by :user
7
-
8
- protected
9
-
10
- # Override this method to implement your authentication logic
11
- # Must return a hash with keys matching the identified_by attributes
12
- # or raise ActionMCP::UnauthorizedError
13
- def authenticate!
14
- # Example using JWT:
15
- token = extract_bearer_token
16
- raise ActionMCP::UnauthorizedError, "Missing token" unless token
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
- # JWT authentication for testing environment
71
- authentication: ["jwt"]
62
+ # No authentication for testing environment
63
+ authentication: ["none"]
72
64
 
73
65
  # Test adapter for testing
74
66
  adapter: test
@@ -78,17 +70,8 @@ test:
78
70
 
79
71
  # Production environment
80
72
  production:
81
- # Multiple authentication methods - try OAuth first, fallback to JWT
82
- authentication: ["oauth", "jwt"]
83
-
84
- # OAuth configuration for production
85
- oauth:
86
- provider: "application_oauth_provider" # Your custom provider class
87
- scopes_supported: ["mcp:tools", "mcp:resources", "mcp:prompts"]
88
- enable_dynamic_registration: true
89
- enable_token_revocation: true
90
- pkce_required: true
91
- issuer_url: https://yourapp.com
73
+ # Configure authentication methods for production (customize as needed)
74
+ authentication: ["none"]
92
75
 
93
76
  # Additional production profiles for external clients
94
77
  profiles:
@@ -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 && config.oauth_config.any?
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 > 0
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 > 0
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 { |_type, count| -count }.first(10)
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 > 0
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.71.1
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: It offers base classes and helpers for creating MCP applications, making
195
- it easier to integrate your Ruby/Rails application with the MCP standard
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/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
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/memory_session.rb
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: Provides essential tooling for building Model Context Protocol (MCP) capable
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