OIDC mode gates access to the MCP server behind an external identity provider
(Authentik, Keycloak, Auth0, etc.). Unlike OAuth mode,
which collects a per-user Home Assistant Long-Lived Access Token via a consent
form, OIDC is purely an access gate: once a user authenticates through
your OIDC provider, all requests share the same Home Assistant credentials
configured on the server (HOMEASSISTANT_TOKEN).
Use OIDC if you want:
- Access controlled by an identity provider you already run (SSO, MFA, group policies) instead of a secret URL
- Every authenticated user to share one Home Assistant identity — there is no per-user HA authorization or isolation
Use the private URL (secret-path) method if you want:
- Simpler setup with no external identity provider (recommended for most users)
Use OAuth mode instead if you want:
- Per-user Home Assistant credentials rather than a shared server-side token
Note: OIDC and OAuth both authenticate the user; only OAuth mode changes which Home Assistant credentials a request uses.
Start the server with the ha-mcp-oidc entrypoint instead of ha-mcp-web:
Docker:
docker run -d --name ha-mcp-oidc \
-p 8086:8086 \
-v ha-mcp-data:/home/mcpuser/.ha-mcp \
-e HOMEASSISTANT_URL=http://homeassistant.local:8123 \
-e HOMEASSISTANT_TOKEN=your-long-lived-access-token \
-e OIDC_CONFIG_URL=https://auth.example.com/application/o/ha-mcp/.well-known/openid-configuration \
-e OIDC_CLIENT_ID=ha-mcp-client \
-e OIDC_CLIENT_SECRET=your-client-secret \
-e MCP_BASE_URL=https://mcp.example.com \
ghcr.io/homeassistant-ai/ha-mcp:latest \
ha-mcp-oidcuvx:
export HOMEASSISTANT_URL=http://homeassistant.local:8123
export HOMEASSISTANT_TOKEN=your-long-lived-access-token
export OIDC_CONFIG_URL=https://auth.example.com/application/o/ha-mcp/.well-known/openid-configuration
export OIDC_CLIENT_ID=ha-mcp-client
export OIDC_CLIENT_SECRET=your-client-secret
export MCP_BASE_URL=https://mcp.example.com
uvx --from=ha-mcp@latest ha-mcp-oidcThe server must be reachable over HTTPS at MCP_BASE_URL — put a
TLS-terminating reverse proxy or tunnel in front of it (the same requirement
as OAuth mode).
| Variable | Description | Default |
|---|---|---|
HOMEASSISTANT_URL |
Required. URL of the Home Assistant instance | None |
HOMEASSISTANT_TOKEN |
Required. Shared Home Assistant long-lived access token (or supervisor token) used for every authenticated request | None |
OIDC_CONFIG_URL |
Required. OIDC discovery URL (.well-known/openid-configuration) |
None |
OIDC_CLIENT_ID |
Required. OAuth client ID registered with your OIDC provider | None |
OIDC_CLIENT_SECRET |
Required. OAuth client secret from your OIDC provider | None |
MCP_BASE_URL |
Required. Public HTTPS URL where this server is accessible | None |
MCP_HOST |
Bind host for the HTTP listener. Set 127.0.0.1 to restrict to loopback-only binds when a reverse proxy on the same host handles external exposure. |
0.0.0.0 |
MCP_PORT |
Server port | 8086 |
MCP_SECRET_PATH |
MCP endpoint path (does not gate the settings UI in this mode) | /mcp |
MCP_SETTINGS_SECRET_PATH |
Dedicated secret path for the web settings UI. In OIDC mode the settings UI never shares the MCP path (its custom routes bypass OIDC auth), so it is served under this separate secret. Auto-generated and printed in the startup log when unset. | Auto-generated /private_<token> |
HA_MCP_DISABLE_SETTINGS_UI |
Set truthy (1/true/yes/on) to not serve the web settings UI at all. |
unset (UI served) |
OIDC_JWT_SIGNING_KEY |
Optional. Secret key for signing FastMCP session JWTs. Sessions persist across restarts by default — when unset, FastMCP derives the signing key deterministically from OIDC_CLIENT_SECRET, so restarting the server does not log users out. Set this var to decouple the signing key from the client secret. To force a logout of all sessions, rotate whichever secret the key derives from (this var if set, otherwise OIDC_CLIENT_SECRET). Generate with: python -c "import secrets; print(secrets.token_urlsafe(32))" |
Derived from OIDC_CLIENT_SECRET |
OIDC_ALLOWED_CLIENT_REDIRECT_URIS |
Optional but strongly recommended for internet-facing deployments. Comma-separated list of redirect URI patterns accepted from dynamically-registered clients. Open dynamic client registration (DCR) lets an attacker register their own client with their own redirect URI; setting this constrains what any dynamically-registered client may register in the first place. | no allow-list — each dynamically-registered client's own redirect URIs are accepted |
OIDC_VERIFY_ID_TOKEN |
Optional. Set true for OIDC providers that issue opaque access tokens the default JWT verifier cannot validate (e.g. Authelia, or Auth0 without an API audience configured). ha-mcp always requests openid, so these providers return the ID token FastMCP verifies instead. |
false |
OIDC_AUDIENCE |
Optional. Expected aud claim for IdP-issued access tokens. Without it (and with OIDC_VERIFY_ID_TOKEN off), FastMCP's JWT verifier checks issuer, signature, and expiry but not audience — fine on a dedicated IdP, weaker on a shared one where other clients' tokens would also pass. With OIDC_VERIFY_ID_TOKEN=true, verification instead pins aud to OIDC_CLIENT_ID and this value is not used for verification — it is still forwarded to the IdP's authorize/token endpoints, which is exactly what makes Auth0 issue JWT rather than opaque access tokens (see the OIDC_VERIFY_ID_TOKEN row). |
Unset (no audience check) |
LOG_LEVEL |
Logging level for ha-mcp and, when FastMCP logging is enabled, FastMCP's OIDC token-validation diagnostics | INFO |
FASTMCP_LOG_ENABLED |
Set false to disable FastMCP framework logging entirely. ha-mcp preserves this opt-out instead of routing FastMCP records through its root logger. |
true |
When registering ha-mcp as an OAuth/OIDC application with your provider:
- Redirect URI:
<MCP_BASE_URL>/auth/callback - Grant type: Authorization Code
- Allowed scope:
openid. ha-mcp always requests this protocol-level OIDC scope, so the provider must allow it for the registered client. - Token endpoint auth method: Client Secret Basic. ha-mcp's OIDC
client (via authlib) does not pass
token_endpoint_auth_method, so it uses authlib's default, which is Client Secret Basic — not Client Secret Post. Providers whose client is locked toclient_secret_postwill fail the authorization code exchange; set the client to Client Secret Basic (or "Basic Auth") in your provider's application settings.
Example discovery URLs:
- Authentik:
https://auth.example.com/application/o/<app-slug>/.well-known/openid-configuration - Keycloak:
https://keycloak.example.com/realms/<realm>/.well-known/openid-configuration - Auth0:
https://<tenant>.auth0.com/.well-known/openid-configuration
ha-mcp keeps openid as the required, default, and advertised scope. A client
that omits scope during dynamic client registration (DCR) therefore receives
openid, and MCP clients discover only openid in protected-resource
metadata. If a client explicitly requests optional provider scopes such as
profile, email, or offline_access, ha-mcp retains them and adds openid
when the client omitted it. The compatibility layer also adds openid to every
upstream authorization request, including requests from clients that supplied
only optional scopes. ha-mcp does not advertise or request optional scopes on
the client's behalf; the upstream identity provider's client and user policy
decides whether to grant the optional scopes the client requested.
After upgrading from a version that did not require openid, ha-mcp retains
persisted DCR registrations and adds openid to a registration the first time
it is loaded. A live access token issued before the fix without openid is
rejected on its next MCP request, and its legacy refresh token is rejected too,
so the client starts authorization once and obtains compliant tokens. The DCR
registration remains intact during this reauthorization. Most clients restart
authorization automatically; manually clearing the client's connector or
authorization cache is only a fallback for a client that does not.
OIDC mode works out of the box with providers that issue JWT access tokens — Authentik and Keycloak are known to work without extra configuration.
ha-mcp always requests the required openid scope. Providers that issue
opaque access tokens (not JWTs) also need OIDC_VERIFY_ID_TOKEN=true so
FastMCP verifies the returned ID token instead of trying to parse the access
token as a JWT:
- Authelia issues opaque access tokens by default. Allow
openidin the client'sscopesand setOIDC_VERIFY_ID_TOKEN=truefor ha-mcp. You do not need to change Authelia'saccess_token_signed_response_algto enable JWT access tokens. - Auth0 issues opaque access tokens unless the client requests a configured API audience.
Once OIDC is configured:
- In Claude.ai, go to Settings > Connectors > Add custom connector
- Enter the MCP endpoint URL:
https://mcp.example.com/mcp - Claude.ai discovers the OIDC endpoints automatically
- You're redirected to your OIDC provider to authenticate
- After authentication, Claude.ai can access your Home Assistant
Back to: Main Documentation