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
This commit is contained in:
parent
b7965602ba
commit
d368c8d8c7
4 changed files with 83 additions and 23 deletions
92
README.md
92
README.md
|
|
@ -90,7 +90,7 @@ npm run dev:integrated
|
||||||
npx -y @modelcontextprotocol/inspector
|
npx -y @modelcontextprotocol/inspector
|
||||||
|
|
||||||
# 4. Connect and test:
|
# 4. Connect and test:
|
||||||
# - Connect to http://localhost:3232
|
# - Connect to http://localhost:3232/mcp
|
||||||
# - Navigate to the Auth tab
|
# - Navigate to the Auth tab
|
||||||
# - Complete the OAuth flow
|
# - Complete the OAuth flow
|
||||||
# - All auth endpoints will be served from :3232
|
# - All auth endpoints will be served from :3232
|
||||||
|
|
@ -108,7 +108,7 @@ npm run dev:with-separate-auth
|
||||||
npx -y @modelcontextprotocol/inspector
|
npx -y @modelcontextprotocol/inspector
|
||||||
|
|
||||||
# 4. Connect and test:
|
# 4. Connect and test:
|
||||||
# - Connect to http://localhost:3232
|
# - Connect to http://localhost:3232/mcp
|
||||||
# - Navigate to the Auth tab
|
# - Navigate to the Auth tab
|
||||||
# - The auth flow will redirect to :3001 for authentication
|
# - The auth flow will redirect to :3001 for authentication
|
||||||
# - After auth, tokens from :3001 will be used on :3232
|
# - After auth, tokens from :3001 will be used on :3232
|
||||||
|
|
@ -117,26 +117,84 @@ npx -y @modelcontextprotocol/inspector
|
||||||
### Architecture Diagrams
|
### Architecture Diagrams
|
||||||
|
|
||||||
#### Integrated Mode
|
#### Integrated Mode
|
||||||
```
|
```mermaid
|
||||||
┌──────────┐ ┌───────────────────┐
|
graph TD
|
||||||
│ MCP │◀────▶│ MCP Server │
|
Client["MCP Client<br/>(Inspector)"]
|
||||||
│ Inspector│ │ (port 3232) │
|
MCP["MCP Server<br/>(port 3232)<br/>• OAuth Server<br/>• Resource Server"]
|
||||||
└──────────┘ │ │
|
|
||||||
│ - OAuth Server │
|
Client <-->|"OAuth flow & MCP resources"| MCP
|
||||||
│ - Resource Server │
|
|
||||||
└───────────────────┘
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Separate Mode
|
#### Separate Mode
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
Client["MCP Client<br/>(Inspector)"]
|
||||||
|
MCP["MCP Server<br/>(port 3232)<br/>Resource Server"]
|
||||||
|
Auth["Auth Server<br/>(port 3001)<br/>OAuth Server"]
|
||||||
|
|
||||||
|
Client <-->|"1. Discover metadata"| MCP
|
||||||
|
Client <-->|"2. OAuth flow<br/>(register, authorize, token)"| Auth
|
||||||
|
Client <-->|"3. Use tokens for MCP resources"| MCP
|
||||||
|
MCP <-->|"Token validation<br/>(introspect)"| Auth
|
||||||
```
|
```
|
||||||
┌──────────┐ ┌───────────────────┐ ┌─────────────────┐
|
|
||||||
│ MCP │◀────▶│ MCP Server │◀────▶│ Auth Server │
|
## OAuth Flow Analysis
|
||||||
│ Inspector│ │ (port 3232) │ │ (port 3001) │
|
|
||||||
└──────────┘ │ │ │ │
|
### OAuth 2.0 + PKCE Flow Sequence
|
||||||
│ │ - Resource Server │ │ - OAuth Server │
|
|
||||||
└───────────▶│ │ │ │
|
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
|
## Installation
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ curl http://localhost:3001/.well-known/oauth-authorization-server
|
||||||
1. Start this auth server: `npm run dev:auth-server`
|
1. Start this auth server: `npm run dev:auth-server`
|
||||||
2. Start MCP server in separate mode: `AUTH_MODE=separate npm run dev`
|
2. Start MCP server in separate mode: `AUTH_MODE=separate npm run dev`
|
||||||
3. Open Inspector: `npx -y @modelcontextprotocol/inspector`
|
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)
|
5. Auth flow will redirect to this server (port 3001)
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
|
||||||
|
|
@ -142,5 +142,5 @@ app.listen(AUTH_SERVER_PORT, () => {
|
||||||
console.log('💡 To test separate mode:');
|
console.log('💡 To test separate mode:');
|
||||||
console.log(' 1. Keep this server running');
|
console.log(' 1. Keep this server running');
|
||||||
console.log(' 2. In another terminal: AUTH_MODE=separate npm run dev');
|
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');
|
||||||
});
|
});
|
||||||
|
|
@ -20,10 +20,11 @@ export const REDIS_KEY_PREFIXES = {
|
||||||
* Redis key expiry times in seconds
|
* Redis key expiry times in seconds
|
||||||
*/
|
*/
|
||||||
export const REDIS_EXPIRY_TIMES = {
|
export const REDIS_EXPIRY_TIMES = {
|
||||||
PENDING_AUTHORIZATION: 10 * 60, // 10 minutes - authorization code -> PendingAuthorization
|
CLIENT_REGISTRATION: 30 * 24 * 60 * 60, // 30 days - client app credentials
|
||||||
TOKEN_EXCHANGE: 10 * 60, // 10 minutes - authorization code -> MCP access token
|
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
|
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;
|
} as const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -92,7 +93,8 @@ export async function saveClientRegistration(
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await redisClient.set(
|
await redisClient.set(
|
||||||
REDIS_KEY_PREFIXES.CLIENT_REGISTRATION + clientId,
|
REDIS_KEY_PREFIXES.CLIENT_REGISTRATION + clientId,
|
||||||
JSON.stringify(registration)
|
JSON.stringify(registration),
|
||||||
|
{ EX: REDIS_EXPIRY_TIMES.CLIENT_REGISTRATION }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue