MCP OAuth via Auth0 / Google

Browser login for remote MCP — Cursor and Claude Desktop can connect without pasting an API key. Works alongside client_credentials (API key → mat_ token).

Check if OIDC is live

npm run doctor:oidc
GET /v1/agent → auth.oidc
GET /mcp/auth   → oidc: "enabled" | "disabled"

When disabled, use API keys or POST /v1/oauth/token with client_credentials.

1. Auth0 application

  1. Auth0 Dashboard → Applications → Create → Regular Web Application
  2. Allowed Callback URLs:
    https://api.webmailagent.com/v1/oauth/callback
    http://127.0.0.1:8787/v1/oauth/callback
  3. Copy Domain, Client ID, Client Secret

2. Deploy secrets (wizard)

npm run wizard:auth0         # paste creds → .dev.vars
npm run wizard:auth0 -- --deploy   # + wrangler secrets + contract test

# OIDC_ISSUER example (prod tenant):
# https://webmailagent.us.auth0.com

Browser login uses stateless JWT for authorize state — no Cloudflare KV puts.

After rotating Client Secret in Auth0: update .dev.varsnpm run setup:oidc-prod.

3. Verify discovery

curl -sS https://api.webmailagent.com/.well-known/oauth-authorization-server | jq .
curl -sS -H "Authorization: Bearer $KEY" https://api.webmailagent.com/mcp/auth | jq .

npm run test:contract:qa:oidc   # prod contract (skip if still disabled)

Or: npm run setup:oidc-prod from .dev.vars after Auth0 setup.

Flow (authorization_code + PKCE)

  1. MCP client → GET /v1/oauth/authorize (PKCE challenge)
  2. Redirect to IdP login
  3. IdP → GET /v1/oauth/callback
  4. Client → POST /v1/oauth/token (authorization_code)
  5. Use mat_ access token on POST /mcp

SDK

import { MailAgent } from "@mailagent/agent";

const mail = new MailAgent({ baseUrl, apiKey });
const auth = await mail.getMcpAuth();
const { access_token } = await mail.fetchMcpAccessToken(); // client_credentials

More