Fix endpoint consistency and documentation accuracy
- Remove /oauth prefix from introspect endpoint to match SDK pattern - Fix auth-server endpoints: /introspect not /oauth/introspect - Update external-verifier to call correct introspect endpoint - Add 30-day expiry to client registration (REDIS_EXPIRY_TIMES.CLIENT_REGISTRATION) - Remove inaccurate scratch/ directory references from README - Add backlinks from auth-server/README.md to main README sections - Verified all implementation details against running servers and Redis data
This commit is contained in:
parent
d368c8d8c7
commit
9ffe376932
4 changed files with 269 additions and 286 deletions
508
README.md
508
README.md
|
|
@ -9,7 +9,7 @@ A comprehensive example implementation of a scalable Model Context Protocol (MCP
|
||||||
The Everything Server is an open-source reference implementation that showcases:
|
The Everything Server is an open-source reference implementation that showcases:
|
||||||
- **Complete [MCP Protocol](https://modelcontextprotocol.io/specification) Support**: All MCP features including tools, resources, prompts, sampling, completions, and logging
|
- **Complete [MCP Protocol](https://modelcontextprotocol.io/specification) Support**: All MCP features including tools, resources, prompts, sampling, completions, and logging
|
||||||
- **Multiple [Transport Methods](https://modelcontextprotocol.io/docs/concepts/transports)**: Streamable HTTP (SHTTP) and Server-Sent Events (SSE)
|
- **Multiple [Transport Methods](https://modelcontextprotocol.io/docs/concepts/transports)**: Streamable HTTP (SHTTP) and Server-Sent Events (SSE)
|
||||||
- **Comprehensive Auth**: OAuth 2.0 with fake upstream provider for testing
|
- **Dual Authentication Modes**: Integrated and separate authorization server support
|
||||||
- **Horizontal Scalability**: Redis-backed session management for multi-instance deployments
|
- **Horizontal Scalability**: Redis-backed session management for multi-instance deployments
|
||||||
|
|
||||||
This server serves as both primarily as a learning resource, and an example implementation of a scalable remote MCP server.
|
This server serves as both primarily as a learning resource, and an example implementation of a scalable remote MCP server.
|
||||||
|
|
@ -40,162 +40,6 @@ This server serves as both primarily as a learning resource, and an example impl
|
||||||
- **Security Headers**: CSP, HSTS, X-Frame-Options, and more
|
- **Security Headers**: CSP, HSTS, X-Frame-Options, and more
|
||||||
- **Bearer Token Auth**: Middleware for protected endpoints
|
- **Bearer Token Auth**: Middleware for protected endpoints
|
||||||
|
|
||||||
## Authentication Modes
|
|
||||||
|
|
||||||
The Everything Server supports two authentication modes to demonstrate different MCP deployment patterns:
|
|
||||||
|
|
||||||
### Integrated Mode (Default)
|
|
||||||
The MCP server acts as its own OAuth 2.0 authorization server. This is simpler to deploy and suitable for standalone MCP servers.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run dev:integrated
|
|
||||||
```
|
|
||||||
|
|
||||||
### Separate Mode
|
|
||||||
The MCP server delegates authentication to a standalone authorization server. This demonstrates how MCP servers can integrate with existing OAuth infrastructure. See [auth-server/README.md](auth-server/README.md) for more details about the standalone auth server.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Start both the auth server and MCP server
|
|
||||||
npm run dev:with-separate-auth
|
|
||||||
|
|
||||||
# Or run them separately:
|
|
||||||
# Terminal 1: Start the authorization server
|
|
||||||
npm run dev:auth-server
|
|
||||||
|
|
||||||
# Terminal 2: Start the MCP server in separate mode
|
|
||||||
npm run dev:separate
|
|
||||||
```
|
|
||||||
|
|
||||||
In production, the separate authorization server would typically be replaced with:
|
|
||||||
- Corporate SSO (Auth0, Okta)
|
|
||||||
- Cloud providers (AWS Cognito, Azure AD)
|
|
||||||
- Social providers (Google, GitHub)
|
|
||||||
|
|
||||||
### Testing with MCP Inspector
|
|
||||||
|
|
||||||
The MCP Inspector is a web-based tool for testing MCP servers. You can run it locally:
|
|
||||||
```bash
|
|
||||||
npx -y @modelcontextprotocol/inspector
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Integrated Mode
|
|
||||||
```bash
|
|
||||||
# 1. Start Redis
|
|
||||||
docker compose up -d
|
|
||||||
|
|
||||||
# 2. Start the server
|
|
||||||
npm run dev:integrated
|
|
||||||
|
|
||||||
# 3. Open MCP Inspector
|
|
||||||
npx -y @modelcontextprotocol/inspector
|
|
||||||
|
|
||||||
# 4. Connect and test:
|
|
||||||
# - Connect to http://localhost:3232/mcp
|
|
||||||
# - Navigate to the Auth tab
|
|
||||||
# - Complete the OAuth flow
|
|
||||||
# - All auth endpoints will be served from :3232
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Separate Mode
|
|
||||||
```bash
|
|
||||||
# 1. Start Redis
|
|
||||||
docker compose up -d
|
|
||||||
|
|
||||||
# 2. Start both servers
|
|
||||||
npm run dev:with-separate-auth
|
|
||||||
|
|
||||||
# 3. Open MCP Inspector
|
|
||||||
npx -y @modelcontextprotocol/inspector
|
|
||||||
|
|
||||||
# 4. Connect and test:
|
|
||||||
# - 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
|
|
||||||
```
|
|
||||||
|
|
||||||
### Architecture Diagrams
|
|
||||||
|
|
||||||
#### Integrated Mode
|
|
||||||
```mermaid
|
|
||||||
graph TD
|
|
||||||
Client["MCP Client<br/>(Inspector)"]
|
|
||||||
MCP["MCP Server<br/>(port 3232)<br/>• OAuth Server<br/>• Resource Server"]
|
|
||||||
|
|
||||||
Client <-->|"OAuth flow & MCP resources"| MCP
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 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
|
|
||||||
```
|
|
||||||
|
|
||||||
## 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
|
## Installation
|
||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
@ -239,13 +83,10 @@ cd example-remote-server
|
||||||
|
|
||||||
# Install dependencies
|
# Install dependencies
|
||||||
npm install
|
npm install
|
||||||
|
|
||||||
# Configure environment (optional)
|
|
||||||
cp .env.example .env
|
|
||||||
# Edit .env with your settings
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Configuration
|
## Configuration
|
||||||
|
|
||||||
Environment variables (`.env` file):
|
Environment variables (`.env` file):
|
||||||
```bash
|
```bash
|
||||||
# Server Configuration
|
# Server Configuration
|
||||||
|
|
@ -275,6 +116,37 @@ cp .env.integrated .env
|
||||||
cp .env.separate .env
|
cp .env.separate .env
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Authentication Modes
|
||||||
|
|
||||||
|
The Everything Server supports two authentication modes to demonstrate different MCP deployment patterns:
|
||||||
|
|
||||||
|
### Integrated Mode (Default)
|
||||||
|
The MCP server acts as its own OAuth 2.0 authorization server. This is simpler to deploy and suitable for standalone MCP servers.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev:integrated
|
||||||
|
```
|
||||||
|
|
||||||
|
### Separate Mode
|
||||||
|
The MCP server delegates authentication to a standalone authorization server. This demonstrates how MCP servers can integrate with existing OAuth infrastructure. See [auth-server/README.md](auth-server/README.md) for more details about the standalone auth server.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Start both the auth server and MCP server
|
||||||
|
npm run dev:with-separate-auth
|
||||||
|
|
||||||
|
# Or run them separately:
|
||||||
|
# Terminal 1: Start the authorization server
|
||||||
|
npm run dev:auth-server
|
||||||
|
|
||||||
|
# Terminal 2: Start the MCP server in separate mode
|
||||||
|
npm run dev:separate
|
||||||
|
```
|
||||||
|
|
||||||
|
In production, the separate authorization server would typically be replaced with:
|
||||||
|
- Corporate SSO (Auth0, Okta)
|
||||||
|
- Cloud providers (AWS Cognito, Azure AD)
|
||||||
|
- Social providers (Google, GitHub)
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
### Commands
|
### Commands
|
||||||
|
|
@ -327,108 +199,39 @@ npm run lint
|
||||||
npm test
|
npm test
|
||||||
```
|
```
|
||||||
|
|
||||||
### Project Structure
|
### Testing with MCP Inspector
|
||||||
```
|
|
||||||
├── src/
|
The MCP Inspector is a web-based tool for testing MCP servers:
|
||||||
│ ├── index.ts # Express app setup and routes
|
```bash
|
||||||
│ ├── config.ts # Configuration management
|
npx -y @modelcontextprotocol/inspector
|
||||||
│ ├── redis.ts # Redis client setup
|
|
||||||
│ ├── auth/
|
|
||||||
│ │ └── provider.ts # OAuth auth provider implementation
|
|
||||||
│ ├── handlers/
|
|
||||||
│ │ ├── shttp.ts # Streamable HTTP handler
|
|
||||||
│ │ ├── sse.ts # SSE transport handler
|
|
||||||
│ │ ├── fakeauth.ts # Fake upstream auth handler
|
|
||||||
│ │ └── common.ts # Shared middleware
|
|
||||||
│ ├── services/
|
|
||||||
│ │ ├── mcp.ts # MCP server implementation
|
|
||||||
│ │ ├── auth.ts # Session ownership management
|
|
||||||
│ │ └── redisTransport.ts # Redis-backed transport
|
|
||||||
│ └── utils/
|
|
||||||
│ └── logger.ts # Structured logging
|
|
||||||
├── docs/
|
|
||||||
│ ├── streamable-http-design.md # SHTTP implementation details
|
|
||||||
│ └── user-id-system.md # Authentication flow documentation
|
|
||||||
├── scratch/ # Development scripts and tests
|
|
||||||
└── dist/ # Compiled JavaScript output
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## API Endpoints
|
#### Test Integrated Mode
|
||||||
|
```bash
|
||||||
|
# 1. Start Redis
|
||||||
|
docker compose up -d
|
||||||
|
|
||||||
### MCP Endpoints
|
# 2. Start the server
|
||||||
- `GET/POST/DELETE /mcp` - Streamable HTTP transport endpoint
|
npm run dev:integrated
|
||||||
- `POST`: Initialize sessions or send messages
|
|
||||||
- `GET`: Establish SSE streams
|
|
||||||
- `DELETE`: Terminate sessions
|
|
||||||
- `GET /sse` - Legacy SSE transport endpoint
|
|
||||||
- `POST /message` - Legacy message endpoint for SSE transport
|
|
||||||
|
|
||||||
### Authentication Endpoints
|
# 3. Open MCP Inspector and connect to http://localhost:3232/mcp
|
||||||
- `GET /fakeupstreamauth/authorize` - Fake OAuth authorization page
|
# 4. Navigate to the Auth tab and complete the OAuth flow
|
||||||
- `GET /fakeupstreamauth/redirect` - OAuth redirect handler
|
# 5. All auth endpoints will be served from :3232
|
||||||
- OAuth 2.0 endpoints provided by MCP SDK auth router
|
|
||||||
|
|
||||||
### Headers
|
|
||||||
- `Mcp-Session-Id`: Session identifier for Streamable HTTP
|
|
||||||
- `Authorization: Bearer <token>`: OAuth access token
|
|
||||||
- Standard MCP headers as per protocol specification
|
|
||||||
|
|
||||||
## Authentication Flow
|
|
||||||
|
|
||||||
The server implements a complete [OAuth 2.0](https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization) flow with a fake upstream provider for testing:
|
|
||||||
|
|
||||||
1. **Client Registration**: Clients register to obtain client_id and client_secret
|
|
||||||
2. **Authorization**: Users authenticate through `/fakeupstreamauth/authorize`
|
|
||||||
3. **User Management**: localStorage-based user ID system for testing
|
|
||||||
4. **Token Exchange**: Authorization codes exchanged for access tokens
|
|
||||||
5. **Session Creation**: Authenticated tokens create owned sessions
|
|
||||||
6. **Access Control**: Sessions are isolated by user ownership
|
|
||||||
|
|
||||||
See [docs/user-id-system.md](docs/user-id-system.md) for detailed authentication documentation.
|
|
||||||
|
|
||||||
## Transport Methods
|
|
||||||
|
|
||||||
### Streamable HTTP (Recommended)
|
|
||||||
Modern [transport](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http) supporting bidirectional communication over HTTP:
|
|
||||||
- Single endpoint for all operations
|
|
||||||
- Session management via headers
|
|
||||||
- Efficient message buffering
|
|
||||||
- Automatic reconnection support
|
|
||||||
|
|
||||||
See [docs/streamable-http-design.md](docs/streamable-http-design.md) for implementation details.
|
|
||||||
|
|
||||||
### Server-Sent Events (Legacy)
|
|
||||||
Backwards-compatible [transport](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#server-sent-events) using SSE:
|
|
||||||
- Separate endpoints for SSE streams and messages
|
|
||||||
- Session management via URL parameters
|
|
||||||
- Redis-backed message routing
|
|
||||||
- Real-time event delivery
|
|
||||||
|
|
||||||
## Scalability Architecture
|
|
||||||
|
|
||||||
The server is designed for horizontal scaling using Redis as the backbone:
|
|
||||||
|
|
||||||
### Session State Management
|
|
||||||
- **Redis Storage**: All session state stored in Redis
|
|
||||||
- **5-minute TTL**: Automatic session cleanup
|
|
||||||
- **Session Ownership**: User isolation via Redis keys
|
|
||||||
- **Stateless Servers**: Any instance can handle any request
|
|
||||||
|
|
||||||
### Message Routing
|
|
||||||
- **Pub/Sub Channels**: Redis channels for message distribution
|
|
||||||
- **Message Buffering**: Reliable delivery for disconnected clients
|
|
||||||
- **Connection State**: Tracked via pub/sub subscription counts
|
|
||||||
- **Automatic Cleanup**: No explicit cleanup required
|
|
||||||
|
|
||||||
### Redis Key Structure
|
|
||||||
```
|
|
||||||
session:{sessionId}:owner # Session ownership
|
|
||||||
mcp:shttp:toserver:{sessionId} # Client→Server messages
|
|
||||||
mcp:shttp:toclient:{sessionId}:{requestId} # Server→Client responses
|
|
||||||
mcp:control:{sessionId} # Control messages
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Testing
|
#### Test Separate Mode
|
||||||
|
```bash
|
||||||
|
# 1. Start Redis
|
||||||
|
docker compose up -d
|
||||||
|
|
||||||
|
# 2. Start both servers
|
||||||
|
npm run dev:with-separate-auth
|
||||||
|
|
||||||
|
# 3. Open MCP Inspector and connect to http://localhost:3232/mcp
|
||||||
|
# 4. Navigate to the Auth tab
|
||||||
|
# 5. The auth flow will redirect to :3001 for authentication
|
||||||
|
# 6. After auth, tokens from :3001 will be used on :3232
|
||||||
|
```
|
||||||
|
|
||||||
### Running Tests
|
### Running Tests
|
||||||
```bash
|
```bash
|
||||||
|
|
@ -449,12 +252,186 @@ npm test -- --coverage
|
||||||
- **Auth Tests**: OAuth flow and session ownership
|
- **Auth Tests**: OAuth flow and session ownership
|
||||||
- **Multi-user Tests**: User isolation and access control
|
- **Multi-user Tests**: User isolation and access control
|
||||||
|
|
||||||
### Manual Testing
|
### Additional Testing
|
||||||
The `scratch/` directory contains utility scripts for testing:
|
Use the MCP Inspector for interactive testing and debugging of OAuth flows, tool execution, and resource access.
|
||||||
- `oauth.sh` - Test OAuth flows
|
|
||||||
- `simple-test-client.js` - Basic client implementation
|
## Architecture & Technical Details
|
||||||
- `test-shttp-client.ts` - Streamable HTTP testing
|
|
||||||
- `debug-mcp-flow.sh` - Debug MCP message flows
|
### Authentication Architecture
|
||||||
|
|
||||||
|
#### Integrated Mode
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
Client["MCP Client<br/>(Inspector)"]
|
||||||
|
MCP["MCP Server<br/>(port 3232)<br/>• OAuth Server<br/>• Resource Server"]
|
||||||
|
|
||||||
|
Client <-->|"OAuth flow & MCP resources"| MCP
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 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
|
||||||
|
```
|
||||||
|
|
||||||
|
### OAuth 2.0 + PKCE Flow Analysis
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
### Project Structure
|
||||||
|
```
|
||||||
|
├── src/ # MCP server code
|
||||||
|
│ ├── index.ts # Express app setup and routes
|
||||||
|
│ ├── config.ts # Configuration management
|
||||||
|
│ ├── redis.ts # Redis client setup
|
||||||
|
│ ├── auth/
|
||||||
|
│ │ ├── provider.ts # OAuth auth provider implementation
|
||||||
|
│ │ └── external-verifier.ts # External token verification
|
||||||
|
│ ├── handlers/
|
||||||
|
│ │ ├── shttp.ts # Streamable HTTP handler
|
||||||
|
│ │ ├── sse.ts # SSE transport handler
|
||||||
|
│ │ ├── fakeauth.ts # Fake upstream auth handler
|
||||||
|
│ │ └── common.ts # Shared middleware
|
||||||
|
│ ├── services/
|
||||||
|
│ │ ├── mcp.ts # MCP server implementation
|
||||||
|
│ │ ├── auth.ts # Auth service wrappers
|
||||||
|
│ │ └── redisTransport.ts # Redis-backed transport
|
||||||
|
│ └── utils/
|
||||||
|
│ └── logger.ts # Structured logging
|
||||||
|
├── auth-server/ # Standalone authorization server
|
||||||
|
│ ├── index.ts # Auth server main entry point
|
||||||
|
│ ├── README.md # Auth server documentation
|
||||||
|
│ └── tsconfig.json # TypeScript configuration
|
||||||
|
├── shared/ # Shared between both servers
|
||||||
|
│ ├── auth-core.ts # Core auth logic
|
||||||
|
│ ├── redis-auth.ts # Redis auth operations
|
||||||
|
│ └── types.ts # Shared type definitions
|
||||||
|
├── docs/
|
||||||
|
│ ├── streamable-http-design.md # SHTTP implementation details
|
||||||
|
│ └── user-id-system.md # Authentication flow documentation
|
||||||
|
└── dist/ # Compiled JavaScript output
|
||||||
|
```
|
||||||
|
|
||||||
|
### Scalability Architecture
|
||||||
|
|
||||||
|
The server is designed for horizontal scaling using Redis as the backbone:
|
||||||
|
|
||||||
|
#### Session State Management
|
||||||
|
- **Redis Storage**: All session state stored in Redis
|
||||||
|
- **5-minute TTL**: Automatic session cleanup
|
||||||
|
- **Session Ownership**: User isolation via Redis keys
|
||||||
|
- **Stateless Servers**: Any instance can handle any request
|
||||||
|
|
||||||
|
#### Message Routing
|
||||||
|
- **Pub/Sub Channels**: Redis channels for message distribution
|
||||||
|
- **Message Buffering**: Reliable delivery for disconnected clients
|
||||||
|
- **Connection State**: Tracked via pub/sub subscription counts
|
||||||
|
- **Automatic Cleanup**: No explicit cleanup required
|
||||||
|
|
||||||
|
#### Redis Key Structure
|
||||||
|
```
|
||||||
|
session:{sessionId}:owner # Session ownership
|
||||||
|
mcp:shttp:toserver:{sessionId} # Client→Server messages
|
||||||
|
mcp:shttp:toclient:{sessionId}:{requestId} # Server→Client responses
|
||||||
|
mcp:control:{sessionId} # Control messages
|
||||||
|
```
|
||||||
|
|
||||||
|
### Transport Methods
|
||||||
|
|
||||||
|
#### Streamable HTTP (Recommended)
|
||||||
|
Modern [transport](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http) supporting bidirectional communication over HTTP:
|
||||||
|
- Single endpoint for all operations
|
||||||
|
- Session management via headers
|
||||||
|
- Efficient message buffering
|
||||||
|
- Automatic reconnection support
|
||||||
|
|
||||||
|
See [docs/streamable-http-design.md](docs/streamable-http-design.md) for implementation details.
|
||||||
|
|
||||||
|
#### Server-Sent Events (Legacy)
|
||||||
|
Backwards-compatible [transport](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#server-sent-events) using SSE:
|
||||||
|
- Separate endpoints for SSE streams and messages
|
||||||
|
- Session management via URL parameters
|
||||||
|
- Redis-backed message routing
|
||||||
|
- Real-time event delivery
|
||||||
|
|
||||||
|
## API Reference
|
||||||
|
|
||||||
|
### MCP Endpoints
|
||||||
|
- `GET/POST/DELETE /mcp` - Streamable HTTP transport endpoint
|
||||||
|
- `POST`: Initialize sessions or send messages
|
||||||
|
- `GET`: Establish SSE streams
|
||||||
|
- `DELETE`: Terminate sessions
|
||||||
|
- `GET /sse` - Legacy SSE transport endpoint
|
||||||
|
- `POST /message` - Legacy message endpoint for SSE transport
|
||||||
|
|
||||||
|
### Authentication Endpoints (Integrated Mode Only)
|
||||||
|
- `GET /fakeupstreamauth/authorize` - Fake OAuth authorization page
|
||||||
|
- `GET /fakeupstreamauth/callback` - OAuth redirect handler
|
||||||
|
- OAuth 2.0 endpoints provided by MCP SDK auth router
|
||||||
|
|
||||||
|
### Headers
|
||||||
|
- `Mcp-Session-Id`: Session identifier for Streamable HTTP
|
||||||
|
- `Authorization: Bearer <token>`: OAuth access token
|
||||||
|
- Standard MCP headers as per protocol specification
|
||||||
|
|
||||||
## Security
|
## Security
|
||||||
|
|
||||||
|
|
@ -505,10 +482,11 @@ redis-cli GET "session:{sessionId}:owner"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Debug Tools
|
### Debug Tools
|
||||||
- Development scripts in `scratch/` directory
|
- MCP Inspector for interactive debugging
|
||||||
- Comprehensive test suite
|
- Comprehensive test suite
|
||||||
- Hot-reload development mode
|
- Hot-reload development mode
|
||||||
- Source maps for debugging
|
- Source maps for debugging
|
||||||
|
- Redis monitoring commands
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,35 +1,32 @@
|
||||||
# MCP Standalone Authorization Server
|
# MCP Standalone Authorization Server
|
||||||
|
|
||||||
This is a demonstration OAuth 2.0 authorization server for MCP.
|
This is a demonstration OAuth 2.0 authorization server for MCP's separate authentication mode.
|
||||||
|
|
||||||
## Purpose
|
## Purpose
|
||||||
|
|
||||||
This server demonstrates how MCP servers can delegate authentication to a separate
|
This server demonstrates how MCP servers can delegate authentication to a separate authorization server. See the main [README Authentication Modes](../README.md#authentication-modes) section for a complete overview of integrated vs separate modes.
|
||||||
authorization server (Mode 2 in our implementation). In production environments,
|
|
||||||
you would typically use established OAuth providers like:
|
|
||||||
|
|
||||||
- Auth0
|
In production environments, you would typically use established OAuth providers like:
|
||||||
- Okta
|
- Auth0, Okta
|
||||||
- Google OAuth
|
- Google OAuth, GitHub OAuth
|
||||||
- GitHub OAuth
|
- Microsoft Azure AD, AWS Cognito
|
||||||
- Microsoft Azure AD
|
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
When running in separate mode, the architecture looks like:
|
For detailed architecture information and OAuth flow analysis, see:
|
||||||
|
- [Authentication Modes](../README.md#authentication-modes) - Overview and comparison
|
||||||
|
- [OAuth 2.0 + PKCE Flow Analysis](../README.md#oauth-20--pkce-flow-analysis) - Step-by-step flow breakdown
|
||||||
|
- [Authentication Architecture](../README.md#authentication-architecture) - Visual diagrams
|
||||||
|
|
||||||
1. MCP Client (e.g., Inspector) discovers auth server URL from MCP server metadata
|
This auth server specifically implements the "Auth Server" component in the separate mode architecture diagram.
|
||||||
2. Client registers and authenticates directly with this auth server
|
|
||||||
3. Auth server issues tokens
|
|
||||||
4. MCP server validates tokens by calling this auth server's introspection endpoint
|
|
||||||
|
|
||||||
## Endpoints
|
## Endpoints
|
||||||
|
|
||||||
- `/.well-known/oauth-authorization-server` - OAuth 2.0 server metadata
|
- `/.well-known/oauth-authorization-server` - OAuth 2.0 server metadata
|
||||||
- `/oauth/authorize` - Authorization endpoint
|
- `/authorize` - Authorization endpoint
|
||||||
- `/oauth/token` - Token endpoint
|
- `/token` - Token endpoint
|
||||||
- `/oauth/register` - Dynamic client registration
|
- `/register` - Dynamic client registration
|
||||||
- `/oauth/introspect` - Token introspection (for MCP server validation)
|
- `/introspect` - Token introspection (for MCP server validation)
|
||||||
- `/fakeupstreamauth/authorize` - Fake upstream auth page (demo only)
|
- `/fakeupstreamauth/authorize` - Fake upstream auth page (demo only)
|
||||||
- `/fakeupstreamauth/callback` - Fake upstream callback (demo only)
|
- `/fakeupstreamauth/callback` - Fake upstream callback (demo only)
|
||||||
- `/health` - Health check endpoint
|
- `/health` - Health check endpoint
|
||||||
|
|
@ -66,24 +63,32 @@ curl http://localhost:3001/.well-known/oauth-authorization-server
|
||||||
```
|
```
|
||||||
|
|
||||||
### With MCP Inspector
|
### With MCP Inspector
|
||||||
|
See the main [Testing with MCP Inspector](../README.md#testing-with-mcp-inspector) section for complete testing instructions for both modes.
|
||||||
|
|
||||||
|
**Quick test for this auth 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. Follow the separate mode testing steps in the main README
|
||||||
4. Connect to `http://localhost:3232/mcp`
|
|
||||||
5. Auth flow will redirect to this server (port 3001)
|
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
The auth server uses the same environment variables as the main server:
|
The auth server uses the same configuration system as the main server. See [Configuration](../README.md#configuration) in the main README for complete environment variable documentation.
|
||||||
|
|
||||||
|
**Auth server specific variables:**
|
||||||
- `AUTH_SERVER_PORT` - Port to run on (default: 3001)
|
- `AUTH_SERVER_PORT` - Port to run on (default: 3001)
|
||||||
- `AUTH_SERVER_URL` - Base URL (default: http://localhost:3001)
|
- `AUTH_SERVER_URL` - Base URL (default: http://localhost:3001)
|
||||||
- `REDIS_URL` - Redis connection (shared with MCP server)
|
- `REDIS_URL` - Redis connection (shared with MCP server)
|
||||||
|
|
||||||
## Production Considerations
|
## Production Considerations
|
||||||
|
|
||||||
In production:
|
**This server is for demonstration only.** In production, use established OAuth providers.
|
||||||
- Use real OAuth providers instead of this demonstration server
|
|
||||||
- Separate Redis instances for auth and resource servers
|
For comprehensive security and deployment guidance, see:
|
||||||
- Enable HTTPS with proper certificates
|
- [Security](../README.md#security) - Security measures and best practices
|
||||||
- Implement proper rate limiting and monitoring
|
- [Configuration](../README.md#configuration) - Environment setup
|
||||||
- Use secure client secrets and token rotation
|
- [Monitoring & Debugging](../README.md#monitoring--debugging) - Operational guidance
|
||||||
|
|
||||||
|
**Production replacement options:**
|
||||||
|
- Corporate SSO (Auth0, Okta)
|
||||||
|
- Cloud providers (AWS Cognito, Azure AD)
|
||||||
|
- Social providers (Google OAuth, GitHub OAuth)
|
||||||
|
|
@ -36,10 +36,10 @@ app.get('/health', (req, res) => {
|
||||||
mode: 'authorization-server',
|
mode: 'authorization-server',
|
||||||
endpoints: {
|
endpoints: {
|
||||||
metadata: `${AUTH_SERVER_URL}/.well-known/oauth-authorization-server`,
|
metadata: `${AUTH_SERVER_URL}/.well-known/oauth-authorization-server`,
|
||||||
authorize: `${AUTH_SERVER_URL}/oauth/authorize`,
|
authorize: `${AUTH_SERVER_URL}/authorize`,
|
||||||
token: `${AUTH_SERVER_URL}/oauth/token`,
|
token: `${AUTH_SERVER_URL}/token`,
|
||||||
register: `${AUTH_SERVER_URL}/oauth/register`,
|
register: `${AUTH_SERVER_URL}/register`,
|
||||||
introspect: `${AUTH_SERVER_URL}/oauth/introspect`
|
introspect: `${AUTH_SERVER_URL}/introspect`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -60,7 +60,7 @@ app.use(mcpAuthRouter({
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Token introspection endpoint (RFC 7662)
|
// Token introspection endpoint (RFC 7662)
|
||||||
app.post('/oauth/introspect', express.urlencoded({ extended: false }), async (req, res) => {
|
app.post('/introspect', express.urlencoded({ extended: false }), async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { token } = req.body;
|
const { token } = req.body;
|
||||||
|
|
||||||
|
|
@ -127,10 +127,10 @@ app.listen(AUTH_SERVER_PORT, () => {
|
||||||
url: AUTH_SERVER_URL,
|
url: AUTH_SERVER_URL,
|
||||||
endpoints: {
|
endpoints: {
|
||||||
metadata: `${AUTH_SERVER_URL}/.well-known/oauth-authorization-server`,
|
metadata: `${AUTH_SERVER_URL}/.well-known/oauth-authorization-server`,
|
||||||
authorize: `${AUTH_SERVER_URL}/oauth/authorize`,
|
authorize: `${AUTH_SERVER_URL}/authorize`,
|
||||||
token: `${AUTH_SERVER_URL}/oauth/token`,
|
token: `${AUTH_SERVER_URL}/token`,
|
||||||
register: `${AUTH_SERVER_URL}/oauth/register`,
|
register: `${AUTH_SERVER_URL}/register`,
|
||||||
introspect: `${AUTH_SERVER_URL}/oauth/introspect`
|
introspect: `${AUTH_SERVER_URL}/introspect`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ export class ExternalAuthVerifier implements OAuthTokenVerifier {
|
||||||
try {
|
try {
|
||||||
// Token introspection is OAuth 2.0 standard (RFC 7662) for validating tokens
|
// Token introspection is OAuth 2.0 standard (RFC 7662) for validating tokens
|
||||||
// The auth server checks if the token is valid and returns metadata about it
|
// The auth server checks if the token is valid and returns metadata about it
|
||||||
const response = await fetch(`${this.authServerUrl}/oauth/introspect`, {
|
const response = await fetch(`${this.authServerUrl}/introspect`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||||
body: `token=${encodeURIComponent(token)}`,
|
body: `token=${encodeURIComponent(token)}`,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue