The Keycloak MCP Server implements user-based authentication using JWT Bearer tokens. This means:
Each user authenticates with their own credentials Keycloak enforces its existing permission system No duplicate permission logic in the MCP server Realm isolation works automatically Role-based access control (RBAC) works naturally
User MCP Server Keycloak
(Cursor)
1. Get JWT Token
>
<
JWT Token (with user's permissions)
2. MCP Request + JWT Token
>
3. Validate Token
>
<
Token Valid
4. Call Admin API (user token)
>
5. Keycloak checks permissions
- Realm access?
- Role permissions?
- Client access?
<
< Response (filtered)
MCP Response
Recommended for: Production deployments, multi-user environments
How it works:
Setup:
# Get your personal token
./scripts/get-mcp-token.sh \
--keycloak-url https://keycloak.example.com \
--username alice \
--password alice-password
# Add to ~/.cursor/mcp.json
{
"mcpServers": {
"keycloak": {
"transport": "sse",
"url": "https://mcp-server.example.com/mcp/sse",
"headers": {
"Authorization": "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
}
}
}
}
# ============================================================
# AUTHENTICATION & AUTHORIZATION
# ============================================================
# OIDC Configuration - Token Validation
quarkus.oidc.auth-server-url=${KC_URL}/realms/${KC_REALM:master}
quarkus.oidc.client-id=${OIDC_CLIENT_ID:mcp-server}
quarkus.oidc.credentials.secret=${OIDC_CLIENT_SECRET:}
quarkus.oidc.application-type=service
quarkus.oidc.token.issuer=${KC_URL}/realms/${KC_REALM:master}
# Security Policies - Require authentication for MCP endpoints
quarkus.http.auth.permission.mcp.paths=/mcp/*
quarkus.http.auth.permission.mcp.policy=authenticated
# Public endpoints (health checks, metrics)
quarkus.http.auth.permission.public.paths=/q/*
quarkus.http.auth.permission.public.policy=permit
# Development Mode - Disable authentication for local dev
%dev.quarkus.http.auth.permission.mcp.policy=permit
%dev.quarkus.oidc.enabled=false
| Variable | Description | Required | Example |
|---|---|---|---|
KC_URL |
Keycloak server URL | Yes | https://keycloak.example.com |
KC_REALM |
Default Keycloak realm | No | master (default) |
OIDC_CLIENT_ID |
OIDC client ID for token validation | No | mcp-server (default) |
./scripts/get-mcp-token.sh \
--keycloak-url https://keycloak.example.com \
--realm master \
--username your-username \
--password your-password \
--mcp-url https://mcp-server.example.com/mcp/sse
The script will:
# Get token
TOKEN=$(curl -X POST \
https://keycloak.example.com/realms/master/protocol/openid-connect/token \
-d 'grant_type=password' \
-d 'client_id=admin-cli' \
-d 'username=your-username' \
-d 'password=your-password' | jq -r '.access_token')
# Use in Cursor config
echo $TOKEN
JWT tokens have a limited lifespan (typically 5-60 minutes). When your token expires:
401 Unauthorized~/.cursor/mcp.jsonIf you have a refresh token:
# Refresh your token
NEW_TOKEN=$(curl -X POST \
https://keycloak.example.com/realms/master/protocol/openid-connect/token \
-d 'grant_type=refresh_token' \
-d 'client_id=admin-cli' \
-d 'refresh_token=your-refresh-token' | jq -r '.access_token')
For development convenience, you can increase token lifespan in Keycloak:
Warning: Long-lived tokens are a security risk in production!
User: Alice (admin role in master realm)
What Alice can do:
List all realms
Create/delete realms
Manage users in all realms
Configure clients
Manage authentication flows
Everything!
Token claims:
{
"realm_access": {
"roles": ["admin", "create-realm", "offline_access", "uma_authorization"]
},
"resource_access": {
"master-realm": {
"roles": ["view-realm", "manage-users", "manage-clients", ...]
}
}
}
User: Bob (manager role in quarkus realm only)
What Bob can do:
List realm: quarkus
Manage users in quarkus realm
View clients in quarkus realm
Access master realm
Access other realms
Create new realms
Token claims:
{
"realm_access": {
"roles": ["offline_access"]
},
"resource_access": {
"quarkus-realm": {
"roles": ["view-realm", "manage-users", "view-clients"]
}
}
}
User: Carol (viewer role)
What Carol can do:
List realms (that she has access to)
View users
View clients
View authentication flows
Create/delete/update anything
Token claims:
{
"realm_access": {
"roles": ["offline_access"]
},
"resource_access": {
"master-realm": {
"roles": ["view-realm", "view-users", "view-clients"]
}
}
}
# Bad
--password admin
# Good
--password "Str0ng!P@ssw0rd#2024"
# Set shorter token lifespan in Keycloak
Access Token Lifespan: 5 minutes (for sensitive operations)
Refresh Token Lifespan: 30 minutes
Development: dev-user
Staging: staging-user
Production: prod-user
Check Keycloak’s admin console:
If a token is compromised:
# Logout user (revokes all tokens)
curl -X POST \
https://keycloak.example.com/realms/master/protocol/openid-connect/logout \
-d 'client_id=admin-cli' \
-d 'refresh_token=compromised-token'
For local development without authentication:
# Start in dev mode
mvn quarkus:dev
# No authentication required
curl http://localhost:8080/mcp/sse
Dev mode automatically:
Cause: Token is invalid or expired
Solution:
echo "$TOKEN" | cut -d'.' -f2 | base64 -d | jq '.exp'
./scripts/get-mcp-token.sh --keycloak-url ... --username ... --password ...
Cause: User doesn’t have required permissions
Solution:
# In Keycloak Admin Console
Users → Your User → Role Mappings
admin - Full accessmanage-users - User managementview-realm - Read-only accessCause: OIDC configuration mismatch
Solution:
echo $KC_URL
echo $KC_REALM
echo $OIDC_CLIENT_ID
# Look for OIDC validation errors
oc logs deployment/keycloak-mcp-server | grep OIDC
curl -s $KC_URL/realms/$KC_REALM/.well-known/openid-configuration | jq .issuer
Cause: Token belongs to different user
Solution:
echo "$TOKEN" | cut -d'.' -f2 | base64 -d | jq .preferred_username
User Authentication: Each user gets their own JWT token Permission Enforcement: Keycloak handles all authorization Realm Isolation: Users can only access their authorized realms Audit Trail: All actions logged under user identity No Duplicate Logic: No permission code in MCP server Standard OAuth2: Industry-standard authentication Simple & Secure: No shared credentials or service accounts
For more information:
**Happy Keycloak management! **