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.
data/README.md ADDED
@@ -0,0 +1,869 @@
1
+ # MCP Auth
2
+
3
+ OAuth 2.1 authorization for Model Context Protocol (MCP) servers in Rails applications.
4
+
5
+ ## What is MCP Authorization?
6
+
7
+ The Model Context Protocol (MCP) is an open standard that enables AI assistants to securely connect to external data sources and tools. MCP Auth implements the [MCP Authorization specification](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization), providing OAuth 2.1-based authentication for MCP servers.
8
+
9
+ ### Why OAuth for MCP?
10
+
11
+ MCP servers often need to access user data or perform actions on behalf of users. OAuth 2.1 provides:
12
+
13
+ - **Delegated Authorization**: Users can grant MCP clients access without sharing credentials
14
+ - **Scope-Based Permissions**: Fine-grained control over what data/actions are allowed
15
+ - **Token-Based Security**: Short-lived access tokens with automatic refresh
16
+ - **PKCE Protection**: Enhanced security for public clients (AI assistants)
17
+
18
+ ## How MCP Auth Works
19
+
20
+ ### Architecture Overview
21
+
22
+ ```
23
+ ┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
24
+ │ │ │ │ │ │
25
+ │ MCP Client │────────▶│ Your Rails App │◀────────│ End User │
26
+ │ (AI Assistant) │ │ (MCP Server + │ │ (Browser) │
27
+ │ │ │ OAuth Server) │ │ │
28
+ └─────────────────┘ └──────────────────┘ └─────────────────┘
29
+ │ │ │
30
+ │ │ │
31
+ │ 1. Discover OAuth Server │ │
32
+ │────────────────────────────▶│ │
33
+ │ │ │
34
+ │ 2. Request Authorization │ │
35
+ │────────────────────────────▶│ │
36
+ │ │ │
37
+ │ │ 3. Show Consent Screen │
38
+ │ │────────────────────────────▶│
39
+ │ │ │
40
+ │ │ 4. User Approves │
41
+ │ │◀────────────────────────────│
42
+ │ │ │
43
+ │ 5. Receive Auth Code │ │
44
+ │◀────────────────────────────│ │
45
+ │ │ │
46
+ │ 6. Exchange for Token │ │
47
+ │────────────────────────────▶│ │
48
+ │ │ │
49
+ │ 7. Access MCP Server │ │
50
+ │────────────────────────────▶│ │
51
+ ```
52
+
53
+ ### OAuth 2.1 Flow Sequence
54
+
55
+ ```mermaid
56
+ sequenceDiagram
57
+ participant Client as MCP Client
58
+ participant Server as MCP Server
59
+ participant AuthServer as OAuth Server
60
+ participant User as End User
61
+
62
+ Note over Client,User: Discovery Phase
63
+ Client->>Server: Request without token
64
+ Server-->>Client: 401 + WWW-Authenticate header
65
+ Client->>Server: GET /.well-known/oauth-protected-resource
66
+ Server-->>Client: Protected Resource Metadata
67
+ Client->>AuthServer: GET /.well-known/oauth-authorization-server
68
+ AuthServer-->>Client: OAuth Server Metadata
69
+
70
+ Note over Client,User: Authorization Phase
71
+ Client->>Client: Generate PKCE challenge
72
+ Client->>User: Open browser with authorization URL
73
+ User->>AuthServer: Authorization request (+ PKCE challenge)
74
+ AuthServer->>User: Show consent screen
75
+ User->>AuthServer: Approve access
76
+ AuthServer-->>User: Redirect with authorization code
77
+ User->>Client: Return authorization code
78
+
79
+ Note over Client,User: Token Exchange
80
+ Client->>AuthServer: Exchange code for token (+ PKCE verifier)
81
+ AuthServer->>AuthServer: Validate PKCE
82
+ AuthServer-->>Client: Access token + Refresh token
83
+
84
+ Note over Client,User: Access MCP Resources
85
+ Client->>Server: MCP request + Bearer token
86
+ Server->>Server: Validate token audience
87
+ Server-->>Client: MCP response
88
+ ```
89
+
90
+ ### Key Security Features
91
+
92
+ 1. **PKCE (Proof Key for Code Exchange)**: Required for all authorization flows
93
+ - Prevents authorization code interception
94
+ - Secure for public clients (AI assistants)
95
+
96
+ 2. **Resource Indicators (RFC 8707)**: Token audience binding
97
+ - Tokens are explicitly bound to your MCP server
98
+ - Prevents token misuse across different services
99
+
100
+ 3. **Token Rotation**: OAuth 2.1 requirement
101
+ - Refresh tokens are rotated on each use
102
+ - Limits impact of token theft
103
+
104
+ 4. **Short-Lived Access Tokens**: Default 1 hour lifetime
105
+ - Reduces security window for stolen tokens
106
+ - Automatic refresh via refresh tokens
107
+
108
+ ## Features
109
+
110
+ - ✅ **OAuth 2.1 Compliant** - Latest draft specification
111
+ - ✅ **PKCE Required** - S256 method for enhanced security
112
+ - ✅ **Dynamic Client Registration** - RFC 7591 support
113
+ - ✅ **Resource Indicators** - RFC 8707 for token audience binding
114
+ - ✅ **Protected Resource Metadata** - RFC 9728 for server discovery
115
+ - ✅ **Token Revocation** - RFC 7009 support
116
+ - ✅ **Token Introspection** - RFC 7662 support
117
+ - ✅ **OpenID Connect** - Basic OIDC Discovery support
118
+ - ✅ **Refresh Token Rotation** - OAuth 2.1 security requirement
119
+ - ✅ **Fully Configurable** - Paths, URLs, lifetimes, and user data
120
+ - ✅ **Beautiful Consent Screen** - Customizable UI
121
+
122
+ ## Installation
123
+
124
+ ```bash
125
+ # Add to Gemfile
126
+ gem 'mcp-auth'
127
+
128
+ # Install
129
+ bundle install
130
+
131
+ # Generate migrations and config
132
+ rails generate mcp:auth:install
133
+
134
+ # Run migrations
135
+ rails db:migrate
136
+ ```
137
+
138
+ ## Quick Start
139
+
140
+ ### 1. Mount Routes
141
+
142
+ **CRITICAL**: Mount at the TOP of `config/routes.rb`:
143
+
144
+ ```ruby
145
+ Rails.application.routes.draw do
146
+ # Mount MCP Auth FIRST - before any catch-all routes
147
+ mount Mcp::Auth::Engine => '/'
148
+
149
+ # Then your other routes
150
+ devise_for :users
151
+ root to: 'dashboard#index'
152
+
153
+ # ... rest of your routes
154
+ end
155
+ ```
156
+
157
+ ⚠️ **Why at the top?** The gem's routes (like `/.well-known/oauth-*` and `/oauth/*`) need to be registered before any catch-all routes or they'll be intercepted by your app's routing.
158
+
159
+ ### 2. Configure MCP Auth
160
+
161
+ Edit `config/initializers/mcp_auth.rb`:
162
+
163
+ ```ruby
164
+ Mcp::Auth.configure do |config|
165
+ # OAuth secret for JWT signing
166
+ config.oauth_secret = ENV.fetch('MCP_HMAC_SECRET', Rails.application.secret_key_base)
167
+
168
+ # Authorization server URL (optional - defaults to same as resource server)
169
+ config.authorization_server_url = ENV.fetch('MCP_AUTHORIZATION_SERVER_URL', nil)
170
+
171
+ # MCP Server Path - where your MCP server is mounted
172
+ # Change this if your MCP server is NOT at '/mcp'
173
+ config.mcp_server_path = ENV.fetch('MCP_SERVER_PATH', '/mcp')
174
+
175
+ # MCP Documentation URL (optional)
176
+ # Default: {mcp_server_path}/docs (e.g., /mcp/docs)
177
+ # Can be a path or full URL:
178
+ config.mcp_docs_url = ENV.fetch('MCP_DOCS_URL', nil)
179
+ # Examples:
180
+ # config.mcp_docs_url = '/docs/mcp-api'
181
+ # config.mcp_docs_url = 'https://docs.example.com/mcp'
182
+
183
+ # Token lifetimes (in seconds)
184
+ config.access_token_lifetime = 3600 # 1 hour
185
+ config.refresh_token_lifetime = 2_592_000 # 30 days
186
+ config.authorization_code_lifetime = 1800 # 30 minutes
187
+
188
+ # User data fetcher - CUSTOMIZE THIS
189
+ config.fetch_user_data = proc do |data|
190
+ user = User.find(data[:user_id])
191
+ org = Org.find(data[:org_id]) if data[:org_id]
192
+
193
+ # Return user data + API key (if you have one)
194
+ {
195
+ email: user.email,
196
+ api_key_id: org&.api_key&.id,
197
+ api_key_secret: org&.api_key&.secret
198
+ }
199
+ rescue ActiveRecord::RecordNotFound
200
+ { email: 'unknown@example.com', api_key_id: nil, api_key_secret: nil }
201
+ end
202
+
203
+ # Methods for authentication
204
+ config.current_user_method = :current_user
205
+ config.current_org_method = :current_org
206
+ end
207
+ ```
208
+
209
+ ### 3. Ensure Authentication Methods
210
+
211
+ Your `ApplicationController` should have these methods:
212
+
213
+ ```ruby
214
+ class ApplicationController < ActionController::Base
215
+ # For Devise users, these are already defined
216
+ # For custom auth, implement these methods:
217
+
218
+ def current_user
219
+ # Your logic to get the currently logged-in user
220
+ @current_user ||= User.find_by(id: session[:user_id])
221
+ end
222
+
223
+ def current_org
224
+ # Your logic to get the current organization (if applicable)
225
+ @current_org ||= current_user&.current_org
226
+ end
227
+ end
228
+ ```
229
+
230
+ ### 4. Set Environment Variables
231
+
232
+ ```bash
233
+ # .env
234
+ MCP_HMAC_SECRET=your_secure_random_string_here
235
+ MCP_SERVER_PATH=/mcp # or /api/mcp, /assistant/api, etc.
236
+ MCP_DOCS_URL=/docs/mcp # optional
237
+ ```
238
+
239
+ ### 5. Restart Server
240
+
241
+ ```bash
242
+ spring stop # Clear spring cache
243
+ rails server
244
+ ```
245
+
246
+ ## OAuth Endpoints (Automatic)
247
+
248
+ MCP Auth provides these endpoints automatically:
249
+
250
+ ### Discovery Endpoints
251
+
252
+ - `GET /.well-known/oauth-protected-resource` - RFC 9728 Protected Resource Metadata
253
+ - `GET /.well-known/oauth-authorization-server` - RFC 8414 Authorization Server Metadata
254
+ - `GET /.well-known/openid-configuration` - OpenID Connect Discovery
255
+ - `GET /.well-known/jwks.json` - JSON Web Key Set (empty for HMAC)
256
+
257
+ ### OAuth Flow Endpoints
258
+
259
+ - `GET/POST /oauth/authorize` - Authorization endpoint (PKCE required)
260
+ - `POST /oauth/approve` - Consent approval endpoint
261
+ - `POST /oauth/token` - Token endpoint (authorization_code, refresh_token)
262
+ - `POST /oauth/register` - Dynamic client registration (RFC 7591)
263
+ - `POST /oauth/revoke` - Token revocation (RFC 7009)
264
+ - `POST /oauth/introspect` - Token introspection (RFC 7662)
265
+ - `GET /oauth/userinfo` - OpenID Connect UserInfo endpoint
266
+
267
+ ## Usage
268
+
269
+ ### Protecting Your MCP Server
270
+
271
+ MCP Auth automatically protects routes matching your configured `mcp_server_path`:
272
+
273
+ ```ruby
274
+ # If mcp_server_path = '/mcp'
275
+ # All routes starting with /mcp/* require OAuth tokens
276
+
277
+ GET /mcp/tools # Protected ✅
278
+ GET /mcp/resources # Protected ✅
279
+ GET /mcp/prompts # Protected ✅
280
+ GET /other/endpoint # Not protected ❌
281
+ ```
282
+
283
+ ### OAuth 2.1 Authorization Flow
284
+
285
+ #### 1. Register a Client (Dynamic Registration)
286
+
287
+ ```bash
288
+ curl -X POST http://localhost:3000/oauth/register \
289
+ -H "Content-Type: application/json" \
290
+ -d '{
291
+ "client_name": "My MCP Client",
292
+ "redirect_uris": ["https://client.example.com/callback"],
293
+ "grant_types": ["authorization_code", "refresh_token"],
294
+ "response_types": ["code"],
295
+ "scope": "mcp:read mcp:write"
296
+ }'
297
+ ```
298
+
299
+ Response:
300
+ ```json
301
+ {
302
+ "client_id": "550e8400-e29b-41d4-a716-446655440000",
303
+ "client_secret": "a1b2c3d4...",
304
+ "client_id_issued_at": 1234567890,
305
+ "client_secret_expires_at": 0,
306
+ "redirect_uris": ["https://client.example.com/callback"],
307
+ "grant_types": ["authorization_code", "refresh_token"],
308
+ "response_types": ["code"],
309
+ "scope": "mcp:read mcp:write"
310
+ }
311
+ ```
312
+
313
+ #### 2. Authorization Request with PKCE
314
+
315
+ Generate PKCE parameters:
316
+
317
+ ```javascript
318
+ // Generate code verifier (43-128 characters)
319
+ const codeVerifier = base64URLEncode(randomBytes(32));
320
+
321
+ // Generate code challenge
322
+ const codeChallenge = base64URLEncode(
323
+ sha256(codeVerifier)
324
+ );
325
+ ```
326
+
327
+ Redirect user to authorization URL:
328
+
329
+ ```
330
+ GET /oauth/authorize?
331
+ response_type=code&
332
+ client_id=550e8400-e29b-41d4-a716-446655440000&
333
+ redirect_uri=https://client.example.com/callback&
334
+ scope=mcp:read+mcp:write&
335
+ state=random_state_string&
336
+ code_challenge=CODE_CHALLENGE&
337
+ code_challenge_method=S256&
338
+ resource=https://example.com/mcp
339
+ ```
340
+
341
+ User will see consent screen and approve/deny access.
342
+
343
+ #### 3. Token Exchange
344
+
345
+ After user approves, exchange authorization code for tokens:
346
+
347
+ ```bash
348
+ curl -X POST https://example.com/oauth/token \
349
+ -H "Content-Type: application/x-www-form-urlencoded" \
350
+ -d "grant_type=authorization_code" \
351
+ -d "code=AUTHORIZATION_CODE" \
352
+ -d "redirect_uri=https://client.example.com/callback" \
353
+ -d "code_verifier=CODE_VERIFIER" \
354
+ -d "client_id=550e8400-e29b-41d4-a716-446655440000" \
355
+ -d "resource=https://example.com/mcp"
356
+ ```
357
+
358
+ Response:
359
+ ```json
360
+ {
361
+ "access_token": "eyJhbGciOiJIUzI1NiIs...",
362
+ "token_type": "Bearer",
363
+ "expires_in": 3600,
364
+ "refresh_token": "a1b2c3d4e5f6...",
365
+ "scope": "mcp:read mcp:write"
366
+ }
367
+ ```
368
+
369
+ #### 4. Access Protected Resources
370
+
371
+ ```bash
372
+ curl https://example.com/mcp/tools \
373
+ -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."
374
+ ```
375
+
376
+ #### 5. Refresh Token
377
+
378
+ When access token expires:
379
+
380
+ ```bash
381
+ curl -X POST https://example.com/oauth/token \
382
+ -H "Content-Type: application/x-www-form-urlencoded" \
383
+ -d "grant_type=refresh_token" \
384
+ -d "refresh_token=a1b2c3d4e5f6..." \
385
+ -d "client_id=550e8400-e29b-41d4-a716-446655440000"
386
+ ```
387
+
388
+ ### Helper Methods in Controllers
389
+
390
+ Access authentication data in your controllers:
391
+
392
+ ```ruby
393
+ class MyController < ApplicationController
394
+ def index
395
+ if mcp_authenticated?
396
+ user_id = mcp_user_id # User ID from token
397
+ org_id = mcp_org_id # Org ID from token
398
+ email = mcp_email # Email from token
399
+ scope = mcp_scope # Token scopes
400
+ api_key = mcp_api_key # API key if configured
401
+
402
+ # Your logic here
403
+ render json: { user_id: user_id, email: email }
404
+ else
405
+ render json: { error: 'Unauthorized' }, status: :unauthorized
406
+ end
407
+ end
408
+ end
409
+ ```
410
+
411
+ Available helper methods:
412
+
413
+ - `mcp_authenticated?` - Returns true if request has valid token
414
+ - `mcp_user_id` - User ID from token
415
+ - `mcp_org_id` - Organization ID from token
416
+ - `mcp_email` - User email from token
417
+ - `mcp_scope` - Token scopes (space-separated string)
418
+ - `mcp_token` - The access token itself
419
+ - `mcp_api_key` - API key if configured in `fetch_user_data`
420
+
421
+ ## Advanced Configuration
422
+
423
+ ### Custom MCP Server Paths
424
+
425
+ If your MCP server is mounted at a different path:
426
+
427
+ ```ruby
428
+ # config/initializers/mcp_auth.rb
429
+ config.mcp_server_path = '/api/v1/assistant' # Custom path
430
+
431
+ # Your MCP server configuration
432
+ FastMcp.mount_in_rails(
433
+ Rails.application,
434
+ path_prefix: '/api/v1/assistant' # Must match mcp_server_path
435
+ )
436
+ ```
437
+
438
+ ### Custom Documentation URL
439
+
440
+ Point to your API documentation:
441
+
442
+ ```ruby
443
+ # Path-based (will be prefixed with your domain)
444
+ config.mcp_docs_url = '/docs/mcp-api'
445
+
446
+ # Full URL to external docs
447
+ config.mcp_docs_url = 'https://docs.example.com/mcp-api'
448
+
449
+ # Default (if not set): {mcp_server_path}/docs
450
+ # Example: /mcp/docs
451
+ ```
452
+
453
+ ### Custom Consent Screen
454
+
455
+ Customize the OAuth consent screen to match your branding:
456
+
457
+ ```ruby
458
+ # 1. Enable custom consent view in config/initializers/mcp_auth.rb
459
+ config.use_custom_consent_view = true
460
+
461
+ # 2. Edit app/views/mcp/auth/consent.html.erb
462
+ ```
463
+
464
+ Available variables in the view:
465
+
466
+ - `@client_name` - Name of the OAuth client requesting access
467
+ - `@requested_scopes` - Array of human-readable scope descriptions
468
+ - `@authorization_params` - OAuth parameters (client_id, redirect_uri, etc.)
469
+
470
+ Example custom consent view:
471
+
472
+ ```erb
473
+ <!DOCTYPE html>
474
+ <html>
475
+ <head>
476
+ <title>Authorization Request</title>
477
+ <style>
478
+ /* Your custom styles */
479
+ </style>
480
+ </head>
481
+ <body>
482
+ <div class="consent-container">
483
+ <h1><%= @client_name %> wants to access your account</h1>
484
+
485
+ <p>This application is requesting permission to:</p>
486
+ <ul>
487
+ <% @requested_scopes.each do |scope| %>
488
+ <li><%= scope %></li>
489
+ <% end %>
490
+ </ul>
491
+
492
+ <%= form_tag oauth_approve_path, method: :post do %>
493
+ <% @authorization_params.each do |key, value| %>
494
+ <%= hidden_field_tag key, value %>
495
+ <% end %>
496
+
497
+ <%= hidden_field_tag :approved, true %>
498
+ <%= submit_tag "Allow Access", class: "btn-primary" %>
499
+ <% end %>
500
+
501
+ <%= form_tag oauth_approve_path, method: :post do %>
502
+ <% @authorization_params.each do |key, value| %>
503
+ <%= hidden_field_tag key, value %>
504
+ <% end %>
505
+
506
+ <%= hidden_field_tag :approved, false %>
507
+ <%= submit_tag "Deny", class: "btn-secondary" %>
508
+ <% end %>
509
+ </div>
510
+ </body>
511
+ </html>
512
+ ```
513
+
514
+ ### Separate Authorization Server
515
+
516
+ If you want to use a separate authorization server:
517
+
518
+ ```ruby
519
+ # config/initializers/mcp_auth.rb
520
+ config.authorization_server_url = 'https://auth.example.com'
521
+ ```
522
+
523
+ This is useful for:
524
+ - Microservices architecture
525
+ - Multiple resource servers sharing one auth server
526
+ - Enterprise SSO integration
527
+
528
+ ## Testing
529
+
530
+ ### Test the Installation
531
+
532
+ ```bash
533
+ # Start your server
534
+ rails server
535
+
536
+ # Test discovery endpoints
537
+ curl http://localhost:3000/.well-known/oauth-protected-resource
538
+ curl http://localhost:3000/.well-known/oauth-authorization-server
539
+
540
+ # Register a test client
541
+ curl -X POST http://localhost:3000/oauth/register \
542
+ -H "Content-Type: application/json" \
543
+ -d '{
544
+ "client_name": "Test Client",
545
+ "redirect_uris": ["http://localhost:3000/callback"]
546
+ }'
547
+ ```
548
+
549
+ ### In Your Test Suite
550
+
551
+ Create tokens directly in tests:
552
+
553
+ ```ruby
554
+ RSpec.describe 'MCP API', type: :request do
555
+ let(:user) { create(:user) }
556
+ let(:org) { create(:org) }
557
+
558
+ let(:access_token) do
559
+ token_data = {
560
+ client_id: 'test-client',
561
+ scope: 'mcp:read mcp:write',
562
+ user_id: user.id,
563
+ org_id: org.id,
564
+ resource: 'http://localhost:3000/mcp'
565
+ }
566
+
567
+ Mcp::Auth::Services::TokenService.generate_access_token(
568
+ token_data,
569
+ base_url: 'http://localhost:3000'
570
+ )
571
+ end
572
+
573
+ it 'allows authenticated requests' do
574
+ get '/mcp/tools', headers: {
575
+ 'Authorization' => "Bearer #{access_token}"
576
+ }
577
+
578
+ expect(response).to have_http_status(:success)
579
+ end
580
+
581
+ it 'rejects unauthenticated requests' do
582
+ get '/mcp/tools'
583
+
584
+ expect(response).to have_http_status(:unauthorized)
585
+ end
586
+ end
587
+ ```
588
+
589
+ ## Rake Tasks
590
+
591
+ MCP Auth includes helpful maintenance tasks:
592
+
593
+ ```bash
594
+ # Clean up expired tokens and codes (run daily)
595
+ rake mcp_auth:cleanup
596
+
597
+ # Show token statistics
598
+ rake mcp_auth:stats
599
+
600
+ # Revoke all tokens for a specific client
601
+ rake mcp_auth:revoke_client_tokens[CLIENT_ID]
602
+
603
+ # Revoke all tokens for a specific user
604
+ rake mcp_auth:revoke_user_tokens[USER_ID]
605
+ ```
606
+
607
+ ### Scheduled Cleanup
608
+
609
+ Add to your scheduler (cron, whenever, sidekiq-cron):
610
+
611
+ ```ruby
612
+ # config/schedule.rb (whenever gem)
613
+ every 1.day, at: '3:00 am' do
614
+ rake 'mcp_auth:cleanup'
615
+ end
616
+ ```
617
+
618
+ ## Security Best Practices
619
+
620
+ ### 1. Use Strong Secrets
621
+
622
+ ```bash
623
+ # Generate a strong secret
624
+ rails secret
625
+
626
+ # Set in environment
627
+ export MCP_HMAC_SECRET="your-256-bit-secret-here"
628
+ ```
629
+
630
+ ### 2. Always Use HTTPS in Production
631
+
632
+ ```ruby
633
+ # config/environments/production.rb
634
+ config.force_ssl = true
635
+ ```
636
+
637
+ MCP Auth automatically enforces HTTPS for OAuth endpoints in production.
638
+
639
+ ### 3. Keep Token Lifetimes Short
640
+
641
+ ```ruby
642
+ # Recommended settings
643
+ config.access_token_lifetime = 3600 # 1 hour
644
+ config.refresh_token_lifetime = 2_592_000 # 30 days
645
+ config.authorization_code_lifetime = 1800 # 30 minutes
646
+ ```
647
+
648
+ ### 4. Validate Redirect URIs
649
+
650
+ Only register trusted redirect URIs for your OAuth clients. MCP Auth validates exact URI matches.
651
+
652
+ ### 5. Monitor Failed Authentications
653
+
654
+ Check logs regularly for suspicious activity:
655
+
656
+ ```bash
657
+ grep "Token validation failed" log/production.log
658
+ grep "Authorization code is invalid" log/production.log
659
+ ```
660
+
661
+ ### 6. Implement Rate Limiting
662
+
663
+ Use rack-attack or similar to prevent brute force attacks:
664
+
665
+ ```ruby
666
+ # config/initializers/rack_attack.rb
667
+ Rack::Attack.throttle('oauth/token', limit: 5, period: 1.minute) do |req|
668
+ req.ip if req.path == '/oauth/token' && req.post?
669
+ end
670
+ ```
671
+
672
+ ### 7. Regular Token Cleanup
673
+
674
+ Run cleanup task daily to remove expired tokens:
675
+
676
+ ```bash
677
+ rake mcp_auth:cleanup
678
+ ```
679
+
680
+ ## Troubleshooting
681
+
682
+ ### Routes Return 404
683
+
684
+ **Problem**: OAuth endpoints return 404 or redirect to login
685
+
686
+ **Solution**: Ensure `mount Mcp::Auth::Engine => '/'` is at the very top of `config/routes.rb`, before any other routes (especially catch-all routes or Devise).
687
+
688
+ ```ruby
689
+ Rails.application.routes.draw do
690
+ # THIS MUST BE FIRST!
691
+ mount Mcp::Auth::Engine => '/'
692
+
693
+ # Then other routes...
694
+ devise_for :users
695
+ # ...
696
+ end
697
+ ```
698
+
699
+ ### "Invalid audience" Errors
700
+
701
+ **Problem**: Token validation fails with audience mismatch
702
+
703
+ **Solutions**:
704
+ - Check `config.mcp_server_path` matches your actual MCP server mount point
705
+ - Verify the `resource` parameter in OAuth requests matches the configured path
706
+ - Ensure tokens are being generated with the correct resource URL
707
+
708
+ ```ruby
709
+ # config/initializers/mcp_auth.rb
710
+ config.mcp_server_path = '/mcp' # Must match FastMCP mount point
711
+
712
+ # When generating tokens
713
+ resource: 'https://example.com/mcp' # Must match mcp_server_path
714
+ ```
715
+
716
+ ### Token Validation Fails
717
+
718
+ **Problem**: Valid-looking tokens are rejected
719
+
720
+ **Solutions**:
721
+ - Check `oauth_secret` is set correctly and consistently
722
+ - Ensure server clocks are synchronized (JWT exp validation is time-sensitive)
723
+ - Verify token hasn't expired (check `exp` claim)
724
+ - Check token audience matches your MCP server
725
+
726
+ ```bash
727
+ # Decode JWT to inspect claims (without verification)
728
+ ruby -rjwt -e "puts JWT.decode('YOUR_TOKEN', nil, false).inspect"
729
+ ```
730
+
731
+ ### "Missing template" Errors
732
+
733
+ **Problem**: Missing template layouts/mcp_auth or consent view errors
734
+
735
+ **Solution**: Run the generator to create views:
736
+
737
+ ```bash
738
+ rails generate mcp:auth:install
739
+ ```
740
+
741
+ ### PKCE Validation Fails
742
+
743
+ **Problem**: "PKCE validation failed" error during token exchange
744
+
745
+ **Solutions**:
746
+ - Ensure `code_verifier` exactly matches what was used to generate `code_challenge`
747
+ - Verify code_challenge_method is 'S256'
748
+ - Check code_verifier is 43-128 characters long
749
+ - Ensure proper base64url encoding (no padding)
750
+
751
+ ### Current User Not Found
752
+
753
+ **Problem**: `undefined method 'current_user'` errors
754
+
755
+ **Solution**: Ensure your ApplicationController defines these methods:
756
+
757
+ ```ruby
758
+ class ApplicationController < ActionController::Base
759
+ def current_user
760
+ # Your authentication logic
761
+ end
762
+
763
+ def current_org
764
+ # Your organization logic (optional)
765
+ end
766
+ end
767
+ ```
768
+
769
+ Or configure different method names:
770
+
771
+ ```ruby
772
+ config.current_user_method = :authenticated_user
773
+ config.current_org_method = :active_organization
774
+ ```
775
+
776
+ ## Standards Compliance
777
+
778
+ MCP Auth implements the following specifications:
779
+
780
+ - [OAuth 2.1](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13) - Core authorization framework
781
+ - [RFC 7591](https://datatracker.ietf.org/doc/html/rfc7591) - Dynamic Client Registration Protocol
782
+ - [RFC 7636](https://datatracker.ietf.org/doc/html/rfc7636) - Proof Key for Code Exchange (PKCE)
783
+ - [RFC 7009](https://datatracker.ietf.org/doc/html/rfc7009) - Token Revocation
784
+ - [RFC 7662](https://datatracker.ietf.org/doc/html/rfc7662) - Token Introspection
785
+ - [RFC 8414](https://datatracker.ietf.org/doc/html/rfc8414) - Authorization Server Metadata
786
+ - [RFC 8707](https://datatracker.ietf.org/doc/html/rfc8707) - Resource Indicators for OAuth 2.0
787
+ - [RFC 9728](https://datatracker.ietf.org/doc/html/rfc9728) - OAuth 2.0 Protected Resource Metadata
788
+ - [MCP Authorization Spec](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization) - Model Context Protocol Authorization
789
+
790
+ ## Development
791
+
792
+ ### Setting Up Development Environment
793
+
794
+ ```bash
795
+ # Clone the repository
796
+ git clone https://github.com/SerhiiBorozenets/mcp-auth.git
797
+ cd mcp-auth
798
+
799
+ # Install dependencies
800
+ bundle install
801
+
802
+ # Run tests
803
+ bundle exec rspec
804
+
805
+ # Check code coverage
806
+ open coverage/index.html
807
+ ```
808
+
809
+ ### Running Tests
810
+
811
+ ```bash
812
+ # Run all tests
813
+ bundle exec rspec
814
+
815
+ # Run specific test file
816
+ bundle exec rspec spec/services/token_service_spec.rb
817
+
818
+ # Run with documentation format
819
+ bundle exec rspec --format documentation
820
+ ```
821
+
822
+ ### Code Quality
823
+
824
+ ```bash
825
+ # Run RuboCop
826
+ bundle exec rubocop
827
+
828
+ # Auto-correct issues
829
+ bundle exec rubocop -a
830
+ ```
831
+
832
+ ## Contributing
833
+
834
+ We welcome contributions! Here's how:
835
+
836
+ 1. Fork the repository
837
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
838
+ 3. Add tests for your changes
839
+ 4. Ensure all tests pass (`bundle exec rspec`)
840
+ 5. Commit your changes (`git commit -m 'Add amazing feature'`)
841
+ 6. Push to the branch (`git push origin feature/amazing-feature`)
842
+ 7. Open a Pull Request
843
+
844
+ Please ensure:
845
+ - All tests pass
846
+ - Code follows Ruby style guide (run `rubocop`)
847
+ - New features include tests and documentation
848
+ - Commits are well-described
849
+
850
+ ## License
851
+
852
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
853
+
854
+ ## Support
855
+
856
+ - **Issues**: https://github.com/SerhiiBorozenets/mcp-auth/issues
857
+ - **Documentation**: https://github.com/SerhiiBorozenets/mcp-auth
858
+ - **MCP Specification**: https://modelcontextprotocol.io
859
+ - **OAuth 2.1**: https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13
860
+
861
+ ## Changelog
862
+
863
+ See [CHANGELOG.md](CHANGELOG.md) for version history and changes.
864
+
865
+ ## Credits
866
+
867
+ Created by Serhii Borozenets
868
+
869
+ Built with ❤️ for the Model Context Protocol community.