
OAuth Gateway
HTTP-SSEOAuth 2.1 authorization server adding authentication to MCP servers without code modification
OAuth 2.1 authorization server adding authentication to MCP servers without code modification
An OAuth 2.1 Authorization Server that adds authentication to any MCP (Model Context Protocol) server without code modification. The gateway acts as an OAuth Authorization Server while using GitHub as the Identity Provider (IdP) for user authentication.
📖 View Documentation | 🔧 Installation Guide | 🏗️ Architecture Overview
This is a reference implementation and test platform for the MCP protocol.
The MCP OAuth Gateway is a zero-modification authentication layer for MCP servers. It implements OAuth 2.1 with dynamic client registration (RFC 7591/7592) and leverages GitHub as the identity provider for user authentication. The architecture follows these core principles:
┌─────────────────────────────────────────────────────────────────────────────────────┐
│ EXTERNAL CLIENTS │
│ (Claude.ai, MCP CLI tools, IDE extensions, Custom integrations) │
└─────────────────────────────────────────────────────────────────────────────────────┘
│
HTTPS │ :443
↓
┌─────────────────────────────────────────────────────────────────────────────────────┐
│ TRAEFIK REVERSE PROXY │
│ (Layer 1: Routing & TLS Termination) │
├─────────────────────────────────────────────────────────────────────────────────────┤
│ • Let's Encrypt automatic HTTPS certificates for all subdomains │
│ • Priority-based routing rules (OAuth > Verify > MCP > Catch-all) │
│ • ForwardAuth middleware for MCP endpoints → Auth Service /verify │
│ • Request routing based on subdomain and path: │
│ - auth.domain.com/* → Auth Service (no auth required) │
│ - *.domain.com/.well-known/* → Auth Service (OAuth discovery) │
│ - *.domain.com/mcp → MCP Services (auth required via ForwardAuth) │
│ • Docker service discovery via labels │
└─────────────────────────────────────────────────────────────────────────────────────┘
│ │
│ OAuth/Auth Requests │ MCP Requests
│ (unauthenticated) │ (authenticated)
↓ ↓
┌───────────────────────────────────────────┐ ┌─────────────────────────────────────┐
│ AUTH SERVICE │ │ MCP SERVICES │
│ (Layer 2: OAuth Authorization Server) │ │ (Layer 3: Protocol Handlers) │
├───────────────────────────────────────────┤ ├─────────────────────────────────────┤
│ Container: auth:8000 │ │ Containers: │
│ Package: mcp-oauth-dynamicclient │ │ • mcp-echo:3000 │
│ │ │ • mcp-fetch:3000 │
│ OAuth Endpoints: │ │ • mcp-memory:3000 │
│ • POST /register (RFC 7591) │ │ • mcp-time:3000 │
│ • GET /authorize + /callback │ │ • ... (dynamically enabled) │
│ • POST /token │ │ │
│ • GET /.well-known/* (RFC 8414) │ │ Architecture: │
│ • POST /revoke, /introspect │ │ • mcp-streamablehttp-proxy wrapper │
│ │ │ • Spawns official MCP stdio servers │
│ Management Endpoints (RFC 7592): │ │ • Bridges stdio ↔ HTTP/SSE │
│ • GET/PUT/DELETE /register/{client_id} │ │ • No OAuth knowledge │
│ │ │ • Receives user identity in headers │
│ Internal Endpoints: │ │ │
│ • GET/POST /verify (ForwardAuth) │ │ Protocol Endpoints: │
│ │ │ • POST /mcp (JSON-RPC over HTTP) │
│ External Integration: │←---│ • GET /mcp (SSE for async messages) │
│ • GitHub OAuth (user authentication) │ │ • Health checks on /health │
└───────────────────────────────────────────┘ └─────────────────────────────────────┘
│ ↑
│ │
└──────────────┬───────────────────────────────┘
│
↓
┌─────────────────────────────────────────────────────────────────────────────────────┐
│ REDIS STORAGE LAYER │
│ (Persistent State Management) │
├─────────────────────────────────────────────────────────────────────────────────────┤
│ Container: redis:6379 │
│ Persistence: AOF + RDB snapshots │
│ │
│ Data Structures: │
│ • oauth:client:{client_id} → OAuth client registrations (90 days / eternal) │
│ • oauth:state:{state} → Authorization flow state (5 minutes) │
│ • oauth:code:{code} → Authorization codes + user info (1 year) │
│ • oauth:token:{jti} → JWT token tracking for revocation (30 days) │
│ • oauth:refresh:{token} → Refresh token data (1 year) │
│ • oauth:user_tokens:{username} → User's active tokens index │
│ • redis:session:{id}:state → MCP session state (managed by proxy) │
│ • redis:session:{id}:messages → MCP message queues │
└─────────────────────────────────────────────────────────────────────────────────────┘
NETWORK TOPOLOGY: • All services connected via 'public' Docker network • Internal service communication only (except Traefik ingress) • Redis exposed on localhost:6379 for debugging only • Each MCP service runs in isolated container with no shared state
reg-{32-byte-random-token}
using secrets.token_urlsafe(32)
oauth:client:{client_id}
The gateway implements a sophisticated OAuth 2.1 system with three distinct authentication flows:
Need Authentication?
├─> For Gateway's Own GitHub Access?
│ └─> Use Device Flow: `just generate-github-token`
│ - Shows code: "Visit github.com/login/device"
│ - No browser redirect needed
│ - Stores GITHUB_PAT in .env
│
├─> For MCP Client Token?
│ └─> Use Device Flow: `just mcp-client-token`
│ - Client uses device flow for browserless auth
│ - Stores MCP_CLIENT_ACCESS_TOKEN in .env
│
└─> For End User Access (Browser)?
└─> Use Standard OAuth Flow
- User visits protected resource
- Redirected to GitHub for login
- Redirected back to gateway
- JWT issued with user+client identity
The gateway implements this sophisticated system that combines client credential authentication with GitHub user authentication:
╔═══════════════════════════════════════════════════════════════════════════════════╗
║ OAUTH CLIENT REGISTRATION (RFC 7591/7592) ║
╠═══════════════════════════════════════════════════════════════════════════════════╣
║ ║
║ 📝 STEP 1: CLIENT REGISTRATION (No Authentication Required) ║
║ ┌─────────────────────────────────────────────────────────────────────────────┐ ║
║ │ POST /register │ ║
║ │ • Public endpoint - any MCP client can register │ ║
║ │ • Creates OAuth client application credentials │ ║
║ │ │ ║
║ │ Request Body: │ ║
║ │ { │ ║
║ │ "redirect_uris": ["https://example.com/callback"], │ ║
║ │ "client_name": "My MCP Client" │ ║
║ │ } │ ║
║ │ │ ║
║ │ Response: │ ║
║ │ • client_id: "client_abc123..." ← OAuth client credentials │ ║
║ │ • client_secret: "secret_xyz789..." ← Used at /token endpoint │ ║
║ │ • registration_access_token: "reg_tok..."← ONLY for client management │ ║
║ │ • registration_client_uri: "https://auth.../register/client_abc123" │ ║
║ └─────────────────────────────────────────────────────────────────────────────┘ ║
║ ║
║ 🔧 OPTIONAL: CLIENT MANAGEMENT (Requires registration_access_token) ║
║ ┌─────────────────────────────────────────────────────────────────────────────┐ ║
║ │ Authorization: Bearer <registration_access_token> │ ║
║ │ │ ║
║ │ • GET /register/{client_id} - View client configuration │ ║
║ │ • PUT /register/{client_id} - Update redirect URIs, etc. │ ║
║ │ • DELETE /register/{client_id} - Delete client registration │ ║
║ │ │ ║
║ │ Note: This token is ONLY for managing the client registration, │ ║
║ │ NOT for accessing MCP resources! │ ║
║ └─────────────────────────────────────────────────────────────────────────────┘ ║
║ ║
╚═══════════════════════════════════════════════════════════════════════════════════╝
↓
Client has credentials, now needs user authorization
↓
╔═══════════════════════════════════════════════════════════════════════════════════╗
║ USER AUTHENTICATION FLOW (GitHub OAuth) ║
╠═══════════════════════════════════════════════════════════════════════════════════╣
║ ║
║ 👤 STEP 2: USER AUTHORIZATION (Human authenticates via GitHub) ║
║ ┌─────────────────────────────────────────────────────────────────────────────┐ ║
║ │ GET /authorize?client_id=client_abc123&redirect_uri=...&code_challenge=... │ ║
║ │ │ ║
║ │ 1. Gateway validates client_id exists │ ║
║ │ 2. Redirects user to GitHub OAuth: │ ║
║ │ → User logs into GitHub │ ║
║ │ → GitHub authenticates the human user │ ║
║ │ → Returns to gateway /callback with GitHub user info │ ║
║ │ 3. Gateway checks ALLOWED_GITHUB_USERS whitelist │ ║
║ │ 4. Creates authorization code tied to: │ ║
║ │ • The OAuth client (client_id) │ ║
║ │ • The GitHub user (username, email, etc.) │ ║
║ │ 5. Redirects back to client with code │ ║
║ └─────────────────────────────────────────────────────────────────────────────┘ ║
║ ║
║ 🎫 STEP 3: TOKEN EXCHANGE (Client credentials + Authorization code) ║
║ ┌─────────────────────────────────────────────────────────────────────────────┐ ║
║ │ POST /token │ ║
║ │ Content-Type: application/x-www-form-urlencoded │ ║
║ │ │ ║
║ │ Request: │ ║
║ │ • client_id=client_abc123 ← Authenticates the OAuth client │ ║
║ │ • client_secret=secret_xyz789 ← Proves client identity │ ║
║ │ • code=auth_code_from_step_2 ← Contains GitHub user info │ ║
║ │ • code_verifier=pkce_verifier ← PKCE verification │ ║
║ │ │ ║
║ │ Response: │ ║
║ │ • access_token: JWT containing: │ ║
║ │ - sub: GitHub user ID │ ║
║ │ - username: GitHub username │ ║
║ │ - email: GitHub email │ ║
║ │ - client_id: client_abc123 │ ║
║ │ • refresh_token: For renewing access │ ║
║ └─────────────────────────────────────────────────────────────────────────────┘ ║
║ ║
║ 🛡️ STEP 4: RESOURCE ACCESS (Using the access token) ║
║ ┌─────────────────────────────────────────────────────────────────────────────┐ ║
║ │ Authorization: Bearer <access_token> │ ║
║ │ │ ║
║ │ • Token contains BOTH client_id AND user identity │ ║
║ │ • Traefik ForwardAuth validates token via /verify │ ║
║ │ • User identity passed to MCP services as headers: │ ║
║ │ - X-User-Id: GitHub user ID │ ║
║ │ - X-User-Name: GitHub username │ ║
║ │ • Access granted to /mcp endpoints on all enabled services │ ║
║ └─────────────────────────────────────────────────────────────────────────────┘ ║
║ ║
╚═══════════════════════════════════════════════════════════════════════════════════╝
🔑 KEY POINTS: • client_id + client_secret authenticate the OAuth CLIENT (e.g., Claude.ai) • GitHub OAuth authenticates the human USER • The final access_token combines BOTH: which client AND which user • registration_access_token is ONLY for client management, NOT resource access
MCP OAuth Gateway - OAuth 2.1 Authorization Server
registration_access_token
: Only for managing client registrations (RFC 7592)access_token
: JWT with user claims + client_id for accessing MCP resourcesrefresh_token
: For renewing access tokensGitHub OAuth - Identity Provider (IdP)
MCP Servers - Protected Resources
auth.your-domain.com
- OAuth authorization serverservice.your-domain.com
- Each MCP service subdomainYou'll need to create a GitHub OAuth App that serves two distinct purposes:
End-User Authentication (Browser-based OAuth flow)
https://auth.yourdomain.com/callback
Gateway Self-Authentication (Device flow)
just generate-github-token
# Clone the repository git clone https://github.com/atrawog/mcp-oauth-gateway.git cd mcp-oauth-gateway # Install dependencies with pixi pixi install # Run initial setup just setup
# 1. Copy the example configuration cp .env.example .env # 2. Generate required secrets just generate-all-secrets # Generates JWT secret, RSA keys, and Redis password # 3. Edit .env with your configuration nano .env
Edit your .env
file with the required configuration:
Domain Setup (REQUIRED)
BASE_DOMAIN=your-domain.com # Your actual domain ACME_EMAIL=[email protected] # Email for Let's Encrypt
GitHub OAuth App (REQUIRED)
GITHUB_CLIENT_ID=your_client_id GITHUB_CLIENT_SECRET=your_client_secret
Access Control (REQUIRED)
# Option 1: Whitelist specific users ALLOWED_GITHUB_USERS=user1,user2,user3 # Option 2: Allow any authenticated GitHub user ALLOWED_GITHUB_USERS=*
MCP OAuth Gateway
https://your-domain.com
https://auth.your-domain.com/callback
.env
file# Start all services just up # Check service health just check-health
All configuration is managed through the .env
file. The gateway uses dynamic service selection - you can enable or disable individual MCP services based on your needs.
Minimum tokens required to run the gateway:
Token | Purpose | How to Get | Required? |
---|---|---|---|
GITHUB_CLIENT_ID | GitHub OAuth App ID | Create at github.com/settings/developers | ✅ YES |
GITHUB_CLIENT_SECRET | GitHub OAuth App Secret | Create at github.com/settings/developers | ✅ YES |
GATEWAY_JWT_SECRET | JWT signing secret | Run: just generate-jwt-secret | ✅ YES |
JWT_PRIVATE_KEY_B64 | RSA key for JWT | Run: just generate-rsa-keys | ✅ YES |
REDIS_PASSWORD | Redis security | Run: just generate-redis-password | ✅ YES |
Tokens only needed for testing:
Token | Purpose | How to Get | Required? |
---|---|---|---|
GITHUB_PAT | GitHub API access in tests | Run: just generate-github-token | ❌ NO |
GATEWAY_OAUTH_* tokens | Test suite authentication | Generated during test setup | ❌ NO |
MCP_CLIENT_* tokens | Testing MCP clients | Run: just mcp-client-token | ❌ NO |
⚡ Key Point: The gateway itself doesn't use OAuth tokens - it only needs GitHub OAuth App credentials to authenticate users!
# Domain Configuration - MUST be publicly accessible domains! BASE_DOMAIN=your-domain.com ACME_EMAIL=[email protected] # GitHub OAuth App Credentials GITHUB_CLIENT_ID=your_client_id GITHUB_CLIENT_SECRET=your_client_secret # Security Keys (generated by just commands) GATEWAY_JWT_SECRET=<generated-by-just-generate-jwt-secret> # Redis Security REDIS_PASSWORD=your_secure_redis_password
# User Whitelist Options: ALLOWED_GITHUB_USERS=user1,user2,user3 # Specific users only # OR ALLOWED_GITHUB_USERS=* # Any GitHub user
# Token Lifetimes (in seconds) CLIENT_LIFETIME=7776000 # Client registration: 90 days (0 = eternal) # Note: Access and refresh token lifetimes are configured in .env
The gateway allows you to enable/disable individual MCP services. All services default to true
(enabled) if not specified.
# Core MCP Services MCP_FETCH_ENABLED=true # Web content fetching (stdio proxy) MCP_FETCHS_ENABLED=true # Native Python fetch implementation MCP_FILESYSTEM_ENABLED=true # File system access (sandboxed) MCP_MEMORY_ENABLED=true # Persistent memory/knowledge graph MCP_TIME_ENABLED=true # Time and timezone operations # Advanced MCP Services MCP_SEQUENTIALTHINKING_ENABLED=true # Structured problem solving MCP_TMUX_ENABLED=true # Terminal multiplexer integration MCP_PLAYWRIGHT_ENABLED=false # Browser automation (resource intensive) MCP_EVERYTHING_ENABLED=true # Test server with all features MCP_ECHO_ENABLED=true # Diagnostic and debugging tools
# MCP Protocol Version (used by services that support it) MCP_PROTOCOL_VERSION=2025-06-18 # Note: Some services only support specific versions: # - mcp-memory: 2024-11-05 # - mcp-sequentialthinking: 2024-11-05 # - mcp-fetch, mcp-filesystem, mcp-time: 2025-03-26 # - Others: 2025-06-18
Service | Description | Protocol Version | Container Port |
---|---|---|---|
mcp-echo | Diagnostic tools & OAuth debugging | 2025-06-18 | 3000 |
mcp-fetch | Web content fetching (stdio wrapper) | 2025-03-26 | 3000 |
mcp-fetchs | Native Python fetch implementation | 2025-06-18 | 3000 |
mcp-filesystem | File system access (sandboxed) | 2025-03-26 | 3000 |
mcp-memory | Persistent memory/knowledge graph | 2024-11-05 | 3000 |
mcp-sequentialthinking | Structured problem solving | 2024-11-05 | 3000 |
mcp-time | Time and timezone operations | 2025-03-26 | 3000 |
mcp-tmux | Terminal multiplexer integration | 2025-06-18 | 3000 |
mcp-playwright | Browser automation | 2025-06-18 | 3000 |
mcp-everything | Test server with all features | 2025-06-18 | 3000 |
All services use mcp-streamablehttp-proxy
to wrap official MCP stdio servers, exposing them via HTTP on port 3000.
The gateway adds several features not specified in the MCP protocol:
# Enable/disable tests for specific services MCP_FETCH_TESTS_ENABLED=false MCP_FETCHS_TESTS_ENABLED=false MCP_FILESYSTEM_TESTS_ENABLED=false MCP_MEMORY_TESTS_ENABLED=false MCP_PLAYWRIGHT_TESTS_ENABLED=false MCP_SEQUENTIALTHINKING_TESTS_ENABLED=false MCP_TIME_TESTS_ENABLED=false MCP_TMUX_TESTS_ENABLED=false MCP_EVERYTHING_TESTS_ENABLED=false MCP_ECHO_TESTS_ENABLED=false # Test parameters TEST_HTTP_TIMEOUT=30.0 TEST_MAX_RETRIES=3 TEST_RETRY_DELAY=1.0
.env
- It contains secrets!.env
- Keep a secure copy of your configurationjust check-health
after changes# Start all services just up # Stop all services just down # Rebuild specific service just rebuild auth just rebuild mcp-fetch just rebuild mcp-memory # View logs just logs # All services just logs auth # Specific service just logs -f traefik # Follow mode
# Generate all required secrets at once just generate-all-secrets # Or generate individually: just generate-jwt-secret # JWT signing secret just generate-rsa-keys # RSA private key just generate-redis-password # Redis password
The gateway uses GitHub Device Flow (RFC 8628) for test token generation:
# Generate test OAuth tokens using GitHub Device Flow just generate-github-token # This will: # 1. Request a device code from GitHub # 2. Display: "Visit https://github.com/login/device and enter: XXXX-XXXX" # 3. Poll GitHub until you authorize # 4. Store the GitHub PAT as GITHUB_PAT in .env # Generate MCP client token (for mcp-streamablehttp-client) just mcp-client-token # Uses device flow for browserless authentication # View OAuth registrations and tokens just oauth-show-all just oauth-list-registrations just oauth-list-tokens # Cleanup expired tokens just oauth-purge-expired
1. GitHub Device Flow (RFC 8628) - Used for:
just generate-github-token
just mcp-client-token
2. Standard OAuth Flow - Used for:
# Run all tests just test # Run specific test file just test tests/test_oauth_flow.py # Run with verbose output just test -v # Run with coverage analysis just test-sidecar-coverage # Run specific test suites just test-oauth-flow just test-mcp-protocol just test-claude-integration
# Comprehensive health check (tokens + services + endpoints) just check-health # Check only environment tokens just check-tokens # Shows which tokens are required vs testing # Check only Docker services just check-services # Quick endpoint health check just health-quick # Check SSL certificates just check-ssl
The just check-health
command now provides a comprehensive health check that:
# Backup OAuth data just oauth-backup # List backups just oauth-backup-list # Restore from latest backup just oauth-restore # View backup contents just oauth-backup-view # Cleanup test registrations just test-cleanup-show # Preview what would be deleted just test-cleanup # Actually delete test data
just check-health
# Run OAuth flow tests just test tests/test_oauth_flow.py -v # Test with a real browser # Visit: https://auth.your-domain.com/.well-known/oauth-authorization-server
# Test MCP protocol compliance just test tests/test_mcp_protocol.py -v # Test specific MCP service just test tests/test_mcp_fetch_integration.py -v
# Run tests with production coverage just test-sidecar-coverage # View HTML coverage report open htmlcov/index.html
# All services just logs # Specific service with follow just logs -f auth just logs -f traefik # Analyze OAuth flows just analyze-oauth-logs
# Check Docker status docker compose ps # Ensure network exists just network-create # Check for port conflicts sudo netstat -tlnp | grep -E ':80|:443'
# Check certificate status just check-ssl # View Traefik logs for ACME errors just logs traefik | grep -i acme
# Verify GitHub OAuth credentials just generate-github-token # Check Redis connectivity just exec redis redis-cli -a $REDIS_PASSWORD ping # Validate tokens just validate-tokens
# Check specific service health docker inspect mcp-oauth-gateway-mcp-fetch-1 --format='{{json .State.Health}}' # Test MCP endpoint directly curl -X POST https://everything.${BASE_DOMAIN}/mcp \ -H "Authorization: Bearer $GATEWAY_OAUTH_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}},"id":1}'
All development must follow the sacred commandments in CLAUDE.md
:
mcp-oauth-gateway/
├── auth/ # OAuth authorization server
├── mcp-*/ # Individual MCP services
├── traefik/ # Reverse proxy configuration
├── tests/ # Integration tests
├── scripts/ # Utility scripts
├── docs/ # Jupyter Book documentation
├── docker-compose.yml # Main compose file
├── docker-compose.*.yml # Additional compose configs
├── justfile # Command definitions
├── pixi.toml # Package management
├── .env.example # Example configuration
└── CLAUDE.md # Development commandments
Apache License 2.0 - see LICENSE file for details.
Contributions are welcome! Please:
just
for all commandsFor questions or issues, please open a GitHub issue.