mirror of
https://github.com/alexbelgium/hassio-addons.git
synced 2026-08-15 01:22:29 +02:00
fix(claude_desktop): correct HA MCP endpoint, harden config perms and chmod scope
Home Assistant's MCP Server integration serves stateless Streamable HTTP at /api/mcp; mcp-proxy defaults to SSE, so the previous registration (SSE at /mcp_server/sse) could never attach. Pass --transport=streamablehttp --stateless and default ha_mcp_url to /api/mcp. Match managed MCP entries by binary basename outside $HOME so a base-image path change still updates them, while user-installed binaries under $HOME remain untouched. Resolve tokensave via command -v like the others. Write Claude config files 0600 (they hold the HA long-lived token in clear text) and scope the build-time chmod +x pass to the shipped script dirs. Docs: dashboard reachability wording, stale /config/data HOME, and the custom-script filename (claude_desktop.sh, per the $slug.sh template). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -5,7 +5,9 @@
|
||||
- Fix the Headroom dashboard being unreachable at `http://<host>:8787/dashboard`: the supervised proxy only listened on `127.0.0.1`; it now binds `0.0.0.0` so the mapped port works.
|
||||
- Fix dispatch/remote sessions and sign-in persistence: install the missing `gnome-keyring` package. The existing keyring bootstrap silently no-oped without it, leaving Electron `safeStorage` unavailable ("cannot store allowlist cache"), so auth tokens and dispatch permission grants were lost on restart.
|
||||
- Add the tokensave code-intelligence MCP server (pinned 7.2.0, built from source like RTK), registered for both Claude Desktop and Claude Code; disable with `install_tokensave: false`.
|
||||
- Implement the Home Assistant MCP bridge for real: `enable_ha_mcp` plus new `ha_mcp_url`/`ha_mcp_token` options register Home Assistant's MCP Server integration in Claude through `mcp-proxy`.
|
||||
- Implement the Home Assistant MCP bridge for real: `enable_ha_mcp` plus new `ha_mcp_url`/`ha_mcp_token` options register Home Assistant's MCP Server integration in Claude through `mcp-proxy`, using the integration's stateless Streamable HTTP endpoint (`/api/mcp`).
|
||||
- Write the Claude configuration files with `0600` permissions, since they hold the Home Assistant access token in clear text.
|
||||
- Restrict the build-time `chmod +x` pass to the directories the add-on actually ships scripts in instead of traversing the whole image.
|
||||
- Register add-on-managed MCP servers in Claude Code's `~/.claude.json` as well as Claude Desktop's config, without clobbering user-customized entries.
|
||||
- Install `uv` and use it for the `additional_pip` option for much faster package installs.
|
||||
|
||||
|
||||
@@ -69,9 +69,11 @@ RUN curl -fsSL --retry 3 --retry-delay 2 \
|
||||
# 3 Install apps #
|
||||
##################
|
||||
|
||||
# Add rootfs
|
||||
# Add rootfs. Only the directories this add-on ships scripts in are traversed, so the chmod
|
||||
# cannot alter executables elsewhere in the image.
|
||||
COPY rootfs/ /
|
||||
RUN find . -type f \( -name "*.sh" -o -name "run" -o -name "finish" \) -print -exec chmod +x {} \;
|
||||
RUN find /etc/cont-init.d /etc/s6-overlay /defaults /usr/local/bin -type f \
|
||||
\( -name "*.sh" -o -name "run" -o -name "finish" \) -print -exec chmod +x {} \;
|
||||
|
||||
# Uses /bin for compatibility purposes
|
||||
# hadolint ignore=DL4005
|
||||
|
||||
@@ -96,10 +96,10 @@ The Headroom dashboard is available at:
|
||||
http://<home-assistant-host>:8787/dashboard
|
||||
```
|
||||
|
||||
through the default `8787/tcp` port mapping. Treat this endpoint as sensitive:
|
||||
it serves your local network only — do not expose it directly to the public
|
||||
internet, and unmap the port in the add-on **Network** section if you do not
|
||||
want it reachable at all.
|
||||
through the default `8787/tcp` port mapping. The dashboard is unauthenticated
|
||||
and is reachable wherever Home Assistant publishes that port, so treat it as
|
||||
sensitive: do not expose it directly to the public internet, and unmap the port
|
||||
in the add-on **Network** section if you do not want it reachable at all.
|
||||
|
||||
## Home Assistant MCP bridge
|
||||
|
||||
@@ -111,9 +111,9 @@ To let Claude query and control Home Assistant:
|
||||
3. Set `enable_ha_mcp: true` and paste the token into `ha_mcp_token` in the
|
||||
add-on configuration, then restart the add-on.
|
||||
|
||||
The add-on bridges Claude to the integration's SSE endpoint with `mcp-proxy`.
|
||||
Override `ha_mcp_url` only if your Home Assistant instance is not reachable as
|
||||
`homeassistant:8123` from add-ons.
|
||||
The add-on bridges Claude to the integration's stateless Streamable HTTP
|
||||
endpoint (`/api/mcp`) with `mcp-proxy`. Override `ha_mcp_url` only if your Home
|
||||
Assistant instance is not reachable as `homeassistant:8123` from add-ons.
|
||||
|
||||
## Custom scripts
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ magic link into the in-session Chromium (not a phone).
|
||||
- Add-on Configuration → `additional_apps: chromium`, restart (installed by
|
||||
`rootfs/etc/cont-init.d/80-configuration.sh`).
|
||||
- Add the two `xdg-settings`/`xdg-mime` commands to the custom script
|
||||
`/addon_configs/db21ed7f_claude-desktop/claude-desktop.sh` (the image ships no standalone
|
||||
`/addon_configs/db21ed7f_claude-desktop/claude_desktop.sh` (the image ships no standalone
|
||||
terminal).
|
||||
|
||||
---
|
||||
@@ -98,14 +98,14 @@ Claude Desktop uses. No extra `dbus-launch` is needed.
|
||||
then exposes the Secret Service and exports `GNOME_KEYRING_CONTROL`/`SSH_AUTH_SOCK`.
|
||||
- `--password-store=gnome-libsecret` forces Electron to use the libsecret backend instead
|
||||
of falling back to plaintext.
|
||||
3. Persistence: the keyring DB lives in `$HOME/.local/share/keyrings/` and `HOME=/config/data`
|
||||
3. Persistence: the keyring DB lives in `$HOME/.local/share/keyrings/` and `HOME=/data/data`
|
||||
(persistent add-on storage), so the empty-password login keyring survives restarts and is
|
||||
re-unlocked automatically each boot by the same `autostart` line — the sign-in then sticks.
|
||||
|
||||
### User-side workaround (no rebuild)
|
||||
- Add-on Configuration → `additional_apps: gnome-keyring, libsecret-1-0, dbus-x11`, restart.
|
||||
- Add the keyring-start lines above to the custom script
|
||||
`/addon_configs/db21ed7f_claude-desktop/claude-desktop.sh`, and relaunch Claude Desktop
|
||||
`/addon_configs/db21ed7f_claude-desktop/claude_desktop.sh`, and relaunch Claude Desktop
|
||||
with `--password-store=gnome-libsecret` (e.g. edit the in-session openbox autostart).
|
||||
|
||||
---
|
||||
|
||||
@@ -42,7 +42,7 @@ options:
|
||||
auto_update: true
|
||||
github_email: ""
|
||||
enable_ha_mcp: false
|
||||
ha_mcp_url: http://homeassistant:8123/mcp_server/sse
|
||||
ha_mcp_url: http://homeassistant:8123/api/mcp
|
||||
ha_mcp_token: ""
|
||||
github_token: ""
|
||||
github_username: ""
|
||||
|
||||
@@ -48,7 +48,7 @@ HA_MCP_ENABLED=false
|
||||
HA_MCP_URL=""
|
||||
HA_MCP_TOKEN=""
|
||||
if bashio::config.true 'enable_ha_mcp'; then
|
||||
HA_MCP_URL="$(bashio::config 'ha_mcp_url' 'http://homeassistant:8123/mcp_server/sse')"
|
||||
HA_MCP_URL="$(bashio::config 'ha_mcp_url' 'http://homeassistant:8123/api/mcp')"
|
||||
if bashio::config.has_value 'ha_mcp_token'; then
|
||||
HA_MCP_TOKEN="$(bashio::config 'ha_mcp_token')"
|
||||
fi
|
||||
@@ -63,7 +63,7 @@ if bashio::config.true 'enable_ha_mcp'; then
|
||||
fi
|
||||
|
||||
HEADROOM_ENABLED="$HEADROOM_ENABLED" HEADROOM_BIN="$(command -v headroom || echo headroom)" \
|
||||
TOKENSAVE_ENABLED="$TOKENSAVE_ENABLED" TOKENSAVE_BIN="/usr/local/bin/tokensave" \
|
||||
TOKENSAVE_ENABLED="$TOKENSAVE_ENABLED" TOKENSAVE_BIN="$(command -v tokensave || echo tokensave)" \
|
||||
HA_MCP_ENABLED="$HA_MCP_ENABLED" HA_MCP_URL="$HA_MCP_URL" HA_MCP_TOKEN="$HA_MCP_TOKEN" \
|
||||
MCP_PROXY_BIN="$(command -v mcp-proxy || echo mcp-proxy)" \
|
||||
CLAUDE_DESKTOP_CONFIG="$CLAUDE_DESKTOP_CONFIG" CLAUDE_CODE_CONFIG="$CLAUDE_CODE_CONFIG" \
|
||||
@@ -72,10 +72,10 @@ import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
MANAGED_COMMANDS = {
|
||||
"headroom": {os.environ["HEADROOM_BIN"], "headroom"},
|
||||
"tokensave": {os.environ["TOKENSAVE_BIN"], "tokensave"},
|
||||
"homeassistant": {os.environ["MCP_PROXY_BIN"], "mcp-proxy"},
|
||||
MANAGED_BASENAMES = {
|
||||
"headroom": "headroom",
|
||||
"tokensave": "tokensave",
|
||||
"homeassistant": "mcp-proxy",
|
||||
}
|
||||
|
||||
desired = {}
|
||||
@@ -84,14 +84,27 @@ if os.environ["HEADROOM_ENABLED"] == "true":
|
||||
if os.environ["TOKENSAVE_ENABLED"] == "true":
|
||||
desired["tokensave"] = {"command": os.environ["TOKENSAVE_BIN"], "args": ["serve"]}
|
||||
if os.environ["HA_MCP_ENABLED"] == "true":
|
||||
# Home Assistant's MCP Server integration speaks stateless Streamable HTTP on /api/mcp;
|
||||
# mcp-proxy defaults to SSE, so the transport flags are required.
|
||||
desired["homeassistant"] = {
|
||||
"command": os.environ["MCP_PROXY_BIN"],
|
||||
"args": [os.environ["HA_MCP_URL"]],
|
||||
"args": ["--transport=streamablehttp", "--stateless", os.environ["HA_MCP_URL"]],
|
||||
"env": {"API_ACCESS_TOKEN": os.environ["HA_MCP_TOKEN"]},
|
||||
}
|
||||
|
||||
# An entry is add-on-managed when its command is one of our binaries living outside the
|
||||
# persistent home. Matching on the basename (rather than the exact path recorded at write
|
||||
# time) keeps entries updatable when a base-image upgrade moves the binary, while commands
|
||||
# under $HOME stay untouched because those are user-installed.
|
||||
HOME_PREFIX = os.path.expanduser("~") + os.sep
|
||||
|
||||
def is_managed(name, entry):
|
||||
return isinstance(entry, dict) and entry.get("command") in MANAGED_COMMANDS[name]
|
||||
if not isinstance(entry, dict):
|
||||
return False
|
||||
command = entry.get("command")
|
||||
if not isinstance(command, str) or command.startswith(HOME_PREFIX):
|
||||
return False
|
||||
return os.path.basename(command) == MANAGED_BASENAMES[name]
|
||||
|
||||
for config_var, stdio_type in (("CLAUDE_DESKTOP_CONFIG", False), ("CLAUDE_CODE_CONFIG", True)):
|
||||
path = Path(os.environ[config_var])
|
||||
@@ -107,7 +120,7 @@ for config_var, stdio_type in (("CLAUDE_DESKTOP_CONFIG", False), ("CLAUDE_CODE_C
|
||||
if not isinstance(servers, dict):
|
||||
servers = {}
|
||||
changed = False
|
||||
for name in MANAGED_COMMANDS:
|
||||
for name in MANAGED_BASENAMES:
|
||||
existing = servers.get(name)
|
||||
if name in desired:
|
||||
entry = dict(desired[name])
|
||||
@@ -128,6 +141,8 @@ for config_var, stdio_type in (("CLAUDE_DESKTOP_CONFIG", False), ("CLAUDE_CODE_C
|
||||
data.pop("mcpServers", None)
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(json.dumps(data, indent=2) + "\n")
|
||||
# The Home Assistant long-lived access token is stored here in clear text.
|
||||
path.chmod(0o600)
|
||||
PY
|
||||
|
||||
# Guide Claude to actually use the headroom compression tools so the MCP integration produces
|
||||
|
||||
Reference in New Issue
Block a user