Keycloak MCP Server
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-clientscan 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.
- 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
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 |
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
Metadata
Release files for keycloak-mcp-server 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| keycloak_mcp_server-0.1.0.tar.gz | 248.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| keycloak_mcp_server-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 283.0 kB
Release files / keycloak_mcp_server-0.1.0.tar.gz
| Download URL | keycloak_mcp_server-0.1.0.tar.gz |
|---|---|
| Size | 248.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5d35287a35f22a1d499d3bc90a80c9b22a29cbd5a31ee3fef5effe5e891da985
|
|
BLAKE2b-256 checksum How to use checksums |
a46737340d53683d7b14f1bd59e7dd403140628a4c89c90efa501e804429ed2c
|
| 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 logRelease files / keycloak_mcp_server-0.1.0-py3-none-any.whl
| Download URL | keycloak_mcp_server-0.1.0-py3-none-any.whl |
|---|---|
| Size | 34.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b09cf19cb16ab6f700a00daf2cfd2f7ca977ac0ff7e8ec6cf0feba6f139f8974
|
|
BLAKE2b-256 checksum How to use checksums |
c4555b41d61dab7d121dced2a5c090eaa219e2b594731e486ffc9220e85cf7f7
|
| 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