keycloak-mcp
Server Configuration
Describes the environment variables required to run the server.
| Name | Required | Description | Default |
|---|---|---|---|
| KEYCLOAK_URL | Yes | Base URL of the Keycloak server, e.g. https://keycloak.example.com | |
| KEYCLOAK_REALM | No | Realm name | master |
| KEYCLOAK_CLIENT_ID | Yes | Service Account client ID | |
| KEYCLOAK_SITES_INI | No | Path to INI file for IP-to-site labeling | |
| KEYCLOAK_CLIENT_SECRET | Yes | Client secret | |
| KEYCLOAK_DEFAULT_DATE_FROM_HOURS | No | Default look-back window for event tools when date_from is omitted | 24 |
Instructions
Guidance the server publishes about itself, which clients place ahead of the tool catalog so the model reads it before choosing anything.
This server publishes no instructions, or was last inspected before Glama recorded them.
Capabilities
Features and capabilities supported by this server
Protocol revision2025-11-25
| Capability | Details |
|---|---|
| tools | {
"listChanged": false
} |
| prompts | {
"listChanged": false
} |
| resources | {
"subscribe": false,
"listChanged": false
} |
| experimental | {} |
Tools
Functions exposed to the LLM to take actions
| Name | Description |
|---|---|
| health_checkA | Report server version and KeyCloak backend connectivity / authentication. Call this at session start (or after a tool-call timeout) to confirm the MCP is up, see which version is running, and verify the KeyCloak Admin API is reachable and the service account can authenticate. Lightweight: it acquires an admin access token via the Client Credentials Grant (reusing the cached client) and does NOT enumerate users, events, or sessions. Always returns the same keys: This description is the only place those value sets are written down. The READMEs used to repeat them, which is three copies to keep in step and two that an LLM never reads — it is handed this text. |
| count_usersA | Get total user count in the realm. |
| search_usersA | Search users by username, email, first name, or last name. Args: query: Search string (partial match). max_results: Maximum results to return (default 20). |
| get_userA | Get detailed user information by exact username (email). If KEYCLOAK_USER_ATTRIBUTE_WHITELIST names any custom attribute keys, this
also does one extra by-ID lookup and appends whichever of those keys are
present on the user (the search endpoint used to resolve the username
returns a brief representation that omits Args: username: Exact username (e.g., user@example.com). |
| reset_passwordA | Reset a user's password. Args: username: Exact username (email). password: New password to set. temporary: If True, user must change password on next login. |
| reset_passwords_batchA | Reset passwords for multiple users from CSV text. Each line should be: username,password If password column is empty, a random 12-char password is generated and included in the response (the caller cannot recover it otherwise). Caller-supplied passwords are never echoed back. Args: csv_text: CSV text with username,password per line (header optional). temporary: If True, users must change password on next login. |
| get_user_sessionsC | Get active sessions for a user. Args: username: Exact username (email). |
| logout_userA | Force logout a user by removing all their active sessions. Args: username: Exact username (email). |
| set_user_enabledA | Enable or disable a user account. Disabling blocks all authentication (SSO logins) for the user — the
containment action for a compromised or decommissioned account. Only the
Disabling does not terminate existing sessions (an already-issued token
stays valid until it expires), so when disabling this reports how many
sessions remain and to run Args: username: Exact username (email). enabled: True to enable, False to disable. |
| get_user_credentialsA | List the credential types configured for one user (password, otp, webauthn, …). Use this to check a single user's MFA status: an Args: username: Exact username (email). |
| get_totp_usersA | Report how many users have TOTP (OTP) configured across the realm. Enumerates users and inspects each one's credentials for an Args:
enabled_only: Only scan enabled users (default True).
list_users: Include the list of usernames with TOTP (default True).
max_users: Cap the number of users scanned. |
| get_brute_force_statusA | Check if a user is temporarily locked due to brute force detection. Args: username: Exact username (email). |
| list_user_groupsA | List groups a user belongs to. Args: username: Exact username (email). |
| list_users_by_groupA | List all users in a group. Args: group_name: Group name (partial match). max_results: Maximum results (default 100). |
| get_eventsA | Get KeyCloak events with optional filters. Args: event_type: Event type filter (e.g., LOGIN, LOGIN_ERROR, UPDATE_PASSWORD). username: Filter by exact username (email). Resolved to user ID internally. client_id: Filter by client ID (SP name). ip_address: Filter events by source IP (client-side filter). date_from: Start date (YYYY-MM-DD). Defaults to last 24h when omitted (KEYCLOAK_DEFAULT_DATE_FROM_HOURS). date_to: End date (YYYY-MM-DD). max_results: Maximum results (default 50). |
| get_login_statsA | Get login success/failure statistics with full pagination. Time-bounded: this call stops after KEYCLOAK_DEADLINE seconds (default 45) and returns what it has; the counts are then a lower bound. The result then starts with a "PARTIAL RESULT" warning. Call again with a narrower window instead of retrying the same call. A wide window on a busy day is what triggers it. Args: date_from: Start date (YYYY-MM-DD). Defaults to last 24h when omitted (KEYCLOAK_DEFAULT_DATE_FROM_HOURS). date_to: End date (YYYY-MM-DD). Empty for all. |
| get_login_stats_by_hourA | Get login statistics broken down by hour (local time). Time-bounded: this call stops after KEYCLOAK_DEADLINE seconds (default 45) and returns what it has; the counts are then a lower bound. The result then starts with a "PARTIAL RESULT" warning. Call again with a narrower window instead of retrying the same call. A wide window on a busy day is what triggers it. Args: date_from: Start date (YYYY-MM-DD). Defaults to last 24h when omitted (KEYCLOAK_DEFAULT_DATE_FROM_HOURS). date_to: End date (YYYY-MM-DD). Empty for all. |
| get_login_failures_by_ipA | Get login failure statistics broken down by source IP. Time-bounded: this call stops after KEYCLOAK_DEADLINE seconds (default 45) and returns what it has; the counts are then a lower bound. The result then starts with a "PARTIAL RESULT" warning. Call again with a narrower window instead of retrying the same call. A wide window on a busy day is what triggers it. Args: date_from: Start date (YYYY-MM-DD). Defaults to last 24h when omitted (KEYCLOAK_DEFAULT_DATE_FROM_HOURS). date_to: End date (YYYY-MM-DD). Empty for all. top: Number of top IPs to show (default 20). |
| get_ip_activityA | Exhaustive investigation of all activity from one source IP address. Unlike Returns a fixed-shape dict (JSON), not formatted text — every key below is always present, even when zero events match. Returns:
error: None on success. Set to a descriptive message if event_types
resolved to no event types (e.g. empty or all-whitespace/commas);
every other key is still present, with an empty/zero result in
that case (no data was fetched).
ip_address: Echoes the input.
site: Site name from KEYCLOAK_SITES_INI, or null if unmatched or
unconfigured (see sites_configured to tell those apart).
sites_configured: True if KEYCLOAK_SITES_INI was loaded at all.
date_from / date_to: The resolved date range actually scanned.
event_types: The event types scanned (echoes the input, split).
summary: total_events, login_success, login_failure, unique_users,
unique_clients, first_seen/last_seen (ISO 8601, null if no match).
login_success/login_failure classify EVERY scanned event type by
whether its type ends in "_ERROR" (matching the users/clients
breakdown below), not just literal LOGIN/LOGIN_ERROR — so widening
event_types always keeps these numbers reconciled with the
per-user/per-client totals. Always computed over the FULL matched
set, unaffected by max_timeline truncation.
users: Per-user breakdown (success/failure counts, distinct error
codes), sorted by total activity descending. Note: successful
LOGIN events often carry only a userId (UUID) while LOGIN_ERROR
carries details.username — this tool keys on
username-or-userId-or-"unknown", so the same human can
legitimately appear under two different keys across success vs.
failure events.
clients: Per-client (SP) breakdown, same shape, sorted descending.
timeline: Chronological event list, capped at max_timeline (most
recent kept on overflow — see truncated). max_timeline<=0 returns
an empty timeline.
truncated: True if timeline was capped; summary/users/clients are
never affected by this cap.
events_capped: True if event pagination itself was cut short by the
wall-clock deadline (KEYCLOAK_DEADLINE) or the per-type cap
(KEYCLOAK_MAX_EVENTS) — i.e. the window was too wide and the WHOLE
result (summary/users/clients/timeline) is incomplete. Distinct from
Time-bounded: this call stops after KEYCLOAK_DEADLINE seconds (default 45) and
returns what it has; the counts are then a lower bound. The returned dict then has
Args: ip_address: Source IP to investigate. Compared against KeyCloak's recorded ipAddress field after normalizing both sides through Python's ipaddress module (so equivalent IPv6 notations like "::1" and "0:0:0:0:0:0:0:1" match); falls back to a raw string compare if either side doesn't parse as an IP. event_types: Comma-separated KeyCloak event types to scan (default "LOGIN,LOGIN_ERROR"). Widen with e.g. "LOGIN,LOGIN_ERROR,LOGOUT,UPDATE_PASSWORD,CLIENT_LOGIN,CLIENT_LOGIN_ERROR" for a broader sweep. Must resolve to at least one type. date_from: Start date (YYYY-MM-DD). Defaults to last 24h when omitted (KEYCLOAK_DEFAULT_DATE_FROM_HOURS). Widening the window means fully paginating every event type over that window before filtering — expect it to be slower on large realms. date_to: End date (YYYY-MM-DD). Empty for open-ended. max_timeline: Cap on the number of most-recent timeline entries returned (default 200; <=0 means no timeline entries). Does not affect summary/users/clients. |
| spray_checkA | Detect password-spray sources and name the accounts they breached — one rule, one call. For every EXTERNAL source IP (anything outside the ranges declared in
KEYCLOAK_SITES_INI) seen in LOGIN / LOGIN_ERROR events during the last
The breach list is built ONLY from LOGIN events whose source IP is the
flagged IP, inside the window. Every entry carries the evidence tuple
Returns a fixed-shape dict:
window: {hours, since, until} actually scanned.
complete: False if event pagination was cut short (KEYCLOAK_DEADLINE /
KEYCLOAK_MAX_EVENTS). When False, treat the result as a lower bound
and do NOT publish a definitive verdict; narrow Users are keyed by lowercased username on both sides: LOGIN_ERROR carries
Time-bounded: this call stops after KEYCLOAK_DEADLINE seconds (default 45) and
returns what it has; the counts are then a lower bound. The returned dict then has
Args:
hours: Look-back window (default 24 — sized for a once-a-day patrol).
min_users: Distinct users an IP must touch to count as a spray (default 10).
max_success_rate: Success-rate ceiling for a spray (default 0.2).
min_report_users: Distinct users an IP needs to appear in
|
| get_login_stats_by_clientA | Get login statistics broken down by client (SP). Time-bounded: this call stops after KEYCLOAK_DEADLINE seconds (default 45) and returns what it has; the counts are then a lower bound. The result then starts with a "PARTIAL RESULT" warning. Call again with a narrower window instead of retrying the same call. A wide window on a busy day is what triggers it. Args: date_from: Start date (YYYY-MM-DD). Defaults to last 24h when omitted (KEYCLOAK_DEFAULT_DATE_FROM_HOURS). date_to: End date (YYYY-MM-DD). Empty for all. |
| detect_login_loopsA | Detect users with rapid repeated logins (possible redirect loops). Scans all LOGIN events and finds users who logged in more than Time-bounded: this call stops after KEYCLOAK_DEADLINE seconds (default 45) and returns what it has; the counts are then a lower bound. The result then starts with a "PARTIAL RESULT" warning. Call again with a narrower window instead of retrying the same call. A wide window on a busy day is what triggers it. Args: date_from: Start date (YYYY-MM-DD). Defaults to last 24h when omitted (KEYCLOAK_DEFAULT_DATE_FROM_HOURS). date_to: End date (YYYY-MM-DD). Empty for all. threshold: Minimum logins within the window to flag (default 10). window_seconds: Time window in seconds (default 60). top: Number of top users to show (default 20). Use 0 for all. |
| get_password_update_eventsA | Get password update events. Args: date_from: Start date (YYYY-MM-DD). Defaults to last 24h when omitted (KEYCLOAK_DEFAULT_DATE_FROM_HOURS). date_to: End date (YYYY-MM-DD). max_results: Maximum results (default 100). |
| get_admin_eventsA | Get KeyCloak admin events (changes performed via the Admin REST API). Admin events record operations performed by service accounts or admin users
— e.g. custom user attribute updates ( Args: operation_types: Comma-separated list of CREATE, UPDATE, DELETE, ACTION. resource_types: Comma-separated list of USER, CLIENT, ROLE, GROUP, REALM_ROLE, etc. resource_path: Filter by resource path (e.g. "users/{userId}"). date_from: Start date (YYYY-MM-DD). Defaults to last 24h when omitted (KEYCLOAK_DEFAULT_DATE_FROM_HOURS). date_to: End date (YYYY-MM-DD). max_results: Maximum results (default 50). max_repr: Max chars of the representation field. 0 = omit, -1 = full. |
| get_user_attribute_historyA | Get admin-side attribute change history for a single user. Queries admin events scoped to Args: username: Exact username (email). date_from: Start date (YYYY-MM-DD). Defaults to last 24h when omitted (KEYCLOAK_DEFAULT_DATE_FROM_HOURS). date_to: End date (YYYY-MM-DD). max_results: Maximum results (default 100). max_repr: Max chars of the representation field. 0 = omit, -1 = full. |
| get_session_statsA | Get active session count per client. |
| get_client_sessionsA | Get active sessions for a specific client (SP). Args: client_id: Client ID (e.g., 'xflow', 'shadowserver'). max_results: Maximum results (default 100). |
| list_clientsA | List all SAML/OIDC clients in the realm. |
| get_clientA | Show one client's configuration, including its authentication flow overrides. Reports an explicit allowlist of fields rather than the raw client
representation. A client representation can carry The headline is Args: client_id: The clientId (not the internal UUID). |
| get_realm_rolesA | List all realm-level roles. |
| get_realm_security_defensesA | Show the realm's security-defense settings (read-only). Reports the realm-level security configuration that the admin console groups under "Security defenses":
Use this to verify that brute-force protection is actually turned on and
how aggressively it locks accounts — the per-user |
| daily_briefA | Run a morning Keycloak health check. Checks (all scoped to the last
A single IP with login failures >=
Output tiers:
Args: since_hours: Look-back window in hours (default 18). ip_failure_threshold: Login failures from a single IP that triggers a WARNING (default 50). |
Prompts
Interactive templates invoked by user choice
| Name | Description |
|---|---|
No prompts | |
Resources
Contextual data attached and managed by the client
| Name | Description |
|---|---|
No resources | |
TDQS
Scored across 32 tools
Most tools are clearly distinct by resource and dimension (e.g., login stats by client/hour vs. IP, user vs. client vs. session stats). Some overlap exists in event retrieval (get_events, get_admin_events, get_password_update_events) and session inspection, but the detailed descriptions help an agent differentiate them.
Names predominantly follow a consistent snake_case verb_noun pattern (get_*, list_*, reset_*, set_*, search_*). A few report-style tools (daily_brief, spray_check, health_check) deviate from strict verb_noun, but casing and readability remain consistent.
32 tools is excessive for a single MCP server; many event and statistics tools (e.g., get_login_stats, get_login_stats_by_client, get_login_stats_by_hour, get_login_failures_by_ip) could be consolidated via parameters. This volume increases navigation overhead and cognitive load for an agent.
The server covers security monitoring and containment well (events, stats, disable user, logout, password reset) but lacks core administrative CRUD operations like create/delete user, group membership management, client/role creation, and clearing brute-force lockouts. These are notable gaps for a Keycloak admin surface.