Central Authentication Server for MCP
Uses Supabase (PostgreSQL) as the backend database for the authentication server, storing MCP clients, token events, authorization codes, revoked tokens, and admin users with Row Level Security enabled.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Central Authentication Server for MCPissue a new static API key for my MCP client"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Central Authentication Server for MCP (Model Context Protocol)
A lightweight, production-ready Central Authentication Server built specifically for Model Context Protocol (MCP) ecosystems, migrated to Python + FastAPI and backed by Supabase (PostgreSQL).
Migration Overview: What Changed & What Did NOT Change
What Changed:
Backend Framework: Migrated from Node.js (Express) to Python 3.12+ (FastAPI + Pydantic + Uvicorn).
Database Engine: Migrated from single-file SQLite to Supabase (PostgreSQL) using the official
supabase-pyclient.Row Level Security (RLS): RLS is enabled on all tables (
mcp_clients,token_events,auth_codes,revoked_tokens,admin_users), completely blocking publicanonaccess while allowing privileged backend operations through the Supabaseservice_rolekey.Offline / Local Fallback: The database layer seamlessly supports both live Supabase and local/offline fallback, ensuring automated tests and development work out-of-the-box.
What Did NOT Change (100% Contract Preservation):
Zero Route / Contract Changes: All endpoint paths (
/.well-known/...,/register,/authorize,/token,/introspect,/revocations,/admin/...) and HTTP methods remain identical.Identical JSON Shapes & Status Codes: Request and response payloads are 100% identical. No MCP server or LLM client configuration requires changes.
RS256 JWT Token Structure: Tokens use the exact same claims (
iss,sub,aud,client_id,auth_mode,jti,iat,exp) and deterministickidsigning.Dual Auth Modes:
Mode 1: Full OAuth 2.1 flow (RFC 8414 Discovery, RFC 7591 Dynamic Client Registration, mandatory SHA-256 PKCE authorization code exchange, client credentials).
Mode 2: Static long-lived API key fallback for header-based LLM configs (
headers: { "x-api-key": "..." }).
Admin Control Center UI: The responsive dark glassmorphic dashboard in
public/admin/operates identically against the FastAPI backend.Verification Middleware: The drop-in MCP verification middleware in Python (
sdk/python/mcp_auth_middleware.py) and Node.js (sdk/node/mcp-auth-middleware.js) remain 100% drop-in compatible.
Related MCP server: mcpauth
Architecture Diagram
+-------------------------------------------------------------+
| CENTRAL AUTH SERVER (FastAPI + Python) |
| |
| [Mode 1: OAuth 2.1] [Discovery & Keys] |
| - /.well-known/oauth-... - /.well-known/jwks.json |
| - /register (RFC 7591) - /revocations /introspect |
| - /authorize (PKCE S256) |
| - /token (auth_code & m2m) [Admin Dashboard] |
| - MCP Client Management |
| [Key Management & Crypto] - Static Token Generation |
| - RS256 Private Key Signing - Audit Logging |
| - bcrypt Secret Hashing - Supabase (Postgres with RLS)|
+-------------------------------------------------------------+
▲ ▲
│ (1. Auth / Token) │ (2. Fetch JWKS /
│ │ Revocations)
+-------------------+------+ +----------+-------------------+
| LLM CLIENTS | | ANY MCP SERVER |
| | | |
| Claude Desktop / Cursor | | Drops in 1 Middleware: |
| ChatGPT / Gemini / Agents| Token | McpAuthMiddleware( |
| Mode 1: OAuth 2.1 (PKCE) | =======> | jwks_uri=".../jwks.json", |
| Mode 2: Static Token | (Header) | audience="mcp-invoicing" |
| (Both emit RS256 JWT) | | ) |
+--------------------------+ | -> ZERO LLM-SPECIFIC CODE! |
+------------------------------+Supabase Setup Guide
1. Create a Supabase Project
Log in to Supabase and create a new project.
Under Project Settings -> API, copy:
Project URL: e.g.,
https://xyzcompany.supabase.coService Role Key (
secret):eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...(never expose this key in client-side code).
2. Execute SQL Schema
Navigate to the SQL Editor in your Supabase dashboard and run the contents of supabase/schema.sql:
Creates
mcp_clients,token_events,auth_codes,revoked_tokens, andadmin_users.Configures indexes and unique constraints.
Enables Row Level Security (RLS) on all tables and revokes direct table access from
anon.
3. Configure Environment Variables
Update .env with your Supabase credentials:
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...4. Database Migrations for Existing Deployments
When pulling repository updates on an existing deployment, execute any pending incremental migration files in supabase/migrations/ in the Supabase SQL Editor:
001_add_current_static_token_jti.sql: Addscurrent_static_token_jtitomcp_clientsfor individual static token rotation tracking and invalidation.
Always apply pending migrations insupabase/migrations/ when updating a production Central Auth deployment to ensure the database schema remains synchronized with backend query models.
5. (Optional) Migrate Existing SQLite Data
If you had an existing SQLite database at data/auth.db, import all existing records directly into Supabase:
python scripts/migrate_sqlite_to_supabase.pyEnvironment Variables Reference
Variable | Default | Description | Status |
|
| Supabase project URL ( | Added |
|
| Supabase service role key with RLS bypass | Added |
|
| Alias fallback for service role key | Added |
|
| Port for FastAPI / Uvicorn server | Retained |
|
| Public URL for token issuance and discovery | Retained |
|
| Enforce HTTPS across all endpoints | Retained |
|
| SQLite migration source / offline fallback | Retained |
|
| Directory storing generated RSA keypair | Retained |
|
| Optional RSA private key in PEM format | Retained |
|
| Optional RSA public key in PEM format | Retained |
|
| Initial admin username | Retained |
|
| Initial admin password | Retained |
|
| Secret used to sign admin session tokens | Retained |
|
| Mode 1 OAuth access token lifespan (seconds) | Retained |
|
| Mode 2 static token lifespan (days) | Retained |
|
| PKCE authorization code lifespan (seconds) | Retained |
Adding a new MCP Server (Developer Workflow)
Database Schema Prerequisite: Ensure all schema migrations from supabase/migrations/ (such as 001_add_current_static_token_jti.sql) have been applied to your database so client listing, token rotation, and deletion operate seamlessly.
Step 1: Register MCP in the Admin Console
Open
http://localhost:3000/admin.Click "Register New MCP".
Provide the Server Name (e.g.
Invoicing Service) and Audience (e.g.mcp-invoicing).Copy the auto-generated Mode 2 Static Token or note the client credentials for Mode 1.
Step 2: Drop Middleware into Your MCP Server
In Python (FastAPI / Starlette)
Copy sdk/python/mcp_auth_middleware.py into your project:
from fastapi import FastAPI, Request
from mcp_auth_middleware import McpAuthMiddleware
app = FastAPI()
# -------------------------------------------------------------
# THE ONLY AUTH CODE YOU EVER WRITE IN ANY MCP SERVER:
# -------------------------------------------------------------
app.add_middleware(
McpAuthMiddleware,
jwks_uri="http://localhost:3000/.well-known/jwks.json",
audience="mcp-invoicing"
)
# -------------------------------------------------------------
@app.post("/tools/list")
async def list_tools(request: Request):
# request.state.auth contains verified JWT claims
# request.state.mcp_client_id contains client identifier
return {"tools": [{"name": "generate_invoice"}]}In Node.js (Express)
Copy sdk/node/mcp-auth-middleware.js into your project (zero external dependencies):
const express = require('express');
const { createMcpAuthMiddleware } = require('./mcp-auth-middleware');
const app = express();
app.use(express.json());
app.use(createMcpAuthMiddleware({
jwksUri: 'http://localhost:3000/.well-known/jwks.json',
audience: 'mcp-invoicing'
}));
app.post('/tools/list', (req, res) => {
res.json({ tools: [{ name: 'generate_invoice' }] });
});Migration Verification Checklist
The migrated system was tested against the exact same test cases as the original server:
Endpoint / Feature | Method | Status | Verification Detail |
Server Metadata |
| Verified | RFC 8414 metadata, mandates |
Resource Metadata |
| Verified | Protected Resource Metadata (PRM) response |
JWKS Endpoint |
| Verified | RFC 7517 public keys in RS256 format |
Admin Login |
| Verified | Bcrypt verification, issues Admin JWT session |
Admin Stats |
| Verified | Total clients, active clients, event count |
Create MCP Client |
| Verified | Client ID & Secret (shown ONCE) + Mode 2 Token |
Dynamic Registration |
| Verified | RFC 7591 DCR, returns 201 with client metadata |
OAuth 2.1 Authorize |
| Verified | Enforces PKCE S256, redirects with 302 and code |
Token Exchange (PKCE) |
| Verified | Validates |
PKCE Verifier Check |
| Verified | Rejects invalid code verifier (400 Bad Request) |
Code Replay Protection |
| Verified | Rejects reused authorization code (400 Bad Request) |
Client Credentials |
| Verified | Machine-to-machine M2M RS256 token issuance |
Token Introspection |
| Verified | RFC 7662 returns active: true/false |
Revocation Check |
| Verified | Returns live list of revoked client IDs |
Admin Revoke |
| Verified | Immediately invalidates Mode 1 and Mode 2 tokens |
Static Token Gen |
| Verified | Issues fresh 90-day RS256 JWT |
Audit Logging |
| Verified | Records all issuances, revocations, and failures |
Middleware (Python) | ASGI Dispatch | Verified | Validates JWKS signature, aud, exp, revocation |
Middleware (Node) | Express Handler | Verified | Compatible with tokens from migrated server |
Running the Application
1. Set up Virtual Environment
uv venv .venv
uv pip install -r requirements.txt2. Run Automated Test Suite
.venv\Scripts\pytest tests/test_auth.py
# or
.venv\Scripts\python tests/test_auth.pyExecutes all 8 test suites validating 100% feature parity with 100% pass rate.
3. Start the FastAPI Central Auth Server
.venv\Scripts\uvicorn src_py.main:app --port 3000 --reloadOpen http://localhost:3000/admin to access the Control Center.
Docker Deployment
The Central Auth Server includes a multi-stage production Dockerfile (python:3.12-slim) and a docker-compose.yml for containerized hosting.
1. Environment Configuration
Create your .env file from the example template:
cp .env.example .envEnsure the following variables are configured:
SUPABASE_URL&SUPABASE_SERVICE_ROLE_KEY: Your Supabase project credentials.ADMIN_PASSWORD&ADMIN_JWT_SECRET: Strong secret credentials for operator console sessions.REQUIRE_HTTPS=true: Mandatory for production deployments.PORT=8000: Container application port.WEB_CONCURRENCY=4: Number of uvicorn worker processes (defaults to2).
2. Run with Docker Compose (Recommended)
docker-compose.yml automatically mounts a persistent named volume for the .keys/ directory.
RSA Keypair Volume Persistence: The .keys/ directory holds the 2048-bit RSA private and public keypair. Persisting this directory via the mcp_keys Docker volume ensures that your server's keypair remains constant across container restarts and redeployments. Regenerating keys on restart would immediately invalidate all issued 90-day static tokens and active JWTs.
Start the service in the background:
docker compose up -d --buildCheck health and logs:
docker compose ps
docker compose logs -f central-auth-mcpTo stop the service:
docker compose down3. Build & Run Manually with Docker CLI
If you prefer running without compose:
# 1. Create a dedicated named volume for RSA keys
docker volume create central_auth_keys
# 2. Build the production image
docker build -t central-auth-mcp:latest .
# 3. Run container as non-root user with persistent volume
docker run -d \
--name central-auth-mcp \
--restart unless-stopped \
-p 8000:8000 \
--env-file .env \
-v central_auth_keys:/app/.keys \
central-auth-mcp:latestProduction VPS Deployment (non-Docker)
For standard Linux VPS hosting (Ubuntu / Debian / RHEL), run the server as a system service managed by systemd to provide automatic recovery on crash or reboot.
1. Install & Configure Application
# 1. Create a dedicated system user
sudo useradd -r -s /bin/false -d /opt/central-auth-mcp mcpuser
# 2. Clone and set up repository in /opt
sudo git clone <REPO_URL> /opt/central-auth-mcp
cd /opt/central-auth-mcp
# 3. Create virtual environment and install dependencies
sudo python3 -m venv .venv
sudo /opt/central-auth-mcp/.venv/bin/pip install --no-cache-dir -r requirements.txt
# 4. Configure production environment
sudo cp .env.example .env
sudo nano .env # Set REQUIRE_HTTPS=true, Supabase credentials, strong passwords
# 5. Set directory ownership and key permissions
sudo chown -R mcpuser:mcpuser /opt/central-auth-mcp
sudo chmod 700 /opt/central-auth-mcp/.keys2. Systemd Service Unit File
Create /etc/systemd/system/central-auth-mcp.service:
[Unit]
Description=Central Authentication Server for MCP (FastAPI + Supabase)
After=network.target
[Service]
Type=simple
User=mcpuser
Group=mcpuser
WorkingDirectory=/opt/central-auth-mcp
EnvironmentFile=/opt/central-auth-mcp/.env
ExecStart=/opt/central-auth-mcp/.venv/bin/uvicorn src_py.main:app --host 127.0.0.1 --port 8000 --workers 4
Restart=always
RestartSec=3s
KillMode=process
# Security sandboxing
ProtectSystem=full
ProtectHome=true
NoNewPrivileges=true
PrivateTmp=true
[Install]
WantedBy=multi-user.target3. Enable and Start Service
sudo systemctl daemon-reload
sudo systemctl enable central-auth-mcp
sudo systemctl start central-auth-mcp
# Verify service status
sudo systemctl status central-auth-mcpReverse Proxy & HTTPS
In production, terminate TLS using an Nginx reverse proxy running on the host. Nginx handles SSL/TLS termination and proxies HTTP requests to Uvicorn on 127.0.0.1:8000.
1. Nginx Configuration
Create /etc/nginx/sites-available/central-auth-mcp:
# HTTP - Redirect all traffic to HTTPS
server {
listen 80;
listen [::]:80;
server_name auth.example.com;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
# HTTPS - Terminate TLS and proxy to FastAPI Uvicorn
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name auth.example.com;
# SSL Certificates (managed by Certbot)
ssl_certificate /etc/letsencrypt/live/auth.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/auth.example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
# Security Headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options nosniff always;
add_header X-Frame-Options DENY always;
# Proxy to Central Auth Uvicorn process
location / {
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;
# CRITICAL HEADERS:
# The central auth server reads X-Forwarded-Proto to enforce HTTPS
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_connect_timeout 10s;
proxy_read_timeout 60s;
}
}Enable site configuration:
sudo ln -s /etc/nginx/sites-available/central-auth-mcp /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx2. Obtain Free SSL Certificate via Let's Encrypt (Certbot)
sudo apt update
sudo apt install -y certbot python3-certbot-nginx
# Obtain certificate and let Certbot configure Nginx automatically
sudo certbot --nginx -d auth.example.com
# Verify auto-renewal timer
sudo systemctl status certbot.timer
sudo certbot renew --dry-runOAuth 2.1 Flow — Verified
The full OAuth 2.1 PKCE authorization code flow has been verified end-to-end against the live server. You can mirror the automated test manually using curl:
Step 1: Dynamic Client Registration (RFC 7591)
Register your client dynamically to receive a client_id and client_secret:
curl -X POST https://auth.example.com/register \
-H "Content-Type: application/json" \
-d '{
"client_name": "Claude Desktop Agent",
"redirect_uris": ["http://localhost:8080/callback"],
"audience": "mcp-filesystem"
}'Output:
{
"client_id": "mcp_claude-desktop_a1b2c3d4",
"client_secret": "mcp_sec_...",
"audience": "mcp-filesystem",
"redirect_uris": ["http://localhost:8080/callback"],
"grant_types": ["authorization_code", "client_credentials"]
}Step 2: Generate PKCE S256 Code Verifier & Challenge
In Bash, generate a random 43-128 character verifier and its S256 SHA-256 base64url challenge:
# Generate 64-byte random verifier
VERIFIER=$(openssl rand -base64 48 | tr -d '=+/' | cut -c1-64)
# Compute S256 challenge: Base64URL(SHA256(verifier)) without padding
CHALLENGE=$(printf %s "$VERIFIER" | openssl dgst -sha256 -binary | openssl base64 -e | tr '+/' '-_' | tr -d '=')
echo "Verifier: $VERIFIER"
echo "Challenge: $CHALLENGE"Step 3: Authorization Request (/authorize)
Send the user or agent browser to the authorization endpoint:
curl -i -G "https://auth.example.com/authorize" \
--data-urlencode "response_type=code" \
--data-urlencode "client_id=mcp_claude-desktop_a1b2c3d4" \
--data-urlencode "redirect_uri=http://localhost:8080/callback" \
--data-urlencode "code_challenge=$CHALLENGE" \
--data-urlencode "code_challenge_method=S256" \
--data-urlencode "state=secure_random_state_123"Response: Returns HTTP 302 Found with redirect target containing the single-use authorization code:
HTTP/2 302
Location: http://localhost:8080/callback?code=ac_8f9a2b4c6e1d&state=secure_random_state_123Step 4: Token Exchange (/token)
Exchange the authorization code for an RS256 Bearer JWT by providing the original code_verifier:
curl -X POST https://auth.example.com/token \
-H "Content-Type: application/json" \
-d '{
"grant_type": "authorization_code",
"code": "ac_8f9a2b4c6e1d",
"redirect_uri": "http://localhost:8080/callback",
"code_verifier": "'"$VERIFIER"'",
"client_id": "mcp_claude-desktop_a1b2c3d4",
"client_secret": "mcp_sec_..."
}'Response:
{
"access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6Im1jcC1hdXRoLTIwMjYtMDEiLCJ0eXAiOiJKV1QifQ...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "mcp:all"
}Step 5: Verify Token Signature Locally via JWKS
Any MCP server can immediately verify the token offline using the public RS256 key:
curl https://auth.example.com/.well-known/jwks.jsonProduction Checklist
Before exposing the Central Auth Server to production traffic, verify each item:
REQUIRE_HTTPS=truein.env: Enforces HTTPS and rejects unencrypted connections.ADMIN_PASSWORDupdated: Changed fromadmin-mcp-secret-2026to a high-entropy passphrase.ADMIN_JWT_SECRETupdated: Changed from default string to a 32+ character random secret..keys/directory backed up & persisted: Mounted to a persistent Docker named volume or stored outside disposable deploy paths on VPS.Supabase
service_rolekey protected: Stored strictly in server.env, never committed to Git, never exposed to clients.Rate limiting configured: Verified
RATE_LIMIT_WINDOW_MSandRATE_LIMIT_MAX_REQUESTSmatch expected traffic capacity.Auto-restart confirmed:
systemdservice (Restart=always) or Docker container restart policy (restart: unless-stopped) active and tested across system reboots.Healthcheck monitored:
/healthendpoint responding with{"status": "ok"}and integrated into external uptime monitors.
This server cannot be deployed
Maintenance
Related MCP Connectors
MCP server for mandates, delegation, policy-gated execution, credential grants, and audit.
Self-hosted federated MCP gateway: one OAuth 2.1 MCP server in front of N apps, user-level scopes.
Hosted MCP server with managed OAuth for 15+ toolkits: Google Workspace, Fitbit, Oura, Kalshi, etc.
MCP server for Argo RPG Platform — connects AI assistants to campaign data via OAuth2
Related MCP Servers
- AlicenseAqualityDmaintenanceMCP server for enterprise authentication and authorization — JWT validation, OIDC token inspection, OAuth 2.0 introspection, and role-based access control for AI agents.8MIT
- AlicenseNot gradedqualityFmaintenanceDrop-in OAuth 2.1 + Dynamic Client Registration for MCP servers, providing authentication middleware and token verification.13 npm1MIT
- AlicenseNot gradedqualityDmaintenanceDrop-in OAuth 2.1 + Dynamic Client Registration token verification for Python MCP servers, backed by mcpauth.MIT
- AlicenseNot gradedqualityAmaintenanceAuthentication and authorization plugin for appium-mcp, adding bearer API keys, OAuth JWT, session tokens, scope-based authorization, rate limiting, and per-session ownership when running over SSE/HTTP Stream.13 npm5Apache 2.0