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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/app/controllers/action_mcp/application_controller.rb +20 -12
  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 +42 -119
  9. data/app/models/concerns/{mcp_console_helpers.rb → action_mcp/mcp_console_helpers.rb} +4 -3
  10. data/app/models/concerns/{mcp_message_inspect.rb → action_mcp/mcp_message_inspect.rb} +4 -3
  11. data/config/routes.rb +0 -13
  12. data/db/migrate/20250727000001_remove_oauth_support.rb +59 -0
  13. data/lib/action_mcp/client/streamable_http_transport.rb +1 -46
  14. data/lib/action_mcp/client.rb +2 -25
  15. data/lib/action_mcp/configuration.rb +51 -24
  16. data/lib/action_mcp/engine.rb +0 -7
  17. data/lib/action_mcp/filtered_logger.rb +2 -6
  18. data/lib/action_mcp/gateway_identifier.rb +187 -3
  19. data/lib/action_mcp/gateway_identifiers/api_key_identifier.rb +56 -0
  20. data/lib/action_mcp/gateway_identifiers/devise_identifier.rb +34 -0
  21. data/lib/action_mcp/gateway_identifiers/request_env_identifier.rb +58 -0
  22. data/lib/action_mcp/gateway_identifiers/warden_identifier.rb +38 -0
  23. data/lib/action_mcp/gateway_identifiers.rb +26 -0
  24. data/lib/action_mcp/resource_template.rb +1 -0
  25. data/lib/action_mcp/server/base_session.rb +2 -0
  26. data/lib/action_mcp/server/resources.rb +8 -7
  27. data/lib/action_mcp/version.rb +1 -1
  28. data/lib/action_mcp.rb +1 -6
  29. data/lib/generators/action_mcp/identifier/identifier_generator.rb +189 -0
  30. data/lib/generators/action_mcp/identifier/templates/identifier.rb.erb +35 -0
  31. data/lib/generators/action_mcp/install/install_generator.rb +1 -1
  32. data/lib/generators/action_mcp/install/templates/application_gateway.rb +80 -31
  33. data/lib/generators/action_mcp/install/templates/mcp.yml +4 -21
  34. metadata +15 -99
  35. data/app/controllers/action_mcp/oauth/endpoints_controller.rb +0 -265
  36. data/app/controllers/action_mcp/oauth/metadata_controller.rb +0 -125
  37. data/app/controllers/action_mcp/oauth/registration_controller.rb +0 -201
  38. data/app/models/action_mcp/oauth_client.rb +0 -159
  39. data/app/models/action_mcp/oauth_token.rb +0 -142
  40. data/db/migrate/20250608112101_add_oauth_to_sessions.rb +0 -28
  41. data/db/migrate/20250708105124_create_action_mcp_oauth_clients.rb +0 -44
  42. data/db/migrate/20250708105226_create_action_mcp_oauth_tokens.rb +0 -39
  43. data/lib/action_mcp/client/jwt_client_provider.rb +0 -135
  44. data/lib/action_mcp/client/oauth_client_provider/memory_storage.rb +0 -47
  45. data/lib/action_mcp/client/oauth_client_provider.rb +0 -234
  46. data/lib/action_mcp/jwt_decoder.rb +0 -28
  47. data/lib/action_mcp/jwt_identifier.rb +0 -28
  48. data/lib/action_mcp/none_identifier.rb +0 -19
  49. data/lib/action_mcp/o_auth_identifier.rb +0 -34
  50. data/lib/action_mcp/oauth/active_record_storage.rb +0 -183
  51. data/lib/action_mcp/oauth/error.rb +0 -79
  52. data/lib/action_mcp/oauth/memory_storage.rb +0 -132
  53. data/lib/action_mcp/oauth/middleware.rb +0 -128
  54. data/lib/action_mcp/oauth/provider.rb +0 -406
  55. data/lib/action_mcp/oauth.rb +0 -12
  56. data/lib/action_mcp/omniauth/mcp_strategy.rb +0 -162
@@ -1,201 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ActionMCP
4
- module OAuth
5
- # OAuth 2.0 Dynamic Client Registration Controller (RFC 7591)
6
- # Allows clients to dynamically register with the authorization server
7
- class RegistrationController < ActionController::Base
8
- protect_from_forgery with: :null_session
9
- before_action :check_oauth_enabled
10
- before_action :check_registration_enabled
11
-
12
- # POST /oauth/register
13
- # Dynamic client registration endpoint as per RFC 7591
14
- def create
15
- # Parse client metadata from request body
16
- client_metadata = parse_client_metadata
17
-
18
- # Validate required fields
19
- validate_client_metadata(client_metadata)
20
-
21
- # Generate client credentials
22
- client_id = generate_client_id
23
- client_secret = nil # Public clients by default
24
-
25
- # Generate client secret for confidential clients
26
- client_secret = generate_client_secret if client_metadata["token_endpoint_auth_method"] != "none"
27
-
28
- # Store client registration
29
- client_info = {
30
- client_id: client_id,
31
- client_secret: client_secret,
32
- client_id_issued_at: Time.current.to_i,
33
- client_metadata: client_metadata,
34
- created_at: Time.current
35
- }
36
-
37
- # Save client registration (delegated to provider)
38
- ActionMCP::OAuth::Provider.register_client(client_info)
39
-
40
- # Build response according to RFC 7591
41
- response_data = {
42
- client_id: client_id,
43
- client_id_issued_at: client_info[:client_id_issued_at]
44
- }
45
-
46
- # Include client secret for confidential clients
47
- if client_secret
48
- response_data[:client_secret] = client_secret
49
- response_data[:client_secret_expires_at] = 0 # Never expires
50
- end
51
-
52
- # Include all client metadata in response
53
- response_data.merge!(client_metadata)
54
-
55
- # Add registration management fields if enabled
56
- if oauth_config[:enable_registration_management]
57
- response_data[:registration_access_token] = generate_registration_access_token(client_id)
58
- response_data[:registration_client_uri] = registration_client_url(client_id)
59
- end
60
-
61
- render json: response_data, status: :created
62
- rescue ActionMCP::OAuth::Error => e
63
- render_registration_error(e.oauth_error_code, e.message)
64
- rescue StandardError => e
65
- Rails.logger.error "Registration error: #{e.message}"
66
- render_registration_error("invalid_client_metadata", "Invalid client metadata")
67
- end
68
-
69
- private
70
-
71
- def check_oauth_enabled
72
- auth_methods = ActionMCP.configuration.authentication_methods
73
- return if auth_methods&.include?("oauth")
74
-
75
- head :not_found
76
- end
77
-
78
- def check_registration_enabled
79
- return if oauth_config[:enable_dynamic_registration]
80
-
81
- head :not_found
82
- end
83
-
84
- def oauth_config
85
- @oauth_config ||= HashWithIndifferentAccess.new(ActionMCP.configuration.oauth_config || {})
86
- end
87
-
88
- def parse_client_metadata
89
- # RFC 7591 requires JSON request body
90
- unless request.content_type&.include?("application/json")
91
- raise ActionMCP::OAuth::InvalidRequestError, "Content-Type must be application/json"
92
- end
93
-
94
- JSON.parse(request.body.read)
95
- rescue JSON::ParserError
96
- raise ActionMCP::OAuth::InvalidRequestError, "Invalid JSON"
97
- end
98
-
99
- def validate_client_metadata(metadata)
100
- # Validate redirect URIs (required for authorization code flow)
101
- if metadata["grant_types"]&.include?("authorization_code") ||
102
- metadata["response_types"]&.include?("code")
103
- unless metadata["redirect_uris"].is_a?(Array) && metadata["redirect_uris"].any?
104
- raise ActionMCP::OAuth::InvalidClientMetadataError, "redirect_uris required for authorization code flow"
105
- end
106
-
107
- # Validate redirect URI format
108
- metadata["redirect_uris"].each do |uri|
109
- validate_redirect_uri(uri)
110
- end
111
- end
112
-
113
- # Validate grant types
114
- if metadata["grant_types"]
115
- unsupported = metadata["grant_types"] - supported_grant_types
116
- if unsupported.any?
117
- raise ActionMCP::OAuth::InvalidClientMetadataError, "Unsupported grant types: #{unsupported.join(', ')}"
118
- end
119
- end
120
-
121
- # Validate response types
122
- if metadata["response_types"]
123
- unsupported = metadata["response_types"] - supported_response_types
124
- if unsupported.any?
125
- raise ActionMCP::OAuth::InvalidClientMetadataError, "Unsupported response types: #{unsupported.join(', ')}"
126
- end
127
- end
128
-
129
- # Validate token endpoint auth method
130
- return unless metadata["token_endpoint_auth_method"]
131
- return if supported_auth_methods.include?(metadata["token_endpoint_auth_method"])
132
-
133
- raise ActionMCP::OAuth::InvalidClientMetadataError, "Unsupported token endpoint auth method"
134
- end
135
-
136
- def validate_redirect_uri(uri)
137
- parsed = URI.parse(uri)
138
-
139
- # Must be absolute URI
140
- raise ActionMCP::OAuth::InvalidClientMetadataError, "Redirect URI must be absolute" unless parsed.absolute?
141
-
142
- # For non-localhost, must use HTTPS
143
- unless [ "localhost", "127.0.0.1" ].include?(parsed.host) || parsed.scheme == "https"
144
- raise ActionMCP::OAuth::InvalidClientMetadataError, "Redirect URI must use HTTPS"
145
- end
146
- rescue URI::InvalidURIError
147
- raise ActionMCP::OAuth::InvalidClientMetadataError, "Invalid redirect URI format"
148
- end
149
-
150
- def generate_client_id
151
- # Generate a unique client identifier
152
- "mcp_#{SecureRandom.hex(16)}"
153
- end
154
-
155
- def generate_client_secret
156
- # Generate a secure client secret
157
- SecureRandom.urlsafe_base64(32)
158
- end
159
-
160
- def generate_registration_access_token(_client_id)
161
- # Generate a token for managing this registration
162
- SecureRandom.urlsafe_base64(32)
163
- end
164
-
165
- def registration_client_url(client_id)
166
- "#{request.base_url}/oauth/register/#{client_id}"
167
- end
168
-
169
- def supported_grant_types
170
- grants = [ "authorization_code" ]
171
- grants << "refresh_token" if oauth_config[:enable_refresh_tokens]
172
- grants << "client_credentials" if oauth_config[:enable_client_credentials]
173
- grants
174
- end
175
-
176
- def supported_response_types
177
- [ "code" ]
178
- end
179
-
180
- def supported_auth_methods
181
- methods = %w[client_secret_basic client_secret_post]
182
- methods << "none" if oauth_config.fetch(:allow_public_clients, true)
183
- methods
184
- end
185
-
186
- def render_registration_error(error_code, description)
187
- render json: {
188
- error: error_code,
189
- error_description: description
190
- }, status: :bad_request
191
- end
192
- end
193
-
194
- # Custom error for invalid client metadata
195
- class InvalidClientMetadataError < Error
196
- def initialize(message = "Invalid client metadata")
197
- super(message, "invalid_client_metadata")
198
- end
199
- end
200
- end
201
- end
@@ -1,159 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ActionMCP
4
- # OAuth 2.0 Client model for storing registered clients
5
- # == Schema Information
6
- #
7
- # Table name: action_mcp_oauth_clients
8
- #
9
- # id :integer not null, primary key
10
- # active :boolean default(TRUE)
11
- # client_id_issued_at :integer
12
- # client_name :string
13
- # client_secret :string
14
- # client_secret_expires_at :integer
15
- # grant_types :json
16
- # metadata :json
17
- # redirect_uris :json
18
- # registration_access_token :string
19
- # response_types :json
20
- # scope :text
21
- # token_endpoint_auth_method :string default("client_secret_basic")
22
- # created_at :datetime not null
23
- # updated_at :datetime not null
24
- # client_id :string not null
25
- #
26
- # Indexes
27
- #
28
- # index_action_mcp_oauth_clients_on_active (active)
29
- # index_action_mcp_oauth_clients_on_client_id (client_id) UNIQUE
30
- # index_action_mcp_oauth_clients_on_client_id_issued_at (client_id_issued_at)
31
- #
32
- # Implements RFC 7591 Dynamic Client Registration
33
- class OAuthClient < ApplicationRecord
34
- self.table_name = "action_mcp_oauth_clients"
35
-
36
- # Validations
37
- validates :client_id, presence: true, uniqueness: true
38
- validates :token_endpoint_auth_method, inclusion: {
39
- in: %w[none client_secret_basic client_secret_post client_secret_jwt private_key_jwt]
40
- }
41
-
42
- # Scopes
43
- scope :active, -> { where(active: true) }
44
- scope :expired, lambda {
45
- where("client_secret_expires_at < ?", Time.current.to_i).where.not(client_secret_expires_at: [ nil, 0 ])
46
- }
47
-
48
- # Callbacks
49
- before_create :set_issued_at
50
-
51
- # Check if client secret is expired
52
- def secret_expired?
53
- return false if client_secret_expires_at.nil? || client_secret_expires_at.zero?
54
-
55
- Time.current.to_i > client_secret_expires_at
56
- end
57
-
58
- # Check if client is public (no authentication required)
59
- def public_client?
60
- token_endpoint_auth_method == "none"
61
- end
62
-
63
- # Check if client is confidential (authentication required)
64
- def confidential_client?
65
- !public_client?
66
- end
67
-
68
- # Validate redirect URI against registered URIs
69
- def valid_redirect_uri?(uri)
70
- return false if redirect_uris.blank?
71
-
72
- redirect_uris.include?(uri)
73
- end
74
-
75
- # Check if grant type is supported by this client
76
- def supports_grant_type?(grant_type)
77
- grant_types.include?(grant_type)
78
- end
79
-
80
- # Check if response type is supported by this client
81
- def supports_response_type?(response_type)
82
- response_types.include?(response_type)
83
- end
84
-
85
- # Check if scope is allowed for this client
86
- def valid_scope?(requested_scope)
87
- return true if scope.blank? # No scope restrictions
88
-
89
- requested_scopes = requested_scope.split(" ")
90
- allowed_scopes = scope.split(" ")
91
-
92
- # All requested scopes must be in allowed scopes
93
- (requested_scopes - allowed_scopes).empty?
94
- end
95
-
96
- # Convert to hash for API responses
97
- def to_api_response
98
- response = {
99
- client_id: client_id,
100
- client_id_issued_at: client_id_issued_at
101
- }
102
-
103
- # Include client secret for confidential clients
104
- if client_secret.present?
105
- response[:client_secret] = client_secret
106
- response[:client_secret_expires_at] = client_secret_expires_at || 0
107
- end
108
-
109
- # Include metadata fields
110
- %w[
111
- client_name redirect_uris grant_types response_types
112
- token_endpoint_auth_method scope
113
- ].each do |field|
114
- value = send(field)
115
- response[field.to_sym] = value if value.present?
116
- end
117
-
118
- # Include additional metadata
119
- response.merge!(metadata) if metadata.present?
120
-
121
- response
122
- end
123
-
124
- # Create from registration request
125
- def self.create_from_registration(client_metadata)
126
- client = new
127
-
128
- # Set basic fields
129
- client.client_id = "mcp_#{SecureRandom.hex(16)}"
130
-
131
- # Set metadata fields
132
- %w[
133
- client_name redirect_uris grant_types response_types
134
- token_endpoint_auth_method scope
135
- ].each do |field|
136
- client.send("#{field}=", client_metadata[field]) if client_metadata[field]
137
- end
138
-
139
- # Generate client secret for confidential clients
140
- client.client_secret = SecureRandom.urlsafe_base64(32) if client.confidential_client?
141
-
142
- # Store any additional metadata
143
- known_fields = %w[
144
- client_name redirect_uris grant_types response_types
145
- token_endpoint_auth_method scope
146
- ]
147
- additional_metadata = client_metadata.except(*known_fields)
148
- client.metadata = additional_metadata if additional_metadata.present?
149
-
150
- client
151
- end
152
-
153
- private
154
-
155
- def set_issued_at
156
- self.client_id_issued_at ||= Time.current.to_i
157
- end
158
- end
159
- end
@@ -1,142 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ActionMCP
4
- # == Schema Information
5
- #
6
- # Table name: action_mcp_oauth_tokens
7
- #
8
- # id :integer not null, primary key
9
- # access_token :string
10
- # code_challenge :string
11
- # code_challenge_method :string
12
- # expires_at :datetime
13
- # metadata :json
14
- # redirect_uri :string
15
- # revoked :boolean default(FALSE)
16
- # scope :text
17
- # token :string not null
18
- # token_type :string not null
19
- # created_at :datetime not null
20
- # updated_at :datetime not null
21
- # client_id :string not null
22
- # user_id :string
23
- #
24
- # Indexes
25
- #
26
- # index_action_mcp_oauth_tokens_on_client_id (client_id)
27
- # index_action_mcp_oauth_tokens_on_expires_at (expires_at)
28
- # index_action_mcp_oauth_tokens_on_revoked (revoked)
29
- # index_action_mcp_oauth_tokens_on_token (token) UNIQUE
30
- # index_action_mcp_oauth_tokens_on_token_type (token_type)
31
- # index_action_mcp_oauth_tokens_on_token_type_and_expires_at (token_type,expires_at)
32
- # index_action_mcp_oauth_tokens_on_user_id (user_id)
33
- #
34
- # OAuth 2.0 Token model for storing access tokens, refresh tokens, and authorization codes
35
- class OAuthToken < ApplicationRecord
36
- self.table_name = "action_mcp_oauth_tokens"
37
-
38
- # Token types
39
- ACCESS_TOKEN = "access_token"
40
- REFRESH_TOKEN = "refresh_token"
41
- AUTHORIZATION_CODE = "authorization_code"
42
-
43
- # Validations
44
- validates :token, presence: true, uniqueness: true
45
- validates :token_type, presence: true, inclusion: { in: [ ACCESS_TOKEN, REFRESH_TOKEN, AUTHORIZATION_CODE ] }
46
- validates :client_id, presence: true
47
- validates :expires_at, presence: true
48
-
49
- # Scopes
50
- scope :active, -> { where(revoked: false).where("expires_at > ?", Time.current) }
51
- scope :expired, -> { where("expires_at <= ?", Time.current) }
52
- scope :access_tokens, -> { where(token_type: ACCESS_TOKEN) }
53
- scope :refresh_tokens, -> { where(token_type: REFRESH_TOKEN) }
54
- scope :authorization_codes, -> { where(token_type: AUTHORIZATION_CODE) }
55
-
56
- # Check if token is still valid
57
- def still_valid?
58
- !revoked? && !expired?
59
- end
60
-
61
- # Check if token is expired
62
- def expired?
63
- expires_at <= Time.current
64
- end
65
-
66
- # Revoke the token
67
- def revoke!
68
- update!(revoked: true)
69
- end
70
-
71
- # Convert to introspection response
72
- def to_introspection_response
73
- if still_valid?
74
- {
75
- active: true,
76
- scope: scope,
77
- client_id: client_id,
78
- username: user_id,
79
- token_type: token_type == ACCESS_TOKEN ? "Bearer" : token_type,
80
- exp: expires_at.to_i,
81
- iat: created_at.to_i,
82
- nbf: created_at.to_i,
83
- sub: user_id,
84
- aud: client_id,
85
- iss: ActionMCP.configuration.oauth_config&.dig("issuer_url")
86
- }.compact
87
- else
88
- { active: false }
89
- end
90
- end
91
-
92
- # Create authorization code
93
- def self.create_authorization_code(client_id:, user_id:, redirect_uri:, scope:, code_challenge: nil,
94
- code_challenge_method: nil)
95
- create!(
96
- token: SecureRandom.urlsafe_base64(32),
97
- token_type: AUTHORIZATION_CODE,
98
- client_id: client_id,
99
- user_id: user_id,
100
- redirect_uri: redirect_uri,
101
- scope: scope,
102
- code_challenge: code_challenge,
103
- code_challenge_method: code_challenge_method,
104
- expires_at: 10.minutes.from_now
105
- )
106
- end
107
-
108
- # Create access token
109
- def self.create_access_token(client_id:, user_id:, scope:)
110
- expires_in = ActionMCP.configuration.oauth_config&.dig("access_token_expires_in") || 3600
111
-
112
- create!(
113
- token: SecureRandom.urlsafe_base64(32),
114
- token_type: ACCESS_TOKEN,
115
- client_id: client_id,
116
- user_id: user_id,
117
- scope: scope,
118
- expires_at: expires_in.seconds.from_now
119
- )
120
- end
121
-
122
- # Create refresh token
123
- def self.create_refresh_token(client_id:, user_id:, scope:, access_token:)
124
- expires_in = ActionMCP.configuration.oauth_config&.dig("refresh_token_expires_in") || 7.days.to_i
125
-
126
- create!(
127
- token: SecureRandom.urlsafe_base64(32),
128
- token_type: REFRESH_TOKEN,
129
- client_id: client_id,
130
- user_id: user_id,
131
- scope: scope,
132
- access_token: access_token,
133
- expires_at: expires_in.seconds.from_now
134
- )
135
- end
136
-
137
- # Clean up expired tokens
138
- def self.cleanup_expired
139
- expired.delete_all
140
- end
141
- end
142
- end
@@ -1,28 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class AddOAuthToSessions < ActiveRecord::Migration[8.0]
4
- def change
5
- # Use json for all databases (PostgreSQL, SQLite3, MySQL) for consistency
6
- json_type = :json
7
-
8
- add_column :action_mcp_sessions, :oauth_access_token, :string unless column_exists?(:action_mcp_sessions,
9
- :oauth_access_token)
10
- add_column :action_mcp_sessions, :oauth_refresh_token, :string unless column_exists?(:action_mcp_sessions,
11
- :oauth_refresh_token)
12
- add_column :action_mcp_sessions, :oauth_token_expires_at, :datetime unless column_exists?(:action_mcp_sessions,
13
- :oauth_token_expires_at)
14
- add_column :action_mcp_sessions, :oauth_user_context, json_type unless column_exists?(:action_mcp_sessions,
15
- :oauth_user_context)
16
- add_column :action_mcp_sessions, :authentication_method, :string, default: 'none' unless column_exists?(
17
- :action_mcp_sessions, :authentication_method
18
- )
19
-
20
- # Add indexes for performance
21
- add_index :action_mcp_sessions, :oauth_access_token, unique: true unless index_exists?(:action_mcp_sessions,
22
- :oauth_access_token)
23
- add_index :action_mcp_sessions, :oauth_token_expires_at unless index_exists?(:action_mcp_sessions,
24
- :oauth_token_expires_at)
25
- add_index :action_mcp_sessions, :authentication_method unless index_exists?(:action_mcp_sessions,
26
- :authentication_method)
27
- end
28
- end
@@ -1,44 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class CreateActionMCPOAuthClients < ActiveRecord::Migration[7.2]
4
- def change
5
- create_table :action_mcp_oauth_clients do |t|
6
- t.string :client_id, null: false, index: { unique: true }
7
- t.string :client_secret
8
- t.string :client_name
9
-
10
- # Store arrays as JSON for database compatibility
11
- if connection.adapter_name.downcase.include?('postgresql')
12
- t.text :redirect_uris, array: true, default: []
13
- t.text :grant_types, array: true, default: [ 'authorization_code' ]
14
- t.text :response_types, array: true, default: [ 'code' ]
15
- else
16
- # For SQLite and other databases, use JSON
17
- t.json :redirect_uris, default: []
18
- t.json :grant_types, default: [ 'authorization_code' ]
19
- t.json :response_types, default: [ 'code' ]
20
- end
21
-
22
- t.string :token_endpoint_auth_method, default: 'client_secret_basic'
23
- t.text :scope
24
- t.boolean :active, default: true
25
-
26
- # Registration metadata
27
- t.integer :client_id_issued_at
28
- t.integer :client_secret_expires_at
29
- t.string :registration_access_token # OAuth 2.1 Dynamic Client Registration
30
-
31
- # Additional metadata as JSON for database compatibility
32
- if connection.adapter_name.downcase.include?('postgresql')
33
- t.jsonb :metadata, default: {}
34
- else
35
- t.json :metadata, default: {}
36
- end
37
-
38
- t.timestamps
39
- end
40
-
41
- add_index :action_mcp_oauth_clients, :active
42
- add_index :action_mcp_oauth_clients, :client_id_issued_at
43
- end
44
- end
@@ -1,39 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class CreateActionMCPOAuthTokens < ActiveRecord::Migration[7.2]
4
- def change
5
- create_table :action_mcp_oauth_tokens do |t|
6
- t.string :token, null: false, index: { unique: true }
7
- t.string :token_type, null: false # 'access_token', 'refresh_token', 'authorization_code'
8
- t.string :client_id, null: false
9
- t.string :user_id
10
- t.text :scope
11
- t.datetime :expires_at
12
- t.boolean :revoked, default: false
13
-
14
- # For authorization codes
15
- t.string :redirect_uri
16
- t.string :code_challenge
17
- t.string :code_challenge_method
18
-
19
- # For refresh tokens
20
- t.string :access_token # Reference to associated access token
21
-
22
- # Additional data - use JSON for database compatibility
23
- if connection.adapter_name.downcase.include?('postgresql')
24
- t.jsonb :metadata, default: {}
25
- else
26
- t.json :metadata, default: {}
27
- end
28
-
29
- t.timestamps
30
- end
31
-
32
- add_index :action_mcp_oauth_tokens, :token_type
33
- add_index :action_mcp_oauth_tokens, :client_id
34
- add_index :action_mcp_oauth_tokens, :user_id
35
- add_index :action_mcp_oauth_tokens, :expires_at
36
- add_index :action_mcp_oauth_tokens, :revoked
37
- add_index :action_mcp_oauth_tokens, %i[token_type expires_at]
38
- end
39
- end