How It Works
Authgent is a standalone OAuth 2.1 authorization server purpose-built for the Model Context Protocol. It sits between MCP clients and your MCP server, handling all authorization flows so your server only needs to verify JWTs.
Architecture
Section titled “Architecture”Key design principle: Authgent handles the OAuth complexity. Your MCP server only verifies JWTs — no dependency on Authgent at runtime.
The authorization flow
Section titled “The authorization flow”Step 1: Discovery
Section titled “Step 1: Discovery”When an MCP client connects to your server, it first discovers which authorization server to use via RFC 9728 Protected Resource Metadata:
GET /.well-known/oauth-protected-resource HTTP/1.1Host: mcp.yourcompany.com
200 OK{ "resource": "https://mcp.yourcompany.com", "authorization_servers": ["https://auth.yourcompany.com"]}The client then fetches the AS metadata to discover all endpoints:
GET /.well-known/oauth-authorization-server HTTP/1.1Host: auth.yourcompany.com
200 OK{ "issuer": "https://auth.yourcompany.com", "authorization_endpoint": "https://auth.yourcompany.com/oauth/authorize", "token_endpoint": "https://auth.yourcompany.com/oauth/token", "registration_endpoint": "https://auth.yourcompany.com/oauth/register", ...}Step 2: Dynamic Client Registration
Section titled “Step 2: Dynamic Client Registration”If the client hasn’t registered before, it self-registers via RFC 7591 DCR:
POST /oauth/register HTTP/1.1Host: auth.yourcompany.comContent-Type: application/json
{ "client_name": "Claude Desktop", "redirect_uris": ["http://localhost:9876/callback"], "grant_types": ["authorization_code"], "response_types": ["code"]}Authgent returns a client_id the client uses for all subsequent requests. No manual setup per client.
Step 3: Authorization + PKCE
Section titled “Step 3: Authorization + PKCE”The client generates a PKCE code verifier and challenge, then redirects the user to Authgent’s consent screen:
GET /oauth/authorize ?response_type=code &client_id=dyn_abc123 &redirect_uri=http://localhost:9876/callback &scope=mcp:read mcp:write &code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM &code_challenge_method=S256 &state=xyz123The user sees a consent screen listing the requested scopes (tool-level permissions). If an upstream IdP is configured, the user authenticates via SSO first.
Step 4: Token exchange
Section titled “Step 4: Token exchange”After consent, the user is redirected back with an authorization code. The client exchanges it for tokens:
POST /oauth/token HTTP/1.1Host: auth.yourcompany.comContent-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=auth_code_here&redirect_uri=http://localhost:9876/callback&client_id=dyn_abc123&code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXkAuthgent returns a signed JWT access token and a refresh token:
{ "access_token": "eyJhbGciOiJFUzI1NiIs...", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "rt_abc123..."}Step 5: JWT verification in your MCP server
Section titled “Step 5: JWT verification in your MCP server”Your MCP server validates the JWT using Authgent’s JWKS endpoint. This is the only integration point — your server fetches the public keys once and verifies tokens offline.
Python (FastMCP):
from fastmcp.server.auth import RemoteAuthProvider
auth = RemoteAuthProvider( authorization_server_url="https://auth.yourcompany.com", audience="https://mcp.yourcompany.com",)Go:
v, _ := verifier.New(verifier.Config{ Issuer: "https://auth.yourcompany.com", Audience: "https://mcp.yourcompany.com", JWKSURL: "https://auth.yourcompany.com/.well-known/jwks.json",})
mux.Handle("/mcp", v.RequireAuth()(handler))TypeScript:
import { createRemoteJWKSet, jwtVerify } from "jose";
const JWKS = createRemoteJWKSet( new URL("https://auth.yourcompany.com/.well-known/jwks.json"));
const { payload } = await jwtVerify(token, JWKS, { issuer: "https://auth.yourcompany.com", audience: "https://mcp.yourcompany.com",});Data flow
Section titled “Data flow”| What | Where | Who controls it |
|---|---|---|
| User credentials | Your IdP (Okta, Entra, Google) | You |
| OAuth tokens | Authgent database (PostgreSQL/SQLite) | You |
| JWKS public keys | Authgent /.well-known/jwks.json | You |
| MCP server data | Your database | You |
Authgent runs in your infrastructure. No data leaves your perimeter.
Upstream IdP federation
Section titled “Upstream IdP federation”Authgent can delegate user authentication to your existing identity provider:
Supported providers:
- Okta — OIDC federation
- Microsoft Entra ID — OIDC federation
- Google Workspace — OIDC federation
- Any OIDC provider — Standard OIDC discovery
Configure with a single environment variable:
UPSTREAM_ISSUER=https://login.yourcompany.comUPSTREAM_CLIENT_ID=authgent-appUPSTREAM_CLIENT_SECRET=your-secretWhen an upstream IdP is configured, Authgent’s consent screen redirects unauthenticated users to the IdP first. After SSO authentication, the user returns to Authgent to approve the MCP client’s scope request.
Deployment topology
Section titled “Deployment topology”Minimal (development)
Section titled “Minimal (development)”# SQLite, no IdP, open DCRdocker run -p 8080:8080 authgent/authgent:latestProduction
Section titled “Production”# PostgreSQL, upstream IdP, constrained DCRdocker run -p 8080:8080 \ -e DATABASE_URL=postgres://user:pass@db:5432/authgent \ -e ISSUER=https://auth.yourcompany.com \ -e UPSTREAM_ISSUER=https://login.yourcompany.com \ -e UPSTREAM_CLIENT_ID=authgent \ -e UPSTREAM_CLIENT_SECRET=secret \ -e DCR_MODE=constrained \ authgent/authgent:latestHigh availability
Section titled “High availability”Run multiple Authgent instances behind a load balancer. All instances share the same PostgreSQL database. JWT verification is stateless — your MCP servers don’t need to talk to Authgent at all.
See the Docker deployment guide and Kubernetes guide for full instructions.