Skip to main content

Keycloak MCP Server

Python 3.12+ Tests Coverage License: Apache 2.0 Open in GitHub Codespaces

An MCP server for the Keycloak Admin REST API where every call runs as the signed-in user. Users log in to Keycloak from their MCP client (Claude Code, Codex, …); the server forwards their own access token to the Admin API, so Keycloak enforces each user's existing roles. There is no shared admin password or service account.

MCP client ──login (OAuth 2.1 + PKCE)──▶ this server ──▶ Keycloak login page
     │                                        │ keeps the user's Keycloak token, encrypted
     └── tool call with an MCP-only token ──▶ │ ──Admin API, Bearer <user's token>──▶ Keycloak
                                                                    (decides: allowed / 403)

Why per-user identity

Most Keycloak MCP servers authenticate with one admin account or service account, so anyone who can reach the server acts with its full power, and the audit trail shows the shared account instead of the person. Here:

  • Least privilege for free: a user with only view-clients can list clients and gets a clean permission error for everything else.
  • Audit trails name the real user.
  • No admin password or client secret to store: the Keycloak client is public, with PKCE.
  • The MCP client never holds the Keycloak token: the FastMCP OAuth proxy issues it a token that only works on this server, and stores the Keycloak token encrypted.
  • No fallback identity: requests are never retried as a service account, and the SDK is prevented from silently logging in again.

Features

  • 20 read-only tools: realm settings; users, groups and memberships; clients, client scopes and protocol mappers; direct and effective role mappings and role holders; login and admin events; sessions; access-token previews; LDAP federation
  • Browser OAuth for any MCP client, with consent, PKCE, audience-bound JWT validation and encrypted per-user token storage, with no database
  • Bounded, safe output: pagination capped at 100, selected fields only; secrets, credentials and admin-event payloads are never returned
  • FastMCP + FastAPI, HTTP/SSE/streamable-HTTP transports, Pydantic settings, structured JSON logging
  • Container and OpenShift manifests, GitHub Actions CI

Current limits

  • Read-only: no create, update or delete tools yet.
  • One realm per server: the realm users sign in to is the realm the tools inspect.
  • stdio mode does not check the token's audience locally; Keycloak validates it on every Admin API call.
  • Single instance: OAuth state is stored on local disk.

Quick Start

git clone https://github.com/drtinkerer/keycloak-mcp-server
cd keycloak-mcp-server
make install        # uv sync + pre-commit hooks + local configuration
make local          # starts server on localhost:5001

Verify in another terminal:

curl http://localhost:5001/health
Manual setup (without Make)
# Sync dependencies from the committed lockfile
uv sync --locked
uv run pre-commit install

# Configure and run
cp .env.example .env
uv run keycloak-mcp-server

# Verify
curl http://localhost:5001/health

All Python commands go through uv; no shell activation is needed. uv sync installs the default dev dependency group. uv.lock is committed for reproducible installs; use uv add to add dependencies and uv lock --upgrade-package <name> to update one. uv manages an ignored environment internally. See uv’s project guide.

Configure browser OAuth

Register a public OIDC client named keycloak-mcp in the target Keycloak realm. Enable authorization code flow with PKCE S256, refresh tokens, and an access-token audience mapper that adds the client's own ID (keycloak-mcp). Disable password grants and service accounts. Register this exact redirect URI:

http://localhost:5001/auth/callback

The authentication guide lists every client setting and includes a Terraform example.

Set these values in the server’s ignored .env file:

ENABLE_AUTH=True
MCP_HOST_ENDPOINT=http://localhost:5001
KEYCLOAK_SERVER_URL=https://keycloak.example.com
KEYCLOAK_REALM=myrealm
KEYCLOAK_OAUTH_CLIENT_ID=keycloak-mcp
KEYCLOAK_OAUTH_AUDIENCE=keycloak-mcp
KEYCLOAK_OAUTH_SIGNING_KEY=<random persistent signing key>
# KEYCLOAK_CA_BUNDLE=/path/to/trusted-ca-bundle.pem

The client uses public authorization code flow with PKCE; no Keycloak client secret is required. Leave KEYCLOAK_OAUTH_CLIENT_SECRET unset. For an existing confidential upstream client, supplying that variable selects client_secret_basic. The signing key below is internal MCP server state, separate from Keycloak credentials.

Generate the signing key once with uv run python -c 'import secrets; print(secrets.token_urlsafe(48))'. Keep it private and stable across restarts. Remove KEYCLOAK_DEV_ACCESS_TOKEN if set. The server fails at startup if OAuth configuration is incomplete. No PostgreSQL is required; encrypted OAuth state is stored in the ignored .keycloak-oauth/ directory.

Start the server with make local. The MCP client opens the browser, and the user signs in to Keycloak. Their existing Keycloak permissions determine which tools they can call; the client registration grants no admin roles. Target URL, realm, and OAuth client ID belong to the server configuration. Each user’s identity comes from the browser login. See the authentication guide for the flow, TLS trust, and troubleshooting.

Use with Codex

With the configured server running:

codex mcp add keycloak --url http://localhost:5001/mcp
codex mcp login keycloak
codex mcp list
codex mcp get keycloak
codex

Adding an OAuth server may start login automatically; run login if authentication is still needed or if the connection was already added with authentication disabled. The user approves the MCP connection and completes Keycloak login in the browser. No client secret or Keycloak access token goes into the Codex MCP configuration.

Inside a new Codex session, use /mcp to check the twenty tools, then ask:

Use the keycloak MCP server to find who has the realm-admin role.

The CLI saves the connection in ~/.codex/config.toml by default. Restart existing Codex sessions after changing MCP configuration. See the official Codex MCP documentation.

Log out or remove the connection:

codex mcp logout keycloak
codex mcp remove keycloak

Use with Claude Code: stdio, no server to run

Claude Code starts the server with uvx and talks to it over stdin/stdout. On the first tool call the server opens the Keycloak sign-in in your browser itself (authorization code + PKCE, loopback callback), then uses your token for every Admin API call. Tokens are cached in ~/.cache/keycloak-mcp-server/tokens.json (mode 0600) and refreshed automatically.

Add http://127.0.0.1:8250/callback as a valid redirect URI on the keycloak-mcp client, then:

claude mcp add --scope user keycloak \
  -e KEYCLOAK_SERVER_URL=https://keycloak.example.com \
  -e KEYCLOAK_REALM=myrealm \
  -e KEYCLOAK_OAUTH_CLIENT_ID=keycloak-mcp \
  -- uvx keycloak-mcp-server --stdio

No signing key or OAuth proxy settings are needed in this mode. It is for one person on their own machine; to share one server between users, use HTTP mode below. Set KEYCLOAK_LOGIN_PORT (and register that port's /callback) if 8250 is taken. To sign out, delete the cache file.

Use with Claude Code: HTTP

claude mcp add --transport http --scope user keycloak http://localhost:5001/mcp
claude mcp login keycloak
claude mcp list
claude mcp get keycloak
claude

In Claude, /mcp shows connection status and offers authentication. Use it if an older Claude CLI does not have mcp login. The user scope makes the connection available across projects. Start a new Claude session after changing the configuration. See the official Claude Code MCP documentation.

Log out or remove the connection:

claude mcp logout keycloak
claude mcp remove --scope user keycloak

Removing a connection does not stop the server. Press Ctrl+C in its terminal to stop it. With ENABLE_AUTH=False, discovery works without login, but browser OAuth routes are absent. Use the direct client to test the protocol without an LLM.

Configuration

Variable Default Description
MCP_HOST localhost Server bind address
MCP_PORT 5001 Server port (1024-65535)
MCP_TRANSPORT_PROTOCOL http Transport protocol (http, sse, streamable-http)
MCP_SSL_KEYFILE None SSL private key file path
MCP_SSL_CERTFILE None SSL certificate file path
ENABLE_AUTH False* Enable OAuth authentication (see Auth Guide)
MCP_HOST_ENDPOINT http://localhost:5001 Public OAuth origin, without /mcp
KEYCLOAK_SERVER_URL Unset Keycloak base URL, including /auth when used
KEYCLOAK_REALM Unset Target realm, e.g. myrealm
KEYCLOAK_OAUTH_CLIENT_ID Unset Registered public Keycloak client with PKCE
KEYCLOAK_OAUTH_CLIENT_SECRET Unset Optional; only set for a confidential upstream client
KEYCLOAK_OAUTH_SIGNING_KEY Unset Persistent random key of at least 32 characters
KEYCLOAK_OAUTH_AUDIENCE keycloak-mcp Required aud claim; add an audience mapper for it
KEYCLOAK_LOGIN_PORT 8250 stdio mode: loopback port for the sign-in callback
KEYCLOAK_TOKEN_CACHE_DIR ~/.cache/keycloak-mcp-server stdio mode: token cache directory
PYTHON_LOG_LEVEL INFO Logging level

* ENABLE_AUTH is True in code but False in .env.example, so a fresh make install starts with tool discovery only.

Documentation

Guide Description
Architecture System diagrams, code structure, key components, MCP tools
Development Setup, running locally, testing, code quality
Deployment Podman, OpenShift, container configuration
CI/CD Workflows, pipeline features, running CI locally
Contributing Development workflow, commit conventions, PR process
Security Vulnerability reporting policy
Changelog Release history
Authentication OAuth setup, auth modes, troubleshooting
Tutorial Your First Tool in 5 Minutes
Examples FastMCP and LangGraph client examples

Contributing

See CONTRIBUTING.md for detailed guidelines.

# Fork, clone, and set up
git clone https://github.com/<your-username>/keycloak-mcp-server.git
cd keycloak-mcp-server
make install

# Create a branch, make changes, verify
git checkout -b feat/your-feature
make lint && make test && make pre-commit

# Commit and open a PR
git commit -m "feat: your descriptive message"
git push origin feat/your-feature

Acknowledgements

Started from redhat-data-and-ai/template-mcp-server (Apache 2.0), a FastMCP server template. The Keycloak integration, OAuth proxy setup and tools are new.

License

Apache 2.0

Metadata

Release files for keycloak-mcp-server 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for keycloak-mcp-server 0.1.1
File Size Uploaded
keycloak_mcp_server-0.1.1.tar.gz 262.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for keycloak-mcp-server 0.1.1
File Interpreter ABI Platform
keycloak_mcp_server-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 301.8 kB

Release files / keycloak_mcp_server-0.1.1.tar.gz

Download URL keycloak_mcp_server-0.1.1.tar.gz
Size 262.0 kB
Tags Source
SHA-256 checksum
How to use checksums
8480326ff8cdcf995d3e480d678baf0004c7c5918ded1e6e7b2ad3c69140af43
BLAKE2b-256 checksum
How to use checksums
e8b98bca3ea853757ffad1678a0f632680367bf557ba9e3223ac9154720eb178
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 11, 2026.

Transparency log

Release files / keycloak_mcp_server-0.1.1-py3-none-any.whl

Download URL keycloak_mcp_server-0.1.1-py3-none-any.whl
Size 39.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
cbd7546292596d59aa5fe7af2bd3fa78e56c3ea59ebf796b83e5f08c574b7837
BLAKE2b-256 checksum
How to use checksums
278151ae4f0e98587e3fa8545f0017de747c6696c96029518839de41344842b6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 11, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page