Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
DEPRECATED: Replaced by mcp-app-builder. Previously used to build MCP servers with tools, resources, and prompts via mcp-use.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
references/authentication/overview.md
1# Authentication23Adding OAuth 2.0/2.1 authentication to your MCP server.45**Use for:** Protecting tools behind user authentication, accessing user identity in tool handlers, integrating with identity providers (Auth0, Better Auth, WorkOS, Supabase, Keycloak, Google, GitHub, Okta, Azure AD, and more).67> **Two integration modes.** Pick by whether your identity provider supports Dynamic Client Registration (DCR):8> - **Remote auth** (`oauthAuth0Provider`, `oauthBetterAuthProvider`, `oauthKeycloakProvider`, `oauthSupabaseProvider`, `oauthWorkOSProvider`, `oauthCustomProvider`) — clients register and authenticate directly with the upstream provider; your server only verifies the resulting bearer token. Requires DCR on the upstream.9> - **OAuth proxy** (`oauthProxy` + `jwksVerifier`) — your server holds pre-registered client credentials and mediates the token exchange. Use this for Google, GitHub, Okta, Azure AD, or any provider where you register the app in a dashboard and receive a fixed `clientId` / `clientSecret`.1011---1213## How It Works1415Pass an OAuth provider to the `oauth` option on `MCPServer`:1617```typescript18import { MCPServer } from "mcp-use/server";1920const server = new MCPServer({21name: "my-server",22version: "1.0.0",23oauth: yourProvider(), // see provider-specific guides24});25```2627This single property:28- Protects all `/mcp/*` routes with bearer token authentication29- Verifies tokens on every request (JWT + JWKS by default)30- Sets up OAuth discovery endpoints (`/.well-known/oauth-authorization-server`, `/.well-known/openid-configuration`, `/.well-known/oauth-protected-resource`)31- In proxy mode, also sets up `/register`, `/authorize`, and `/token` endpoints that mediate the upstream flow32- Populates `ctx.auth` in all tool/resource/prompt handlers3334---3536## Accessing User Context3738Every tool handler receives `ctx.auth` when OAuth is enabled:3940```typescript41server.tool(42{43name: "get-profile",44description: "Get the authenticated user's profile",45},46async (_args, ctx) =>47object({48userId: ctx.auth.user.userId,49email: ctx.auth.user.email,50name: ctx.auth.user.name,51})52);53```5455### `ctx.auth` Shape5657```typescript58ctx.auth.user // UserInfo object (see below)59ctx.auth.accessToken // Raw bearer token string60ctx.auth.scopes // string[] — parsed from JWT `scope` claim61ctx.auth.permissions // string[] — parsed from JWT `permissions` claim62ctx.auth.payload // Raw JWT payload (all claims, Record<string, unknown>)63```6465### `ctx.auth.user` (UserInfo)6667All providers populate these base fields:6869| Field | Type | Description |70|-------|------|-------------|71| `userId` | `string` | Unique user identifier (`sub` claim) |72| `email` | `string?` | User's email |73| `name` | `string?` | Display name |74| `username` | `string?` | Username |75| `nickname` | `string?` | Nickname |76| `picture` | `string?` | Avatar URL |77| `roles` | `string[]?` | User roles |78| `permissions` | `string[]?` | User permissions |7980Providers may add extra fields (e.g., WorkOS adds `organization_id`, Keycloak adds `username`, Supabase adds `aal`). Access them via `ctx.auth.user.organization_id` or `ctx.auth.payload` for raw claims.8182### `ctx.auth.payload` (Raw Claims)8384**Type:** `Record<string, unknown>` — values require explicit casts. Applies to **all providers** since `verifyToken` always returns `{ payload: Record<string, unknown> }`.8586**Prefer typed accessors** (`ctx.auth.user.*`, `ctx.auth.scopes`, `ctx.auth.permissions`) over raw payload access. If your provider has non-standard claims, map them into typed `ctx.auth.user` fields via `getUserInfo` rather than casting in every tool handler:8788```typescript89// ✅ Preferred: map claims once in getUserInfo90oauth: oauthCustomProvider({91// ...endpoints and verifyToken...92getUserInfo: (payload) => ({93userId: payload.sub as string,94email: payload.mail as string,95name: payload.display_name as string,96roles: (payload.groups as string[]) || [],97}),98})99100// Then access typed fields in tools:101async (_args, ctx) => object({ email: ctx.auth.user.email })102```103104```typescript105// ❌ Avoid: casting raw payload in every tool handler106async (_args, ctx) => {107const exp = ctx.auth.payload.exp as number; // unknown → number cast needed108return object({ expiresAt: new Date(exp * 1000).toISOString() });109}110```111112If you must read raw claims (debugging or one-off provider-specific fields), cast explicitly:113114```typescript115const exp = ctx.auth.payload.exp as number | undefined;116const customField = ctx.auth.payload.my_field as string;117```118119---120121## Zero-Config Setup122123All built-in remote-auth providers support zero-config via environment variables. Call the factory with no arguments and it reads from `MCP_USE_OAUTH_*` env vars:124125```typescript126oauth: oauthAuth0Provider() // reads MCP_USE_OAUTH_AUTH0_*127oauth: oauthWorkOSProvider() // reads MCP_USE_OAUTH_WORKOS_*128oauth: oauthSupabaseProvider() // reads MCP_USE_OAUTH_SUPABASE_*129oauth: oauthKeycloakProvider() // reads MCP_USE_OAUTH_KEYCLOAK_*130```131132Or pass config explicitly to override env vars. See each provider's guide for available options.133134`oauthProxy` and `oauthCustomProvider` have no zero-config mode — all endpoints must be passed explicitly.135136---137138## Available Providers139140### Remote auth (DCR)141142| Provider | Factory | Required Config | Guide |143|----------|---------|-----------------|-------|144| **Auth0** | `oauthAuth0Provider()` | `domain`, `audience` (env: `MCP_USE_OAUTH_AUTH0_DOMAIN`, `MCP_USE_OAUTH_AUTH0_AUDIENCE`) | [auth0.md](auth0.md) |145| **Better Auth** | `oauthBetterAuthProvider({ authURL })` | `BETTER_AUTH_SECRET` | [better-auth.md](better-auth.md) |146| **WorkOS** | `oauthWorkOSProvider()` | `subdomain` (env: `MCP_USE_OAUTH_WORKOS_SUBDOMAIN`) | [workos.md](workos.md) |147| **Supabase** | `oauthSupabaseProvider()` | `projectId` (env: `MCP_USE_OAUTH_SUPABASE_PROJECT_ID`) | [supabase.md](supabase.md) |148| **Keycloak** | `oauthKeycloakProvider()` | `serverUrl`, `realm` (env: `MCP_USE_OAUTH_KEYCLOAK_SERVER_URL`, `MCP_USE_OAUTH_KEYCLOAK_REALM`) | [keycloak.md](keycloak.md) |149| **Custom (DCR)** | `oauthCustomProvider({ ... })` | `issuer`, endpoints, `verifyToken` | [custom.md](custom.md) |150151### OAuth proxy (non-DCR)152153| Use for | Factory | Guide |154|---------|---------|-------|155| Google, GitHub, Okta, Azure AD, Auth0 (non-EA), any pre-registered app | `oauthProxy({ ... })` + `jwksVerifier({ ... })` | [custom.md](custom.md#oauth-proxy-non-dcr-providers) |156157---158159## Making Authenticated API Calls160161Use `ctx.auth.accessToken` to call your provider's API on behalf of the user:162163```typescript164server.tool(165{ name: "fetch-data", description: "Fetch user data from API" },166async (_args, ctx) => {167const res = await fetch("https://api.example.com/me", {168headers: {169Authorization: `Bearer ${ctx.auth.accessToken}`,170},171});172173if (!res.ok) {174return error(`API call failed: ${res.status}`);175}176177return object(await res.json());178}179);180```181182Provider-specific examples (Supabase, Keycloak, Auth0, etc.) live in each provider's guide.183184---185186## Common Mistakes187188- **Wrong `ctx.auth` shape** — User info is nested: `ctx.auth.user.email`, not `ctx.auth.email`189- **Using `oauthCustomProvider` for non-DCR providers** — For Google, GitHub, Okta, Azure AD, etc., use `oauthProxy` + `jwksVerifier` instead. `oauthCustomProvider` only works with providers that advertise a `registration_endpoint`.190- **Custom `verifyToken` returning the wrong shape** — It must resolve to `{ payload: Record<string, unknown> }` or throw. The proxy surfaces `payload` to `getUserInfo` and to `ctx.auth`.191- **Hardcoding provider credentials** — Use env vars; never commit secrets192- **Skipping JWT verification in production** — `verifyJwt: false` is development only193- **Throwing errors instead of returning `error()`** — Use the `error()` response helper for auth-related failures194195---196197## Next Steps198199- **Auth0 setup** → [auth0.md](auth0.md)200- **Better Auth setup** → [better-auth.md](better-auth.md)201- **WorkOS setup** → [workos.md](workos.md)202- **Supabase setup** → [supabase.md](supabase.md)203- **Keycloak setup** → [keycloak.md](keycloak.md)204- **Custom provider / OAuth proxy** → [custom.md](custom.md)205- **Build tools** → [../server/tools.md](../server/tools.md)206- **See examples** → [../patterns/common-patterns.md](../patterns/common-patterns.md)207