@@ -436,15 +436,15 @@ Use this section to track progress across sessions:
436436| 3. Authenticated Tools | ✅ Complete | 2024-12-10 | 12 authenticated tools with session context |
437437| 4. Resources & Prompts | ✅ Complete | 2024-12-10 | 10 resources, 6 prompts migrated |
438438| 5. Entry Point & Routing | ✅ Complete | 2024-12-10 | /mcp uses createMcpHandler, backward compat kept |
439- | 6. Authentication | ❌ Blocked | 2024-12-10 | See "Multi-User Auth Challenge" below |
440- | 7. Testing | ⬜ Not Started | | |
441- | 8. Cleanup & Deploy | ⬜ Not Started | | |
439+ | 6. Authentication | ✅ Complete | 2024-12-10 | OAuth 2.0 + session_id fallback for Claude Desktop |
440+ | 7. Testing | ✅ Complete | 2024-12-10 | Tested with Windsurf (OAuth) and Claude Desktop (session_id) |
441+ | 8. Cleanup & Deploy | ✅ Complete | 2024-12-10 | Deployed to production |
442442
443443Legend: ⬜ Not Started | 🟡 In Progress | ✅ Complete | ❌ Blocked
444444
445445---
446446
447- ## Multi-User Auth Challenge (BLOCKING )
447+ ## Multi-User Auth Challenge (RESOLVED )
448448
449449### Problem Statement
450450
@@ -455,99 +455,132 @@ Claude Desktop's MCP connector does not persist session IDs across conversations
455455
456456This means auth stored under ` session:{uuid1} ` is lost when the next conversation uses ` session:{uuid2} ` .
457457
458- ### Current State (as of 2024-12-10)
458+ ### Solution: Hybrid Authentication (Completed 2024-12-10)
459459
460- - ✅ SDK integration working (` /mcp ` endpoint uses ` createMcpHandler ` )
461- - ✅ Session ID extraction from headers and URL params
462- - ✅ Auth stored in KV per session ID
463- - ✅ Login flow works and stores auth correctly
464- - ❌ Auth does not persist across Claude Desktop conversations
465- - ⚠️ Temporary global fallback implemented (NOT safe for multi-user)
466-
467- ### Options for Multi-User Production
468-
469- #### Option 1: OAuth 2.0 with PKCE (Recommended)
470- ** Status** : Not implemented
471- ** Effort** : High
472- ** Pros** : MCP spec compliant, industry standard, secure
473- ** Cons** : Complex implementation, requires OAuth provider setup
474-
475- The MCP spec recommends OAuth 2.0 for remote server authentication. Cloudflare Agents SDK supports this via:
476- - ` @cloudflare/workers-oauth-provider ` package
477- - ` OAuthProvider ` class with ` createMcpHandler `
478- - Tokens stored client-side, persist across sessions
479-
480- Implementation steps:
481- 1 . Set up OAuth provider (Cloudflare Access or custom)
482- 2 . Implement authorization endpoint
483- 3 . Implement token endpoint
484- 4 . Use ` getMcpAuthContext() ` in tools
485- 5 . Map OAuth identity to Last.fm session
486-
487- References:
488- - [ MCP Authorization Spec] ( https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization )
489- - [ Cloudflare MCP Authorization] ( https://developers.cloudflare.com/agents/model-context-protocol/authorization/ )
490-
491- #### Option 2: Cloudflare Access Integration
492- ** Status** : Not implemented
493- ** Effort** : Medium
494- ** Pros** : Handles auth at edge, enterprise-grade
495- ** Cons** : Requires Cloudflare Access subscription, adds dependency
496-
497- Use Cloudflare Access as OAuth provider:
498- 1 . Configure Access application for the MCP server
499- 2 . Access handles authentication before request reaches worker
500- 3 . Worker receives authenticated user identity in headers
501- 4 . Map Access identity to Last.fm session
502-
503- Reference: [ Secure MCP with Access] ( https://developers.cloudflare.com/cloudflare-one/access-controls/ai-controls/saas-mcp/ )
504-
505- #### Option 3: User-Configured Session IDs
506- ** Status** : Implemented (workaround)
507- ** Effort** : Low
508- ** Pros** : Works now, no server changes needed
509- ** Cons** : Poor UX, requires user to configure URL
510-
511- Users add a fixed session ID to their connector URL:
512- ```
513- https://lastfm-mcp-prod.rian-db8.workers.dev/mcp?session_id=USER-SPECIFIC-UUID
514- ```
460+ We implemented ** two authentication methods** to support different MCP clients:
515461
516- This is a valid workaround but not ideal for a public service.
462+ 1 . ** OAuth 2.0** - For clients that support it (Windsurf, future Claude updates)
463+ 2 . ** Session ID URL parameter** - For clients that don't support OAuth (Claude Desktop)
517464
518- #### Option 4: Cookie-Based Auth with SameSite=None
519- ** Status** : Partially implemented
520- ** Effort** : Low
521- ** Pros** : Standard web auth pattern
522- ** Cons** : MCP clients may not support cookies, CORS complexity
465+ ** New Files Created:**
466+ - ` src/index-oauth.ts ` - Hybrid auth entry point (OAuth + session_id fallback)
467+ - ` src/auth/oauth-handler.ts ` - Last.fm OAuth integration + manual login flow
468+ - ` src/mcp/tools/authenticated.ts ` - Added ` registerAuthenticatedToolsWithOAuth() ` function
523469
524- Cookies are set on auth callback but Claude's connector likely doesn't persist them.
470+ ** Key Changes:**
471+ - Added ` @cloudflare/workers-oauth-provider ` dependency
472+ - Added ` OAUTH_KV ` namespace binding to ` wrangler.toml `
473+ - OAuth tools use ` getMcpAuthContext() ` for auth from OAuth tokens
474+ - Session-based tools use KV lookup via ` session_id ` query parameter
475+ - Manual ` /login ` endpoint for Claude Desktop users
525476
526- ### Recommended Path Forward
477+ ### Current State (as of 2024-12-10)
527478
528- For a ** public multi-user service** , implement ** Option 1 (OAuth 2.0)** with this approach:
479+ ** OAuth 2.0 (RFC 9728 compliant):**
480+ - ✅ OAuth discovery (` /.well-known/oauth-authorization-server ` )
481+ - ✅ Protected resource metadata (` /.well-known/oauth-protected-resource ` )
482+ - ✅ Client registration endpoint (` /oauth/register ` )
483+ - ✅ Authorization endpoint (` /authorize ` → Last.fm)
484+ - ✅ Token exchange endpoint (` /oauth/token ` )
485+ - ✅ Last.fm callback (` /lastfm-callback ` )
486+ - ✅ Proper ` WWW-Authenticate ` header with ` resource_metadata ` URL
487+ - ✅ Works with Windsurf and OAuth-compliant MCP clients
529488
530- 1 . ** Phase 1** : Keep current implementation for local/personal use
531- 2 . ** Phase 2** : Add OAuth 2.0 support alongside current auth
532- 3 . ** Phase 3** : Deprecate session-based auth, require OAuth
489+ ** Session ID Fallback (for Claude Desktop):**
490+ - ✅ Manual login endpoint (` /login ` )
491+ - ✅ Session stored in KV with 30-day TTL
492+ - ✅ Session ID passed via URL query parameter
493+ - ✅ Works with Claude Desktop via ` ?session_id= ` parameter
533494
534- OAuth implementation outline:
535- ``` typescript
536- import { OAuthProvider } from " @cloudflare/workers-oauth-provider" ;
537- import { createMcpHandler , getMcpAuthContext } from " agents/mcp" ;
495+ ### Production Architecture
538496
539- // In tool handler:
540- const authContext = getMcpAuthContext ();
541- const userId = authContext .props .sub ; // From OAuth token
542- const lastfmSession = await lookupLastfmSession (userId );
543- ```
497+ ** Entry Point:** ` src/index-oauth.ts ` (hybrid auth)
498+ ** Deploy Command:** ` npm run deploy:prod `
499+
500+ #### Authentication Flow Diagram
544501
545- ### Temporary Workaround (Current)
502+ ```
503+ ┌─────────────────────────────────────────────────────────────────┐
504+ │ MCP Client Request │
505+ │ POST /mcp │
506+ └─────────────────────────────┬───────────────────────────────────┘
507+ │
508+ ▼
509+ ┌──────────────────────┐
510+ │ Has session_id param? │
511+ └──────────────────────┘
512+ │ │
513+ YES NO
514+ │ │
515+ ▼ ▼
516+ ┌─────────────────┐ ┌─────────────────────┐
517+ │ Lookup session │ │ OAuthProvider │
518+ │ from KV │ │ checks Bearer token │
519+ └────────┬────────┘ └──────────┬──────────┘
520+ │ │
521+ ┌─────┴─────┐ ┌─────┴─────┐
522+ │ Valid? │ │ Valid? │
523+ └───────────┘ └───────────┘
524+ │ │ │ │
525+ YES NO YES NO
526+ │ │ │ │
527+ ▼ ▼ ▼ ▼
528+ ┌──────┐ ┌──────────┐ ┌──────┐ ┌──────────────┐
529+ │ MCP │ │ 401 + │ │ MCP │ │ 401 + │
530+ │Server│ │ login URL│ │Server│ │ WWW-Auth │
531+ └──────┘ └──────────┘ └──────┘ │ resource_ │
532+ │ metadata │
533+ └──────────────┘
534+ ```
546535
547- Global auth fallback is implemented but ** NOT SAFE for multi-user** :
548- - Last authenticated user becomes the "default" for everyone
549- - Acceptable for single-user testing only
550- - Must be replaced with proper OAuth before public launch
536+ #### Client Compatibility
537+
538+ | Client | Auth Method | Status |
539+ | --------| -------------| --------|
540+ | Windsurf | OAuth 2.0 | ✅ Working |
541+ | Claude Desktop | session_id URL param | ✅ Working (manual setup) |
542+ | Claude Code | session_id URL param | ✅ Working (manual setup) |
543+ | MCP Inspector | OAuth 2.0 | ✅ Should work |
544+ | Custom clients | Either | ✅ Both supported |
545+
546+ #### OAuth 2.0 Flow (Windsurf, etc.)
547+
548+ 1 . Client requests ` /mcp ` → gets 401 with ` WWW-Authenticate: Bearer resource_metadata="..." `
549+ 2 . Client fetches ` /.well-known/oauth-protected-resource `
550+ 3 . Client fetches ` /.well-known/oauth-authorization-server `
551+ 4 . Client registers via ` /oauth/register `
552+ 5 . Client redirects user to ` /authorize `
553+ 6 . Server redirects to Last.fm auth
554+ 7 . Last.fm redirects to ` /lastfm-callback `
555+ 8 . Server completes OAuth, issues token
556+ 9 . Client accesses ` /mcp ` with Bearer token
557+
558+ #### Session ID Flow (Claude Desktop)
559+
560+ 1 . User visits ` https://lastfm-mcp-prod.rian-db8.workers.dev/login `
561+ 2 . Redirects to Last.fm authentication
562+ 3 . Last.fm redirects to ` /callback `
563+ 4 . Server stores session in KV, shows session ID
564+ 5 . User adds ` ?session_id=XXX ` to their MCP config URL
565+ 6 . All requests to ` /mcp?session_id=XXX ` use stored session
566+
567+ #### Endpoint Reference
568+
569+ | Endpoint | Method | Purpose |
570+ | ----------| --------| --------|
571+ | ` / ` | GET | API info JSON |
572+ | ` /mcp ` | POST | MCP JSON-RPC endpoint |
573+ | ` /mcp?session_id=XXX ` | POST | MCP with session auth |
574+ | ` /login ` | GET | Manual login (Claude Desktop) |
575+ | ` /callback ` | GET | Manual login callback |
576+ | ` /authorize ` | GET | OAuth authorization |
577+ | ` /oauth/token ` | POST | OAuth token exchange |
578+ | ` /oauth/register ` | POST | OAuth client registration |
579+ | ` /lastfm-callback ` | GET | OAuth Last.fm callback |
580+ | ` /.well-known/oauth-authorization-server ` | GET | OAuth server metadata |
581+ | ` /.well-known/oauth-protected-resource ` | GET | OAuth resource metadata |
582+ | ` /.well-known/mcp.json ` | GET | MCP server discovery |
583+ | ` /health ` | GET | Health check |
551584
552585---
553586
0 commit comments