From d368c8d8c7fae0ccdde5c81000dda0daa0b58ec6 Mon Sep 17 00:00:00 2001 From: Basil Hosmer Date: Thu, 11 Sep 2025 14:57:20 -0400 Subject: [PATCH] address comments: * fix: add /mcp to endpoints in misc README.mds * improve architecture interaction diagrams in README.md * add "OAuth Flow Analysis" section in README.md --- README.md | 92 +++++++++++++++++++++++++++++++++++-------- auth-server/README.md | 2 +- auth-server/index.ts | 2 +- shared/redis-auth.ts | 10 +++-- 4 files changed, 83 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 263fb50..cce94c1 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ npm run dev:integrated npx -y @modelcontextprotocol/inspector # 4. Connect and test: -# - Connect to http://localhost:3232 +# - Connect to http://localhost:3232/mcp # - Navigate to the Auth tab # - Complete the OAuth flow # - All auth endpoints will be served from :3232 @@ -108,7 +108,7 @@ npm run dev:with-separate-auth npx -y @modelcontextprotocol/inspector # 4. Connect and test: -# - Connect to http://localhost:3232 +# - Connect to http://localhost:3232/mcp # - Navigate to the Auth tab # - The auth flow will redirect to :3001 for authentication # - After auth, tokens from :3001 will be used on :3232 @@ -117,26 +117,84 @@ npx -y @modelcontextprotocol/inspector ### Architecture Diagrams #### Integrated Mode -``` -┌──────────┐ ┌───────────────────┐ -│ MCP │◀────▶│ MCP Server │ -│ Inspector│ │ (port 3232) │ -└──────────┘ │ │ - │ - OAuth Server │ - │ - Resource Server │ - └───────────────────┘ +```mermaid +graph TD + Client["MCP Client
(Inspector)"] + MCP["MCP Server
(port 3232)
• OAuth Server
• Resource Server"] + + Client <-->|"OAuth flow & MCP resources"| MCP ``` #### Separate Mode +```mermaid +graph TD + Client["MCP Client
(Inspector)"] + MCP["MCP Server
(port 3232)
Resource Server"] + Auth["Auth Server
(port 3001)
OAuth Server"] + + Client <-->|"1. Discover metadata"| MCP + Client <-->|"2. OAuth flow
(register, authorize, token)"| Auth + Client <-->|"3. Use tokens for MCP resources"| MCP + MCP <-->|"Token validation
(introspect)"| Auth ``` -┌──────────┐ ┌───────────────────┐ ┌─────────────────┐ -│ MCP │◀────▶│ MCP Server │◀────▶│ Auth Server │ -│ Inspector│ │ (port 3232) │ │ (port 3001) │ -└──────────┘ │ │ │ │ - │ │ - Resource Server │ │ - OAuth Server │ - └───────────▶│ │ │ │ - └───────────────────┘ └─────────────────┘ + +## OAuth Flow Analysis + +### OAuth 2.0 + PKCE Flow Sequence + +The server implements a complete OAuth 2.0 authorization code flow with PKCE. Here's how each step maps to data storage and expiry: + +**1. Client Registration** (app setup - happens once) ``` +App → Auth Server: "I want to use OAuth, here's my info" +Auth Server → App: "OK, your client_id is XYZ, client_secret is ABC" +``` +- **Storage**: Client credentials for future OAuth flows +- **Expiry**: 30 days (long-lived app credentials) + +**2. Authorization Request** (starts each OAuth flow) +``` +User → App: "I want to connect to MCP server" +App → Auth Server: "User wants access, here's my PKCE challenge" +Auth Server: Stores pending authorization, shows auth page +``` +- **Storage**: `PENDING_AUTHORIZATION` - temporary state during flow +- **Expiry**: 10 minutes (short-lived temporary state) + +**3. Authorization Code Exchange** (completes OAuth flow) +``` +User → Auth Server: "I approve this app" +Auth Server → App: "Here's your authorization code" +App → Auth Server: "Exchange code + PKCE verifier for tokens" +Auth Server → App: "Here are your access/refresh tokens" +``` +- **Storage**: `TOKEN_EXCHANGE` - prevents replay attacks +- **Expiry**: 10 minutes (single-use, consumed immediately) + +**4. Token Storage** (long-term user session) +``` +Auth Server: Issues access_token + refresh_token +Server: Stores user installation with tokens +``` +- **Storage**: `UPSTREAM_INSTALLATION` - the actual user session +- **Expiry**: 7 days (balances security vs usability) + +**5. Token Refresh** (extends user session) +``` +App → Auth Server: "My access token expired, here's my refresh token" +Auth Server → App: "Here's a new access token" +``` +- **Storage**: `REFRESH_TOKEN` - mapping for token rotation +- **Expiry**: 7 days (matches installation lifetime) + +### Data Lifecycle Hierarchy + +**Timeline (shortest to longest expiry):** +1. **OAuth flow state** (10 minutes) - very temporary +2. **User sessions** (7 days) - medium-term +3. **Client credentials** (30 days) - long-term + +This creates a logical hierarchy where each layer outlives the layers it supports. ## Installation diff --git a/auth-server/README.md b/auth-server/README.md index 5e8135a..abf8855 100644 --- a/auth-server/README.md +++ b/auth-server/README.md @@ -69,7 +69,7 @@ curl http://localhost:3001/.well-known/oauth-authorization-server 1. Start this auth server: `npm run dev:auth-server` 2. Start MCP server in separate mode: `AUTH_MODE=separate npm run dev` 3. Open Inspector: `npx -y @modelcontextprotocol/inspector` -4. Connect to `http://localhost:3232` +4. Connect to `http://localhost:3232/mcp` 5. Auth flow will redirect to this server (port 3001) ## Configuration diff --git a/auth-server/index.ts b/auth-server/index.ts index 147e0e0..7fb446c 100644 --- a/auth-server/index.ts +++ b/auth-server/index.ts @@ -142,5 +142,5 @@ app.listen(AUTH_SERVER_PORT, () => { console.log('💡 To test separate mode:'); console.log(' 1. Keep this server running'); console.log(' 2. In another terminal: AUTH_MODE=separate npm run dev'); - console.log(' 3. Connect Inspector to http://localhost:3232'); + console.log(' 3. Connect Inspector to http://localhost:3232/mcp'); }); \ No newline at end of file diff --git a/shared/redis-auth.ts b/shared/redis-auth.ts index aa54b3d..7539c10 100644 --- a/shared/redis-auth.ts +++ b/shared/redis-auth.ts @@ -20,10 +20,11 @@ export const REDIS_KEY_PREFIXES = { * Redis key expiry times in seconds */ export const REDIS_EXPIRY_TIMES = { - PENDING_AUTHORIZATION: 10 * 60, // 10 minutes - authorization code -> PendingAuthorization - TOKEN_EXCHANGE: 10 * 60, // 10 minutes - authorization code -> MCP access token + CLIENT_REGISTRATION: 30 * 24 * 60 * 60, // 30 days - client app credentials + PENDING_AUTHORIZATION: 10 * 60, // 10 minutes - authorization code -> PendingAuthorization + TOKEN_EXCHANGE: 10 * 60, // 10 minutes - authorization code -> MCP access token UPSTREAM_INSTALLATION: 7 * 24 * 60 * 60, // 7 days - MCP access token -> UpstreamInstallation - REFRESH_TOKEN: 7 * 24 * 60 * 60, // 7 days - MCP refresh token -> access token + REFRESH_TOKEN: 7 * 24 * 60 * 60, // 7 days - MCP refresh token -> access token } as const; /** @@ -92,7 +93,8 @@ export async function saveClientRegistration( ): Promise { await redisClient.set( REDIS_KEY_PREFIXES.CLIENT_REGISTRATION + clientId, - JSON.stringify(registration) + JSON.stringify(registration), + { EX: REDIS_EXPIRY_TIMES.CLIENT_REGISTRATION } ); }