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 }
);
}