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 ChatGPT apps with interactive React widgets via mcp-use.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
references/authentication/keycloak.md
1# Keycloak Authentication23Setting up OAuth with Keycloak. Keycloak exposes full OAuth 2.1 + OIDC endpoints on every realm, including native [Dynamic Client Registration](https://www.keycloak.org/securing-apps/client-registration) (RFC 7591). MCP clients discover Keycloak via `.well-known` metadata, register themselves, complete a PKCE flow, and send the access token as a bearer on MCP requests — **the MCP server only verifies the JWT against Keycloak's JWKS**.45**Learn more:** [Keycloak Documentation](https://www.keycloak.org/documentation) · [Keycloak DCR](https://www.keycloak.org/securing-apps/client-registration) · [Standalone starter template](https://github.com/mcp-use/mcp-oauth-keycloak-template) · [Runnable example](https://github.com/mcp-use/mcp-use/tree/main/libraries/typescript/packages/mcp-use/examples/server/oauth/keycloak)67---89## Quick Start1011```typescript12import { MCPServer, oauthKeycloakProvider, object } from "mcp-use/server";1314const server = new MCPServer({15name: "my-server",16version: "1.0.0",17oauth: oauthKeycloakProvider(), // reads MCP_USE_OAUTH_KEYCLOAK_SERVER_URL + _REALM18});1920server.tool(21{ name: "whoami", description: "Get authenticated user info" },22async (_args, ctx) =>23object({24userId: ctx.auth.user.userId,25username: ctx.auth.user.username,26email: ctx.auth.user.email,27roles: ctx.auth.user.roles,28permissions: ctx.auth.permissions,29scopes: ctx.auth.scopes,30})31);3233server.listen();34```3536With a `.env` file:3738```bash39MCP_USE_OAUTH_KEYCLOAK_SERVER_URL=http://localhost:808040MCP_USE_OAUTH_KEYCLOAK_REALM=demo41```4243---4445## Setup4647This guide assumes you already have a Keycloak instance running with admin access. If not, see [Keycloak's getting started guide](https://www.keycloak.org/guides#getting-started).4849### 1. Enable Dynamic Client Registration5051Keycloak exposes `/{realm}/clients-registrations/openid-connect` on every realm. Anonymous (no-token) registration is gated by **Client Registration Policies**:52531. **Realm settings → Client registration → Anonymous Access Policies**542. Open the **Trusted Hosts** policy553. Add the hostnames MCP clients will register from (`localhost`, `127.0.0.1`) to **Trusted Hosts**564. Make sure **Client URIs Must Match** is enabled so `redirect_uris` in the registration request are validated5758> **Browser MCP clients** (like the Inspector) also need the **Allowed Registration Web Origins** policy (Keycloak 26.6+) listing every origin the client runs from. Without it, DCR requests fail with CORS `403 Invalid origin`.5960For non-localhost redirect URIs, mint an **Initial Access Token** (Realm settings → Client registration → Initial access token) and have clients pass it on the DCR POST.6162### 2. (Optional) Audience enforcement6364Keycloak doesn't set `aud` to the resource server by default. To require it:65661. Add an **Audience** protocol mapper to a client scope in Keycloak.672. Set `MCP_USE_OAUTH_KEYCLOAK_AUDIENCE` to the matching value.6869Without the mapper, tokens will be rejected if `audience` is configured.7071---7273## Environment Variables7475| Variable | Required | Description |76|----------|----------|-------------|77| `MCP_USE_OAUTH_KEYCLOAK_SERVER_URL` | Yes | Base URL of your Keycloak server (no trailing slash, no `/realms` path) |78| `MCP_USE_OAUTH_KEYCLOAK_REALM` | Yes | Realm name (e.g. `demo`) |79| `MCP_USE_OAUTH_KEYCLOAK_AUDIENCE` | No | If set, enforced as the access token's `aud` claim. Requires an Audience protocol mapper. |8081---8283## Configuration Options8485Zero-config (reads from env vars):8687```typescript88oauth: oauthKeycloakProvider()89```9091Explicit config:9293```typescript94oauth: oauthKeycloakProvider({95serverUrl: "https://keycloak.example.com",96realm: "demo",97audience: "https://my-mcp-server.example.com/mcp", // optional98verifyJwt: process.env.NODE_ENV === "production", // default: true99scopesSupported: ["openid", "profile", "email"], // override advertised scopes100})101```102103| Option | Type | Default | Description |104|--------|------|---------|-------------|105| `serverUrl` | `string` | env var | Base URL of your Keycloak server |106| `realm` | `string` | env var | Realm name |107| `audience` | `string?` | env var | Required `aud` claim — needs an Audience mapper in Keycloak |108| `verifyJwt` | `boolean?` | `true` | Set `false` to skip JWT verification (**development only**) |109| `scopesSupported` | `string[]?` | `["openid", "profile", "email", "offline_access", "roles"]` | Override advertised scopes |110111---112113## The Flow114115```116MCP Client ──(1) GET /.well-known/oauth-protected-resource ─▶ MCP Server117MCP Client ──(2) GET /.well-known/oauth-authorization-server ─▶ MCP Server ─▶ Keycloak118MCP Client ──(3) POST /clients-registrations/openid-connect ─▶ Keycloak (DCR)119MCP Client ──(4) GET /protocol/openid-connect/auth ─▶ Keycloak (PKCE)120MCP Client ──(5) POST /protocol/openid-connect/token ─▶ Keycloak121MCP Client ──(6) MCP request + Bearer <token> ─▶ MCP Server (verifies JWT via JWKS)122```123124Step 2 is a passthrough from the MCP server back to Keycloak's metadata — it's what tells the client where to register and where to send the user for login. Everything else goes directly to Keycloak.125126---127128## User Context129130Keycloak puts realm roles in `realm_access.roles` and resource roles in `resource_access.{client}.roles`. The provider normalizes them onto `ctx.auth`:131132| Field | Type | Source |133|-------|------|--------|134| `userId` | `string` | `sub` claim |135| `email` | `string?` | `email` claim |136| `name` | `string?` | `name` claim |137| `username` | `string?` | `preferred_username` claim |138| `roles` | `string[]?` | `realm_access.roles` — **realm roles only** |139| `permissions` | `string[]?` | `resource_access.{client}.roles` — as `"client:role"` strings |140| `scopes` | `string[]?` | Parsed from `scope` claim |141142### Role-Based Access Control143144```typescript145server.tool(146{ name: "admin-action", description: "Admin-only action" },147async (_args, ctx) => {148if (!ctx.auth.user.roles?.includes("admin")) {149return error("Forbidden: admin role required");150}151152// ... admin logic153return text("Done");154}155);156```157158For per-client roles, check `ctx.auth.permissions` (formatted as `client:role`).159160---161162## Making Keycloak API Calls163164Call Keycloak's userinfo endpoint with the raw access token:165166```typescript167server.tool(168{169name: "get-keycloak-userinfo",170description: "Fetch the full userinfo document from Keycloak",171},172async (_args, ctx) => {173const serverUrl = process.env.MCP_USE_OAUTH_KEYCLOAK_SERVER_URL!;174const realm = process.env.MCP_USE_OAUTH_KEYCLOAK_REALM!;175176const res = await fetch(177`${serverUrl}/realms/${realm}/protocol/openid-connect/userinfo`,178{ headers: { Authorization: `Bearer ${ctx.auth.accessToken}` } }179);180181return object(await res.json());182}183);184```185186---187188## Example `.env`189190```bash191# Required: base URL of your Keycloak server — no trailing slash, no /realms path192MCP_USE_OAUTH_KEYCLOAK_SERVER_URL=http://localhost:8080193194# Required: realm name195MCP_USE_OAUTH_KEYCLOAK_REALM=demo196197# Optional: if set, the provider enforces that the token's `aud` claim equals198# this value. Requires an Audience protocol mapper on the client scope.199# MCP_USE_OAUTH_KEYCLOAK_AUDIENCE=http://localhost:3000200```201202---203204## Production Notes205206- **Audience enforcement.** Keycloak doesn't set `aud` to the resource server by default. To require it, add an *Audience* protocol mapper to the client scope and set `MCP_USE_OAUTH_KEYCLOAK_AUDIENCE`.207- **Anonymous DCR.** Fine for local dev, risky in production. Disable anonymous access and issue Initial Access Tokens that clients pass on the DCR request.208- **Transport.** Always serve Keycloak and the MCP server over HTTPS outside of local dev.209- **Scope of realm roles.** `ctx.auth.user.roles` only contains realm roles. Use `ctx.auth.permissions` (formatted as `client:role`) for per-client roles.210211---212213## Next Steps214215- **Auth overview** → [overview.md](overview.md)216- **Custom providers + OAuth proxy reference** → [custom.md](custom.md)217- **WorkOS setup** → [workos.md](workos.md)218- **Build tools** → [../server/tools.md](../server/tools.md)219