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 exposes full OAuth 2.1 + OIDC endpoints on every realm, including native Dynamic 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.
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.
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.
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:
- Realm settings → Client registration → Anonymous Access Policies
- Open the Trusted Hosts policy
- Add the hostnames that MCP clients will register from (
localhost, 127.0.0.1) to Trusted Hosts
- Make sure Client URIs Must Match is enabled so
redirect_uris in the registration request are validated
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.
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
# 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
// 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:
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.
Keycloak puts realm roles in realm_access.roles and resource roles in resource_access.{client}.roles. The provider normalizes them onto ctx.auth:
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:
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
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
Next Steps