> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mcp-use.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Keycloak Provider

> Configure Keycloak OAuth for your MCP server using Dynamic Client Registration

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 authorization flow, and send the resulting access token as a bearer token on MCP requests  -  **the MCP server only verifies the JWT against Keycloak's JWKS**. No OAuth traffic is proxied through your server.

<Note>
  You need a reachable Keycloak realm with DCR enabled. The provider handles everything on the MCP-server side, but Keycloak itself must permit anonymous DCR from the hosts your MCP clients will register from.
</Note>

## Setup

This guide assumes you already have a Keycloak instance running and admin access to it. If you don't, see [Keycloak's getting started guide](https://www.keycloak.org/guides#getting-started).

### 1. Enable Dynamic Client Registration

Keycloak exposes `/{realm}/clients-registrations/openid-connect` on every realm. Anonymous (no-token) registration is gated by **Client Registration Policies**:

1. **Realm settings → Client registration → Anonymous Access Policies**
2. Open the **Trusted Hosts** policy
3. Add the hostnames that MCP clients will register from (`localhost`, `127.0.0.1`) to **Trusted Hosts**
4. Make sure **Client URIs Must Match** is enabled so `redirect_uris` in the registration request are validated

<Warning>
  Browser-based 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 are blocked by CORS with `403 Invalid origin`.
</Warning>

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

### 2. Environment variables

```bash theme={null}
# Base URL of your Keycloak server  -  no trailing slash, no /realms path
MCP_USE_OAUTH_KEYCLOAK_SERVER_URL=http://localhost:8080

# Realm name
MCP_USE_OAUTH_KEYCLOAK_REALM=demo

# Optional. If set, the provider enforces that the access token's `aud` claim
# equals this value. Requires an Audience protocol mapper on the client scope.
# MCP_USE_OAUTH_KEYCLOAK_AUDIENCE=http://localhost:3000
```

## Configure the MCP server

```typescript theme={null}
// server.ts
import { MCPServer, oauthKeycloakProvider, object } from "mcp-use/server";

const server = new MCPServer({
  name: "my-server",
  version: "1.0.0",
  // Zero-config: reads MCP_USE_OAUTH_KEYCLOAK_* env vars
  oauth: oauthKeycloakProvider(),
});

server.tool(
  {
    name: "get-user-info",
    description: "Return identity info extracted from the Keycloak access token",
  },
  async (_args, ctx) =>
    object({
      userId: ctx.auth.user.userId,
      username: ctx.auth.user.username,
      email: ctx.auth.user.email,
      roles: ctx.auth.user.roles,
      permissions: ctx.auth.permissions,
      scopes: ctx.auth.scopes,
    }),
);

await server.listen(3000);
```

Or pass config explicitly:

```typescript theme={null}
oauth: oauthKeycloakProvider({
  serverUrl: "https://keycloak.example.com",
  realm: "demo",

  // Optional: required `aud` claim  -  needs an Audience mapper in Keycloak
  audience: "https://my-mcp-server.example.com/mcp",

  // Disable JWT verification during development only
  verifyJwt: process.env.NODE_ENV === "production",

  // Override advertised scopes
  // Default: ["openid", "profile", "email", "offline_access", "roles"]
  scopesSupported: ["openid", "profile", "email"],
});
```

## The flow

```
MCP Client ──(1) GET /.well-known/oauth-protected-resource   ─▶ MCP Server
MCP Client ──(2) GET /.well-known/oauth-authorization-server ─▶ MCP Server ─▶ Keycloak
MCP Client ──(3) POST /clients-registrations/openid-connect  ─▶ Keycloak      (DCR)
MCP Client ──(4) GET  /protocol/openid-connect/auth          ─▶ Keycloak      (PKCE)
MCP Client ──(5) POST /protocol/openid-connect/token         ─▶ Keycloak
MCP Client ──(6) MCP request + Bearer <token>                ─▶ MCP Server    (verifies JWT via JWKS)
```

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

## Accessing user info in tools

Keycloak puts realm roles in `realm_access.roles` and resource roles in `resource_access.{client}.roles`. The provider normalizes them onto `ctx.auth`:

```typescript theme={null}
server.tool(
  {
    name: "get-user-info",
    description: "Get authenticated user info",
  },
  async (_args, ctx) =>
    object({
      userId: ctx.auth.user.userId,
      email: ctx.auth.user.email,
      username: ctx.auth.user.username,

      // Realm roles
      roles: ctx.auth.user.roles,

      // Resource roles as "client:role" strings
      permissions: ctx.auth.permissions,

      scopes: ctx.auth.scopes,
    }),
);
```

You can also call Keycloak's userinfo endpoint with the raw access token:

```typescript theme={null}
server.tool(
  {
    name: "get-keycloak-userinfo",
    description: "Fetch the full userinfo document from Keycloak",
  },
  async (_args, ctx) => {
    const serverUrl = process.env.MCP_USE_OAUTH_KEYCLOAK_SERVER_URL!;
    const realm = process.env.MCP_USE_OAUTH_KEYCLOAK_REALM!;
    const res = await fetch(
      `${serverUrl}/realms/${realm}/protocol/openid-connect/userinfo`,
      { headers: { Authorization: `Bearer ${ctx.auth.accessToken}` } },
    );
    return object(await res.json());
  },
);
```

## Role-based access control

```typescript theme={null}
server.tool(
  {
    name: "admin-action",
    description: "Admin-only action",
  },
  async (_args, ctx) => {
    if (!ctx.auth.user.roles?.includes("admin")) {
      return {
        content: [{ type: "text", text: "Forbidden: admin role required" }],
        isError: true,
      };
    }

    // ... admin logic
    return { content: [{ type: "text", text: "Done" }] };
  },
);
```

## Production notes

* **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 in Keycloak and set `MCP_USE_OAUTH_KEYCLOAK_AUDIENCE` to the matching value. Without the mapper, tokens will be rejected.
* **Anonymous DCR.** Fine for local development, risky in production. Disable anonymous access and issue Initial Access Tokens (Realm settings → Client registration → Initial access token) that clients pass on the DCR request.
* **Transport.** Always serve Keycloak and the MCP server over HTTPS outside of local dev.
* **Scope of realm roles.** `ctx.auth.user.roles` only contains realm roles. Use `ctx.auth.permissions` (formatted as `client:role`) for per-client roles.

## Resources

* [Runnable mcp-use + Keycloak DCR example](https://github.com/mcp-use/mcp-use/tree/main/libraries/typescript/packages/mcp-use/examples/server/oauth/keycloak)
* [Keycloak Documentation](https://www.keycloak.org/documentation)
* [Keycloak Dynamic Client Registration](https://www.keycloak.org/securing-apps/client-registration)
* [Keycloak Authorization Services](https://www.keycloak.org/docs/latest/authorization_services/)

## Next Steps

* [User Context](/typescript/server/authentication/user-context)  -  Access user information in tools
