Related
Follow-up to #787 / #790. The disk persistence fix in #790 writes to ~/.ha-mcp/oauth_state.json inside the container filesystem, which is ephemeral by default. Container restarts still lose all OAuth state.
Root Cause
HomeAssistantOAuthProvider defaults state_dir to Path.home() / ".ha-mcp" (i.e. /root/.ha-mcp in Docker). No VOLUME is declared in the Dockerfile, and __main__.py does not pass a state_dir to the constructor. The HA add-on has the same issue unless /root/.ha-mcp happens to be on a persistent volume managed by Supervisor.
Possible Fixes
Option A — Fully stateless tokens (recommended)
Make both access and refresh tokens stateless by encoding the HA LLAT in each (same base64 pattern already used for access tokens). Set long expiry aligned with the LLAT itself (10 years in HA). On refresh, decode the LLAT from the old refresh token and issue new tokens — no server-side map needed.
- Access token:
base64({"ha_token": "...", "iat": ..., "type": "access"}) — long expiry (e.g. 10 years)
- Refresh token:
base64({"ha_token": "...", "iat": ..., "type": "refresh"}) — long expiry
- Eliminates
_refresh_to_access_map, _save_state(), _load_state(), and all disk I/O
- Survives container restarts by design — zero server-side state for token management
- Compatible with ChatGPT (which requires refresh tokens and refreshes on every tool call regardless of expiry)
- Security boundary unchanged: if the LLAT is revoked in HA, both token types fail immediately on the next API call
Clients still need to re-register (DCR) after container restart, but DCR is automatic and transparent.
Option B — Volume mount + state_dir configuration
Declare a VOLUME in Dockerfile and/or pass state_dir=/data for the HA add-on. Document volume mount for standalone Docker.
- Pros: Minimal code change
- Cons: Requires user to configure volume mount correctly; add-on needs to use Supervisor persistent storage path; doesn't eliminate the complexity of disk persistence code
Recommendation
Option A aligns with the existing stateless access token design and eliminates all server-side persistence. The LLAT is already 10 years in HA — the OAuth token expiry should match rather than impose an artificial 1-hour boundary that forces complex refresh state management.
Related
Follow-up to #787 / #790. The disk persistence fix in #790 writes to
~/.ha-mcp/oauth_state.jsoninside the container filesystem, which is ephemeral by default. Container restarts still lose all OAuth state.Root Cause
HomeAssistantOAuthProviderdefaultsstate_dirtoPath.home() / ".ha-mcp"(i.e./root/.ha-mcpin Docker). NoVOLUMEis declared in the Dockerfile, and__main__.pydoes not pass astate_dirto the constructor. The HA add-on has the same issue unless/root/.ha-mcphappens to be on a persistent volume managed by Supervisor.Possible Fixes
Option A — Fully stateless tokens (recommended)
Make both access and refresh tokens stateless by encoding the HA LLAT in each (same base64 pattern already used for access tokens). Set long expiry aligned with the LLAT itself (10 years in HA). On refresh, decode the LLAT from the old refresh token and issue new tokens — no server-side map needed.
base64({"ha_token": "...", "iat": ..., "type": "access"})— long expiry (e.g. 10 years)base64({"ha_token": "...", "iat": ..., "type": "refresh"})— long expiry_refresh_to_access_map,_save_state(),_load_state(), and all disk I/OClients still need to re-register (DCR) after container restart, but DCR is automatic and transparent.
Option B — Volume mount + state_dir configuration
Declare a
VOLUMEin Dockerfile and/or passstate_dir=/datafor the HA add-on. Document volume mount for standalone Docker.Recommendation
Option A aligns with the existing stateless access token design and eliminates all server-side persistence. The LLAT is already 10 years in HA — the OAuth token expiry should match rather than impose an artificial 1-hour boundary that forces complex refresh state management.