authentik-mcp-server
Allows interaction with an Authentik identity provider, providing tools for listing users, groups, and SSO applications, viewing user details, retrieving recent audit/auth events, checking system status, and enabling or disabling user accounts.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@authentik-mcp-serverWhich users are currently inactive?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
authentik-mcp-server
A minimal Model Context Protocol server that connects to Authentik, packaged for Docker.
It runs as a standing network service (streamable-http transport, not stdio),
so any MCP client on your internal network can connect to
http://<host>:<port>/mcp — the container isn't spawned per-client, and
container lifecycle/updates can be handed off to a tool like
Dockhand.
Two modes, one image
This image can run as either of two containers, chosen by AUTHENTIK_MCP_MODE:
Mode | Port (default) | Tools |
| 8937 | Everything except |
| 8942 | Everything |
This is not a config toggle guarding one shared codebase — in read mode,
set_user_active is never defined at all, so it can't appear in a tool
listing or be called under any circumstances. docker-compose.yml runs
read always-on and puts write behind a Compose profile, so a plain
docker compose up -d only ever starts something that structurally cannot
write to your identity provider. Starting the write container is a
deliberate, separate action: docker compose --profile write up -d.
Related MCP server: Remote MCP AuthKit
Tools
Tool | Mode | Description |
| read, write | List users, optionally filtered by search term and/or active status |
| read, write | Full details for one user by ID |
| read, write | List groups, optionally filtered by name |
| read, write | List configured SSO applications |
| read, write | Recent audit/auth events (logins, failures, config changes, ...) |
| read, write | Authentik version and runtime info |
| write only | Enable or disable a user account |
Why no destructive tools
Authentik is an identity provider — it holds real user accounts, group membership, and SSO/application configuration for everything else behind it. A wrong or malicious tool call here has a much bigger blast radius than one against a media manager, so this server is deliberately read-mostly, with writes isolated behind several independent layers rather than one flag:
Mode split (structural).
set_user_activeonly exists in write-mode containers. Point a client at the read-mode server and the tool is genuinely absent — not hidden, not permission-denied, just not there.AUTHENTIK_ALLOW_WRITES=true— even in a write-mode container, this must be explicitly set (defaultfalse). An operator has to turn writes on for that specific deployment.confirm=Trueon the call itself — an assistant has to explicitly ask for the write every time; it can't happen from a misread instruction or a config left at its default.Separate credentials recommended (see Configuration below): give the read and write containers different, separately-scoped Authentik API tokens and different
MCP_AUTH_TOKENs, so a compromise of the read container's environment doesn't hand over write-capable credentials too.
Beyond set_user_active, there is no create/delete tool for users,
groups, applications, providers, or flows, and no tool that touches password
hashes, tokens, or recovery links, even though Authentik's API supports all
of that (set_password, recovery, impersonate, etc. on the users
endpoint). If you need those, use Authentik's own admin UI.
recent_events is read-only but still sensitive — audit events can include
IPs and usernames tied to real login activity. Scope who can reach either
server's /mcp endpoint accordingly (see Authentication, below).
If you want more capability than this, treat it as a deliberate decision to widen scope, not a missing feature — add tools individually, decide which mode they belong in, and mind the permission each one needs.
Health endpoints
Two plain HTTP endpoints per container, reachable without MCP_AUTH_TOKEN
(so Docker's HEALTHCHECK, Dockhand, or any other monitor can poll them
without the secret):
Endpoint | Checks | Healthy | Unhealthy |
| The process is up and serving HTTP. Does not call Authentik. |
| (doesn't respond) |
|
|
|
|
They're split deliberately: /health is what each container's own
HEALTHCHECK uses (so a transient Authentik outage doesn't get the
container itself restarted in a loop), while /ready is for verifying
config — curl http://<host>:8937/ready (read) or
curl http://<host>:8942/ready (write) tells you plainly whether that
container's host is reachable, its token is valid, or both. The response's
mode field confirms which container answered.
/ready deliberately checks token validity, not the specific permissions
individual tools need. system_status needs the
authentik_rbac.view_system_info permission and set_user_active needs
write access to users; a 403 from either of those is a permission-scope
problem with that container's token, not a readiness failure — /ready will
still report healthy as long as the token can authenticate at all.
Authentication
Set MCP_AUTH_TOKEN_READ and MCP_AUTH_TOKEN_WRITE (random shared secrets —
openssl rand -hex 32 each) and every request to the corresponding
container must carry Authorization: Bearer <that container's token> or it
returns 401. Use two different values — that's what stops a leaked
read-side bearer token from being replayed against the write container's
port. This is checked by a small Starlette middleware in front of the MCP
app, not the mcp SDK's built-in OAuth support (mcp.server.auth) —
that machinery expects a full OAuth authorization server (issuer/resource
metadata, RFC 8414/8707/9068 discovery), which is unnecessary complexity for
a secret shared by trusted LAN clients.
Leave a container's token unset and that container runs with no auth —
anything that can reach its /mcp endpoint can call every tool it exposes.
For the write container that includes set_user_active (still behind
AUTHENTIK_ALLOW_WRITES + confirm=True, but still — set the token). Each
container logs a warning on startup when it's running without one. Either
way, the trust boundary is still the network:
Do not publish either port through any reverse proxy, port-forward, or anything else reachable from outside your LAN/VLAN.
Bind the compose
ports:mapping to a specific internal interface (e.g.192.168.1.50:8937:8937) rather than all interfaces, if you want to be stricter about which hosts on your network can reach it at all.
Authentik API token scope — use two, not one
Create two separate service accounts in Authentik (Directory > Users > Create Service Account) rather than reusing your own admin user, or reusing one account for both modes. Create each one's API token under Directory > Tokens and App passwords (Intent: API Token):
Read token (
AUTHENTIK_API_TOKEN_READ): grant onlyview_user,view_group,view_application,view_event, andauthentik_rbac.view_system_info— whatever the read tools you actually use need. This account should have no write permissions on anything.Write token (
AUTHENTIK_API_TOKEN_WRITE): the above, plusauthentik_core.change_userforset_user_active.
Don't rely on assumed defaults for what a fresh service account or API token can and can't do — Authentik's permission model has changed across versions (see the RBAC/permissions docs for your installed version), and what a token can do depends on the role/group it's actually assigned, not just on it being newly created. Check the token's effective permissions directly in Authentik's admin UI after creating it, for both accounts.
This is the layer that actually matters most: even a full compromise of the
read container's process — its environment, its memory, everything — can't
yield a credential capable of writing anything, because Authentik's own RBAC
enforces that, not just this server's tool registry. A single
AUTHENTIK_API_TOKEN (no _READ/_WRITE suffix) is supported as a fallback
if you'd rather use one token for both, but that gives up this guarantee.
Configuration
Environment variables (see .env.example):
Variable | Required | Default | Description |
| no |
|
|
| yes | — | e.g. |
| yes, in read mode | — | Falls back to plain |
| yes, in write mode | — | Falls back to plain |
| no |
| Write-mode-only master switch for |
| no |
| Interface the server binds to inside the container |
| no |
| Port the server listens on |
| no | — | Per-mode bearer secret. Falls back to plain |
Authentik's REST API is a fixed path prefix (/api/v3/...) rather than
content-negotiated or runtime-discoverable the way the Servarr apps
(Sonarr/Radarr, also in this family of MCP servers) expose a GET /api
endpoint reporting current/deprecated versions. There's no
AUTHENTIK_API_VERSION setting here because there's nothing to point it
at — v3 is hardcoded into server.py's HTTP client, and a future
Authentik v4 would need a code change here. Unlike the Servarr-family
servers, /ready can't detect that drift automatically; if tool calls start
404ing after an Authentik upgrade, check Authentik's release notes for an
API version bump.
Image
Built and pushed to ghcr.io/barrow1990/authentik-mcp-server by
.github/workflows/ci.yml on every push to
main that passes tests, tagged :latest and :<commit-sha>. The same
image serves both modes — docker-compose.yml runs it twice with different
AUTHENTIK_MCP_MODE values. Swap in build: . there instead if you'd
rather build locally from the Dockerfile.
The image is a three-stage build: builder compiles dependencies into
--target=/deps (all of them, including cryptography's compiled cffi
extension, ship musllinux wheels, so this needs no compiler even on alpine);
prep starts fresh from python:3.12-alpine, drops pip/setuptools/wheel,
strips stdlib pieces this headless server never touches (tkinter,
idlelib, lib2to3, ensurepip, ...), adds the non-root app user, and
copies in /deps and server.py; runtime then does a single
COPY --from=prep / / onto a scratch base. That last step matters more
than it looks — a plain RUN rm -rf only hides files still physically
present in the base image's own layers underneath, so it doesn't shrink a
normal layered image at all; copying the already-trimmed filesystem onto
scratch is what actually drops those bytes from what gets pushed.
That takes the published image to roughly ~98MB, the same floor as the
other servers in this family — mcp's own dependency graph pulls in
cryptography's AES-GCM/HKDF regardless. Dependencies in requirements.txt
are pinned to exact versions rather than >= ranges, so a routine
docker build can't silently pull in a heavier resolution than the one that
was actually tested.
Running with Docker Compose
cp .env.example .env # fill in AUTHENTIK_URL and both API tokens
docker compose up -d --pull always # read container only
docker compose --profile write up -d # also starts the write containerThe read server is reachable at http://<docker-host>:8937/mcp; the write
server, once started, at http://<docker-host>:8942/mcp.
One .env, but no container sees more than its own variables
docker-compose.yml deliberately does not use env_file: (which would
dump the whole .env into every container). Instead each service's
environment: block names its own variables explicitly via ${VAR}
substitution — a variable never referenced in a service's block simply never
reaches that container. Verify this yourself any time after editing .env:
docker compose --profile write configauthentik-mcp-read's resolved environment should show AUTHENTIK_API_TOKEN_READ
but never AUTHENTIK_API_TOKEN_WRITE or MCP_AUTH_TOKEN_WRITE — confirmed
during development of this compose file, but re-check it after any edit,
since a typo in a service's environment: block would silently reintroduce
exactly the leak this design exists to prevent.
Does this fit how Dockhand expects to be used?
Only if Dockhand feeds docker compose a plain .env. ${VAR}
substitution reads exclusively from a literal .env in the project
directory (or --env-file <path> / real shell environment variables) —
never from an arbitrarily-named file. This is a change from how earlier
servers in this family (sonarr, radarr, ...) load config — those use
env_file: [.env, .env.dockhand] on the understanding that Dockhand writes
its UI-configured values to a separate .env.dockhand, which env_file:
(unlike substitution) can load regardless of filename.
This repo hasn't been verified against a live Dockhand deployment. Before relying on it under Dockhand:
Deploy this stack in Dockhand and check whether the values you set in its UI actually reach the containers —
docker exec authentik-mcp-read env.If they don't (Dockhand is writing to
.env.dockhand, substitution never sees it, and the:?markers indocker-compose.ymlmakedocker compose upfail outright rather than start with empty credentials): edit.envdirectly in this repo's checkout instead of through Dockhand's UI, or check whether Dockhand's stack settings let you point it at.envinstead of.env.dockhand.
Make the GHCR package public, or every pull will need docker login ghcr.io with a PAT on each deploy host — a private package by default
requires auth even to docker pull, which most homelab boxes won't have
configured.
Set a restart policy of unless-stopped (already in docker-compose.yml) so
Dockhand-driven restarts and host reboots bring the read container back up
without manual intervention. The write container, being profile-gated, needs
its profile invoked again after a host reboot too — that's intentional, not
a bug to fix; it's meant to require a deliberate action to bring back.
Connecting a client
Claude Code
claude mcp add authentik -s user --transport http http://<docker-host>:8937/mcp \
--header "Authorization: Bearer <MCP_AUTH_TOKEN_READ>"Add the write server as a separate, explicitly-named connection only when you actually want it available, rather than always-on alongside the read one:
claude mcp add authentik-write -s user --transport http http://<docker-host>:8942/mcp \
--header "Authorization: Bearer <MCP_AUTH_TOKEN_WRITE>"(Drop the --header flag on either if you're running that container with
its token unset.)
Claude Desktop
Claude Desktop's built-in config expects a locally-spawned command, so for
a network server like this you'll need an HTTP-to-stdio bridge such as
mcp-remote:
{
"mcpServers": {
"authentik": {
"command": "npx",
"args": [
"-y", "mcp-remote", "http://<docker-host>:8937/mcp",
"--header", "Authorization: Bearer <MCP_AUTH_TOKEN_READ>"
]
}
}
}Running without Docker
pip install -r requirements.txt
AUTHENTIK_URL=http://192.168.1.50:9000 AUTHENTIK_MCP_MODE=read \
AUTHENTIK_API_TOKEN_READ=your-read-scoped-token \
MCP_AUTH_TOKEN_READ=your-shared-secret python server.pyTesting
pip install -r requirements-dev.txt
python -m pytest tests/ -vtests/test_tools.py— each tool's logic against a mocked Authentik (httpx.MockTransport, no extra mocking library needed). Runs withAUTHENTIK_MCP_MODE=write(the superset) soset_user_activeis covered alongside everything read mode has.tests/test_http.py—/health,/ready, and the bearer-auth middleware, viaserver.build_app()(the exact app__main__runs) through Starlette'sTestClient.tests/test_mode.py— the mode split itself, via subprocess (this behavior only shows up at import time, so it can't be exercised by monkeypatching an already-imported module): read mode genuinely lackingset_user_active, write mode having it, per-mode port defaults, invalidAUTHENTIK_MCP_MODEvalues exiting non-zero, and the_READ/_WRITEcredential fallback logic.tests/test_live_authentik.py— opt-in contract tests against a real Authentik instance, to catch drift if an Authentik upgrade renames/removes a field these tools depend on (pk,username,is_active,action, ...). Skipped by default (no Authentik in CI); run with:RUN_LIVE_AUTHENTIK_TESTS=1 AUTHENTIK_URL=https://authentik.example.com \ AUTHENTIK_API_TOKEN=<real token> python -m pytest tests/test_live_authentik.py -v
CI (.github/workflows/ci.yml) runs the mocked suite on every push/PR; the
GHCR build only runs after it passes.
License
MIT
This server cannot be deployed
Maintenance
Related MCP Connectors
MCP server for mandates, delegation, policy-gated execution, credential grants, and audit.
Governed MCP gateway: one endpoint for your tools, with credential custody and audit log.
Remote MCP server to read and manage your Atako AI agents, messages, files, and integrations.
An MCP server that provides an API to LLMs to manage their JumpCloud resources.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceAuthentik MCP provides seamless integration with Authentik's API, supporting both full-featured and diagnostic modes. These enable secure, automated user, group, and system management through MCP-compatible tools.17MIT
- FlicenseNot gradedqualityCmaintenanceEnables remote MCP server connections with WorkOS AuthKit authentication and user management. Supports organization-centric authentication and permission-based tool access control.-
- AlicenseNot gradedqualityAmaintenanceMCP server for Authentik identity management, enabling natural language management of users, groups, applications, flows, policies, providers, and more.128 npm11MIT
- AlicenseAqualityAmaintenanceMCP server for Authentik identity provider enabling management of users, groups, apps, tokens, flows, and policy bindings through natural language.7MIT