mcp-auth 0.1.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.
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators'
4
+ require 'rails/generators/active_record'
5
+
6
+ module Mcp
7
+ module Auth
8
+ module Generators
9
+ class InstallGenerator < Rails::Generators::Base
10
+ include ActiveRecord::Generators::Migration
11
+
12
+ source_root File.expand_path('templates', __dir__)
13
+
14
+ desc "Generates MCP Auth migrations, initializer, and views"
15
+
16
+ def copy_migrations
17
+ migration_template "create_oauth_clients.rb.erb",
18
+ "db/migrate/create_mcp_auth_oauth_clients.rb",
19
+ migration_version: migration_version
20
+
21
+ migration_template "create_authorization_codes.rb.erb",
22
+ "db/migrate/create_mcp_auth_authorization_codes.rb",
23
+ migration_version: migration_version
24
+
25
+ migration_template "create_access_tokens.rb.erb",
26
+ "db/migrate/create_mcp_auth_access_tokens.rb",
27
+ migration_version: migration_version
28
+
29
+ migration_template "create_refresh_tokens.rb.erb",
30
+ "db/migrate/create_mcp_auth_refresh_tokens.rb",
31
+ migration_version: migration_version
32
+ end
33
+
34
+ def copy_initializer
35
+ template "initializer.rb", "config/initializers/mcp_auth.rb"
36
+ end
37
+
38
+ def copy_views
39
+ # Create the directory first
40
+ empty_directory "app/views/mcp/auth"
41
+
42
+ # Copy consent view template
43
+ template "views/consent.html.erb", "app/views/mcp/auth/consent.html.erb"
44
+ end
45
+
46
+ def show_readme
47
+ if File.exist?(File.join(self.class.source_root, "README"))
48
+ readme "README"
49
+ end
50
+ rescue Thor::Error
51
+ # Skip silently
52
+ end
53
+
54
+ def show_post_install_message
55
+ say "\n" + "="*80
56
+ say "MCP Auth has been installed!", :green
57
+ say "="*80
58
+ say "\nFiles created:"
59
+ say " - db/migrate/*_create_mcp_auth_*.rb (4 migrations)"
60
+ say " - config/initializers/mcp_auth.rb"
61
+ say " - app/views/mcp/auth/consent.html.erb"
62
+ say "\nNext steps:"
63
+ say "1. Run migrations: rails db:migrate"
64
+ say "2. Configure: config/initializers/mcp_auth.rb"
65
+ say "3. Customize consent view: app/views/mcp/auth/consent.html.erb"
66
+ say "4. Mount routes in config/routes.rb (if not already done):"
67
+ say " mount Mcp::Auth::Engine => '/'"
68
+ say "\nDocumentation: https://github.com/SerhiiBorozenets/mcp-auth"
69
+ say "="*80 + "\n"
70
+ end
71
+
72
+ private
73
+
74
+ def migration_version
75
+ "[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,114 @@
1
+ ===============================================================================
2
+
3
+ MCP Auth has been installed!
4
+
5
+ Next steps:
6
+
7
+ 1. Run the migrations:
8
+
9
+ rails db:migrate
10
+
11
+ 2. Configure the initializer at config/initializers/mcp_auth.rb
12
+
13
+ Set your OAuth secret and customize user data fetching.
14
+
15
+ 3. Ensure your ApplicationController has authentication methods:
16
+
17
+ - current_user: returns the currently signed-in user
18
+ - current_org: returns the current organization (optional)
19
+ - user_signed_in?: returns true if user is signed in
20
+
21
+ 4. Set environment variables (optional):
22
+
23
+ MCP_HMAC_SECRET: Secret key for signing JWTs
24
+ MCP_AUTHORIZATION_SERVER_URL: Custom authorization server URL
25
+
26
+ 5. Your OAuth endpoints are now available at:
27
+
28
+ - /.well-known/oauth-protected-resource (Protected Resource Metadata)
29
+ - /.well-known/oauth-authorization-server (Authorization Server Metadata)
30
+ - /oauth/authorize (Authorization endpoint)
31
+ - /oauth/token (Token endpoint)
32
+ - /oauth/register (Dynamic client registration)
33
+ - /oauth/revoke (Token revocation)
34
+ - /oauth/introspect (Token introspection)
35
+
36
+ 6. Mount the routes in your config/routes.rb:
37
+
38
+ Rails.application.routes.draw do
39
+ # Mount MCP Auth routes at the top
40
+ mount Mcp::Auth::Engine => "/"
41
+
42
+ # ... your other routes
43
+ end
44
+
45
+ CUSTOMIZING THE CONSENT SCREEN
46
+ -------------------------------
47
+
48
+ The generator has created a default consent view at:
49
+ app/views/mcp/auth/consent.html.erb
50
+
51
+ To use your custom consent view:
52
+
53
+ 1. Edit config/initializers/mcp_auth.rb and set:
54
+ config.use_custom_consent_view = true
55
+
56
+ 2. Customize app/views/mcp/auth/consent.html.erb to match your branding
57
+
58
+ 3. Available instance variables in the view:
59
+ @client_name - Name of the OAuth client requesting access
60
+ @requested_scopes - Array of human-readable permission descriptions
61
+ @authorization_params - Hash containing OAuth flow parameters
62
+
63
+ 4. The form must POST to oauth_approve_path with:
64
+ - All @authorization_params as hidden fields
65
+ - A button with name="approved" value="true" for approval
66
+ - A button with name="approved" value="false" for denial
67
+
68
+ Example form:
69
+ <%= form_with url: oauth_approve_path, method: :post do |f| %>
70
+ <% @authorization_params.each do |key, value| %>
71
+ <%= f.hidden_field key, value: value, id: nil %>
72
+ <% end %>
73
+ <%= f.button 'Deny', name: 'approved', value: 'false' %>
74
+ <%= f.button 'Authorize', name: 'approved', value: 'true' %>
75
+ <% end %>
76
+
77
+ TESTING THE OAUTH FLOW
78
+ -----------------------
79
+
80
+ 1. Start your Rails server:
81
+ rails server
82
+
83
+ 2. Test the discovery endpoints:
84
+ curl http://localhost:3000/.well-known/oauth-protected-resource
85
+ curl http://localhost:3000/.well-known/oauth-authorization-server
86
+
87
+ 3. Register a test client:
88
+ curl -X POST http://localhost:3000/oauth/register \
89
+ -H "Content-Type: application/json" \
90
+ -d '{
91
+ "client_name": "Test Client",
92
+ "redirect_uris": ["http://localhost:3000/callback"]
93
+ }'
94
+
95
+ 4. Use the returned client_id and client_secret for OAuth flow
96
+
97
+ RAKE TASKS
98
+ ----------
99
+
100
+ Clean up expired tokens:
101
+ rake mcp_auth:cleanup
102
+
103
+ Show statistics:
104
+ rake mcp_auth:stats
105
+
106
+ Revoke tokens for a client:
107
+ rake mcp_auth:revoke_client_tokens[CLIENT_ID]
108
+
109
+ Revoke tokens for a user:
110
+ rake mcp_auth:revoke_user_tokens[USER_ID]
111
+
112
+ For more information, see: https://github.com/SerhiiBorozenets/mcp-auth
113
+
114
+ ===============================================================================
@@ -0,0 +1,23 @@
1
+ class CreateMcpAuthAccessTokens < ActiveRecord::Migration<%= migration_version %>
2
+ def up
3
+ create_table :mcp_auth_access_tokens do |t|
4
+ t.string :token, null: false, index: { unique: true }
5
+ t.string :client_id, null: false
6
+ t.string :resource
7
+ t.string :scope
8
+ t.references :user, foreign_key: true
9
+ t.references :org, foreign_key: true, null: true
10
+ t.datetime :expires_at, null: false
11
+ t.timestamps
12
+ end
13
+
14
+ add_index :mcp_auth_access_tokens, :client_id
15
+ add_index :mcp_auth_access_tokens, :expires_at
16
+ end
17
+
18
+ def down
19
+ remove_index :mcp_auth_access_tokens, :client_id
20
+ remove_index :mcp_auth_access_tokens, :expires_at
21
+ drop_table :mcp_auth_access_tokens
22
+ end
23
+ end
@@ -0,0 +1,26 @@
1
+ class CreateMcpAuthAuthorizationCodes < ActiveRecord::Migration<%= migration_version %>
2
+ def up
3
+ create_table :mcp_auth_authorization_codes do |t|
4
+ t.string :code, null: false, index: { unique: true }
5
+ t.string :client_id, null: false
6
+ t.string :redirect_uri, null: false
7
+ t.string :code_challenge
8
+ t.string :code_challenge_method
9
+ t.string :resource
10
+ t.string :scope
11
+ t.references :user, foreign_key: true
12
+ t.references :org, foreign_key: true, null: true
13
+ t.datetime :expires_at, null: false
14
+ t.timestamps
15
+ end
16
+
17
+ add_index :mcp_auth_authorization_codes, :client_id
18
+ add_index :mcp_auth_authorization_codes, :expires_at
19
+ end
20
+
21
+ def down
22
+ remove_index :mcp_auth_authorization_codes, :client_id
23
+ remove_index :mcp_auth_authorization_codes, :expires_at
24
+ drop_table :mcp_auth_authorization_codes
25
+ end
26
+ end
@@ -0,0 +1,22 @@
1
+ class CreateMcpAuthOauthClients < ActiveRecord::Migration<%= migration_version %>
2
+ def up
3
+ create_table :mcp_auth_oauth_clients, id: false do |t|
4
+ t.uuid :client_id, primary_key: true, null: false, default: -> { 'gen_random_uuid()' }
5
+ t.string :client_secret, null: false
6
+ t.text :redirect_uris
7
+ t.text :grant_types
8
+ t.text :response_types
9
+ t.string :scope
10
+ t.string :client_name
11
+ t.string :client_uri
12
+ t.timestamps
13
+ end
14
+
15
+ add_index :mcp_auth_oauth_clients, :client_id, unique: true
16
+ end
17
+
18
+ def down
19
+ remove_index :mcp_auth_oauth_clients, :client_id
20
+ drop_table :mcp_auth_oauth_clients
21
+ end
22
+ end
@@ -0,0 +1,22 @@
1
+ class CreateMcpAuthRefreshTokens < ActiveRecord::Migration<%= migration_version %>
2
+ def up
3
+ create_table :mcp_auth_refresh_tokens do |t|
4
+ t.string :token, null: false, index: { unique: true }
5
+ t.string :client_id, null: false
6
+ t.string :scope
7
+ t.references :user, foreign_key: true
8
+ t.references :org, foreign_key: true, null: true
9
+ t.datetime :expires_at, null: false
10
+ t.timestamps
11
+ end
12
+
13
+ add_index :mcp_auth_refresh_tokens, :client_id
14
+ add_index :mcp_auth_refresh_tokens, :expires_at
15
+ end
16
+
17
+ def down
18
+ remove_index :mcp_auth_refresh_tokens, :client_id
19
+ remove_index :mcp_auth_refresh_tokens, :expires_at
20
+ drop_table :mcp_auth_refresh_tokens
21
+ end
22
+ end
@@ -0,0 +1,199 @@
1
+ # frozen_string_literal: true
2
+
3
+ Mcp::Auth.configure do |config|
4
+ # ============================================================================
5
+ # OAUTH CONFIGURATION
6
+ # ============================================================================
7
+
8
+ # OAuth secret for signing JWTs
9
+ # Should be a secure random string in production (use: rails secret)
10
+ config.oauth_secret = ENV.fetch('MCP_HMAC_SECRET', Rails.application.secret_key_base)
11
+
12
+ # Authorization server URL (optional - defaults to same as resource server)
13
+ # Set this if you're using a separate authorization server
14
+ # Example: config.authorization_server_url = 'https://auth.example.com'
15
+ config.authorization_server_url = ENV.fetch('MCP_AUTHORIZATION_SERVER_URL', nil)
16
+
17
+ # ============================================================================
18
+ # MCP SERVER CONFIGURATION
19
+ # ============================================================================
20
+
21
+ # MCP Server Path - where your MCP server is mounted
22
+ # This MUST match the path where you mount FastMCP or your MCP server
23
+ # Default: '/mcp'
24
+ # Examples: '/api/mcp', '/v1/assistant', '/assistant/api'
25
+ config.mcp_server_path = ENV.fetch('MCP_SERVER_PATH', '/mcp')
26
+
27
+ # MCP Documentation URL - link to your MCP server documentation
28
+ # Can be a full URL (https://docs.example.com/mcp) or a path (/docs/mcp)
29
+ # Default: nil (will auto-generate as {mcp_server_path}/docs)
30
+ # Examples:
31
+ # config.mcp_docs_url = '/docs/mcp-api'
32
+ # config.mcp_docs_url = 'https://docs.example.com/mcp-api'
33
+ config.mcp_docs_url = ENV.fetch('MCP_DOCS_URL', nil)
34
+
35
+ # ============================================================================
36
+ # TOKEN LIFETIMES (in seconds)
37
+ # ============================================================================
38
+
39
+ config.access_token_lifetime = 3600 # 1 hour
40
+ config.refresh_token_lifetime = 2_592_000 # 30 days
41
+ config.authorization_code_lifetime = 1800 # 30 minutes
42
+
43
+ # ============================================================================
44
+ # USER DATA FETCHER
45
+ # ============================================================================
46
+
47
+ # This proc is called when generating tokens to fetch user-specific data
48
+ # Customize this based on your application's user and organization models
49
+ #
50
+ # Expected return value: Hash with keys:
51
+ # - :email (String) - User's email address
52
+ # - :api_key_id (String/Integer, optional) - API key ID if using API keys
53
+ # - :api_key_secret (String, optional) - API key secret if using API keys
54
+ config.fetch_user_data = proc do |data|
55
+ user = User.find(data[:user_id])
56
+
57
+ # Example: If you have API keys per organization user
58
+ # org_user = OrgUser.find_by(user_id: data[:user_id], org_id: data[:org_id])
59
+ # api_key = org_user&.api_key
60
+
61
+ {
62
+ email: user.email,
63
+ api_key_id: nil, # Set to your API key ID if applicable
64
+ api_key_secret: nil # Set to your API key secret if applicable
65
+ }
66
+ rescue ActiveRecord::RecordNotFound
67
+ { email: 'unknown@example.com', api_key_id: nil, api_key_secret: nil }
68
+ end
69
+
70
+ # ============================================================================
71
+ # AUTHENTICATION METHODS
72
+ # ============================================================================
73
+
74
+ # Methods used to get current user and organization in your controllers
75
+ # Change these if you use different method names (e.g., authenticated_user)
76
+ config.current_user_method = :current_user
77
+ config.current_org_method = :current_org
78
+
79
+ # ============================================================================
80
+ # SCOPE CONFIGURATION
81
+ # ============================================================================
82
+
83
+ # Register scopes that your application needs:
84
+ #
85
+ # Syntax:
86
+ # config.register_scope 'scope_key',
87
+ # name: 'Display Name',
88
+ # description: 'What this scope allows (shown to users)',
89
+ # required: false # Set to true if scope is always required
90
+ #
91
+ # IMPORTANT: At least one scope should be registered for OAuth to work properly.
92
+
93
+ # Basic read access - typically required
94
+ config.register_scope 'mcp:read',
95
+ name: 'Read Access',
96
+ description: 'Read your data and resources',
97
+ required: true # Usually required for MCP to function
98
+
99
+ # Write access - allows modifications
100
+ config.register_scope 'mcp:write',
101
+ name: 'Write Access',
102
+ description: 'Create and modify data on your behalf',
103
+ required: false
104
+
105
+ # Execute tools and automated actions
106
+ # config.register_scope 'mcp:tools',
107
+ # name: 'Execute Tools',
108
+ # description: 'Run tools and perform automated actions in your account'
109
+
110
+ # Analytics and reporting
111
+ # config.register_scope 'mcp:analytics',
112
+ # name: 'Analytics Access',
113
+ # description: 'View analytics dashboards, charts, and reports'
114
+
115
+ # Data export capabilities
116
+ # config.register_scope 'mcp:export',
117
+ # name: 'Data Export',
118
+ # description: 'Export data in CSV, PDF, and Excel formats'
119
+
120
+ # Administrative access
121
+ # config.register_scope 'mcp:admin',
122
+ # name: 'Administrative Access',
123
+ # description: 'Manage settings, users, and perform administrative actions',
124
+ # required: false
125
+
126
+ # Custom application-specific scopes
127
+ # config.register_scope 'mcp:orders',
128
+ # name: 'Order Management',
129
+ # description: 'View and manage customer orders'
130
+
131
+ # config.register_scope 'mcp:notifications',
132
+ # name: 'Send Notifications',
133
+ # description: 'Send notifications and messages on your behalf'
134
+
135
+ # ============================================================================
136
+ # SCOPE VALIDATION (OPTIONAL)
137
+ # ============================================================================
138
+
139
+ # Validate which scopes users can approve based on their roles/permissions
140
+ # This callback is called for each requested scope during authorization
141
+ #
142
+ # Parameters:
143
+ # - user: Current user object
144
+ # - org: Current organization object (may be nil)
145
+ # - scope: Scope being requested (String)
146
+ #
147
+ # Return:
148
+ # - true: User can approve this scope
149
+ # - false: User cannot approve this scope (will be filtered out)
150
+ #
151
+ # Example: Restrict admin scope to admin users only
152
+ # config.validate_scope_for_user = proc do |user, org, scope|
153
+ # case scope
154
+ # when 'mcp:admin'
155
+ # # Only admins can approve admin scope
156
+ # user.admin? || org&.admins&.include?(user)
157
+ # when 'mcp:analytics'
158
+ # # Check if user has analytics permission
159
+ # user.has_permission?(:view_analytics)
160
+ # when 'mcp:export'
161
+ # # Check if organization plan includes export
162
+ # org&.plan&.includes_export?
163
+ # else
164
+ # true # Allow all other scopes
165
+ # end
166
+ # end
167
+
168
+ # ============================================================================
169
+ # CUSTOM CONSENT VIEW (OPTIONAL)
170
+ # ============================================================================
171
+
172
+ # Set to true to use your own consent view instead of the gem's default
173
+ # The view will be at app/views/mcp/auth/consent.html.erb
174
+ config.use_custom_consent_view = false
175
+
176
+ # Path to custom consent view (relative to app/views)
177
+ # Only used if use_custom_consent_view is true
178
+ config.consent_view_path = 'mcp/auth/consent'
179
+
180
+ # To customize the consent screen:
181
+ # 1. Set use_custom_consent_view = true
182
+ # 2. Copy the default view from the gem or generate it:
183
+ # rails generate mcp:auth:install
184
+ # 3. Edit app/views/mcp/auth/consent.html.erb
185
+ # 4. The view has access to these instance variables:
186
+ # - @client_name: Name of the OAuth client requesting access
187
+ # - @requested_scopes: Array of scope hashes with keys:
188
+ # * :key - Scope identifier (e.g., 'mcp:read')
189
+ # * :name - Human-readable name (e.g., 'Read Access')
190
+ # * :description - What the scope allows
191
+ # * :required - Whether scope is required (true/false)
192
+ # * :pre_selected - Whether scope was in the original request
193
+ # - @authorization_params: Hash of OAuth parameters to preserve
194
+ end
195
+
196
+ # Include controller helpers in ApplicationController
197
+ Rails.application.config.to_prepare do
198
+ ApplicationController.include Mcp::Auth::ControllerHelpers
199
+ end