Skip to content

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.

MCP CLIENTSClaudeCursorGPTAUTHGENT OAUTH ASOAuth 2.1 + PKCEDynamic Client RegConsent screenToken signing (ES256)JWKS endpointRefresh tokensYOUR MCP SERVERJWT verify onlyNo Authgent depat runtimeoptionalYOUR IDPOkta · Entra · Google

Key design principle: Authgent handles the OAuth complexity. Your MCP server only verifies JWTs — no dependency on Authgent at runtime.

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.1
Host: 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.1
Host: 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",
...
}

If the client hasn’t registered before, it self-registers via RFC 7591 DCR:

POST /oauth/register HTTP/1.1
Host: auth.yourcompany.com
Content-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.

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=xyz123

The user sees a consent screen listing the requested scopes (tool-level permissions). If an upstream IdP is configured, the user authenticates via SSO first.

After consent, the user is redirected back with an authorization code. The client exchanges it for tokens:

POST /oauth/token HTTP/1.1
Host: auth.yourcompany.com
Content-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_wW1gFWFOEjXk

Authgent 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",
});
WhatWhereWho controls it
User credentialsYour IdP (Okta, Entra, Google)You
OAuth tokensAuthgent database (PostgreSQL/SQLite)You
JWKS public keysAuthgent /.well-known/jwks.jsonYou
MCP server dataYour databaseYou

Authgent runs in your infrastructure. No data leaves your perimeter.

Authgent can delegate user authentication to your existing identity provider:

USERBrowserAUTHGENTConsent screenYOUR IDPSSO loginAUTHGENTScope approvalTOKEN ISSUEDJWT access token

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:

Terminal window
UPSTREAM_ISSUER=https://login.yourcompany.com
UPSTREAM_CLIENT_ID=authgent-app
UPSTREAM_CLIENT_SECRET=your-secret

When 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.

Terminal window
# SQLite, no IdP, open DCR
docker run -p 8080:8080 authgent/authgent:latest
Terminal window
# PostgreSQL, upstream IdP, constrained DCR
docker 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:latest

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.