Skip to content

Commit 08d9f0d

Browse files
chore(internal): sync tool docs after merge [skip ci]
1 parent ac4844d commit 08d9f0d

1 file changed

Lines changed: 2 additions & 2 deletions

File tree

site/src/data/tools.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
{
4141
"name": "ha_manage_app",
4242
"title": "Manage App (add-on)",
43-
"description": "Manage a Home Assistant App (formerly known as an add-on, and this tool as ha_manage_addon) — update its configuration or call its internal API.\n\nFive mutually exclusive operating modes:\n\n**Lifecycle mode** (when ``action`` is one of install/uninstall/start/\nstop/restart/rebuild/update):\nRuns a Supervisor add-on action on ``slug``. ``install`` / ``update`` go\nthrough the store (the add-on's repository must be registered — it shows\nup in ``ha_get_app(source=\"available\")``); the rest act on an installed\nadd-on. This is how an assistant brings an add-on online for the user\n(e.g. installing + starting the dashboard screenshot engine).\n\n**Store-repository mode** (when ``action`` is ``add_repository`` or\n``remove_repository``):\nRegisters or unregisters a custom add-on store repository. These actions\noperate on the store rather than an installed add-on, so they take the\n``repository`` param and no ``slug``: ``add_repository`` POSTs the\nrepository URL to ``/store/repositories``; ``remove_repository`` DELETEs\n``/store/repositories/{slug}`` by the repository's slug. Adding a\nrepository (e.g. balloob's add-ons) is the missing step that lets an\nassistant then install an add-on from it via ``action=\"install\"``.\n\n**Config mode** (when any of options/network/boot/auto_update/watchdog is provided):\nUpdates the add-on's Supervisor configuration via POST /addons/{slug}/options.\nAll config parameters are optional; only provided fields are updated — current values\nare fetched and merged automatically (including one level of nested dicts).\n\n**Proxy mode** (when path is provided without array_patch):\nRoutes HTTP or WebSocket requests through Home Assistant's Ingress\nproxy by default (works on HAOS, Supervised, and off-host PyPI/uvx\ninstalls). Pass `port=...` to bypass Ingress and connect directly to\nan add-on's container port — that mode requires the MCP host to\nshare Home Assistant's container network (i.e. only the HAOS addon).\n\n**ESPHome Device Builder dashboard (current rewrite):** config and log\naccess is a WebSocket JSON-command API, NOT REST. The legacy endpoints\nare gone — `GET /edit?configuration=` now returns the dashboard SPA, and\nthe old `/compile` `/validate` `/logs` WebSocket paths (which took\n`{\"type\": \"spawn\", ...}` bodies) reject the upgrade (HTTP 200). Use\ninstead:\n- HTTP `GET /devices` → JSON list of configured devices; each entry's\n `configuration` field is the YAML filename to pass below.\n- WebSocket `path=\"/ws\"` with body\n `{\"command\": \"<cmd>\", \"message_id\": \"1\", \"args\": {...}}`. The server\n sends a `server_info` message first, then one reply per `message_id`.\n Wire-confirmed commands: `devices/get_config` `{configuration}` → raw\n YAML (in the reply's `result`); `devices/logs` (stream)\n `{configuration, port: \"OTA\"}` → live device logs. Also exposed by the\n dashboard frontend (command/arg names not wire-tested here):\n `devices/update_config` `{configuration, content}` → save,\n `devices/validate`, `firmware/compile`.\n- The `/ws` channel stays open, so for a one-shot read or a bounded log\n capture pass `wait_for_close=False` with `message_limit` (and\n `message_offset` to skip the server_info / config-banner preamble).\n Reach the dashboard through Ingress — omit `port`; direct `port=` does\n not route to it.\n\n**Array-patch mode** (when path AND array_patch are provided):\nAtomic \"GET array, mutate, POST array\" workflow for addon APIs whose write\ncontract is \"send the whole resource collection back\". Operations are applied\nin order to a working copy; if any op fails validation (unknown id, collision,\nmalformed shape) nothing is posted. Returns a compact summary instead of the\nfull array. Designed for Node-RED /flows and similar endpoints.\n\n**Response shaping (proxy mode):**\n- WebSocket streams can be noisy (e.g. the ESPHome dashboard's devices/logs\n dumps the device's full config banner on connect), which is what\n `summarize` is for. INFO/WARNING/ERROR/exit lines always pass through\n it, and pagination via `message_offset` / `message_limit` works on the\n raw collected list before summarize runs.\n- `python_transform` runs after slicing and summarize, and before the\n response size cap, so it can narrow an oversized response back under\n the limit. What `response` binds to and what may be done to it is on\n the parameter itself. Only the interaction is here: undecodable\n WebSocket frames arrive as ANSI-stripped strings, and elision markers\n as `{\"elided\": N, \"note\": \"...\"}` dicts when summarize ran.\n\n**WARNING:** Setting boot=\"auto\"/\"manual\" will fail for add-ons whose Supervisor\nmetadata locks the boot mode. The Supervisor returns an error in this case.\n\n**NOTE:** This tool only works with Home Assistant OS or Supervised installations.\n\n**Examples:**\n- Install an add-on: ha_manage_app(slug=\"...\", action=\"install\")\n- Start an add-on: ha_manage_app(slug=\"...\", action=\"start\")\n- Add a store repository: ha_manage_app(action=\"add_repository\", repository=\"https://github.com/balloob/home-assistant-addons\")\n- Remove a store repository: ha_manage_app(action=\"remove_repository\", repository=\"0f1cc410\")\n- Set add-on option: ha_manage_app(slug=\"...\", options={\"log_level\": \"debug\"})\n Note: only the fields you provide are updated — current values are fetched first\n and merged automatically. Fields not in the add-on's schema are ignored with a warning.\n- Disable auto-update: ha_manage_app(slug=\"...\", auto_update=False)\n- Change host port: ha_manage_app(slug=\"...\", network={\"5800/tcp\": 8082})\n- Set boot mode: ha_manage_app(slug=\"...\", boot=\"manual\")\n- Call HTTP API: ha_manage_app(slug=\"...\", path=\"/api/events\")\n- Direct port: ha_manage_app(slug=\"...\", path=\"/flows\", port=1880)\n- ESPHome list devices (HTTP): ha_manage_app(slug=\"<prefix>_esphome\", path=\"/devices\")\n- ESPHome read a device's YAML (WS one-shot): ha_manage_app(slug=\"<prefix>_esphome\", path=\"/ws\", websocket=True, wait_for_close=False, message_limit=2, body={\"command\": \"devices/get_config\", \"message_id\": \"1\", \"args\": {\"configuration\": \"device.yaml\"}})\n- ESPHome live logs (WS, bounded): ha_manage_app(slug=\"<prefix>_esphome\", path=\"/ws\", websocket=True, wait_for_close=False, message_limit=60, body={\"command\": \"devices/logs\", \"message_id\": \"1\", \"args\": {\"configuration\": \"device.yaml\", \"port\": \"OTA\"}})\n- Filter WS errors only: ha_manage_app(slug=\"...\", path=\"/ws\", websocket=True, python_transform=\"response = [m for m in response if 'ERROR' in str(m) or 'WARN' in str(m)]\")\n- HTTP subset: ha_manage_app(slug=\"...\", path=\"/flows\", python_transform=\"response = [f['id'] for f in response]\")\n- Array-patch (Node-RED, rename a node):\n ha_manage_app(\n slug=\"a0d7b954_nodered\", path=\"/flows\",\n array_patch={\"operations\": [\n {\"op\": \"patch\", \"id\": \"abc123\", \"patches\": {\"name\": \"New Name\"}},\n ]},\n )\n- Array-patch (Node-RED, replace one tab's nodes atomically):\n ha_manage_app(\n slug=\"a0d7b954_nodered\", path=\"/flows\",\n array_patch={\"operations\": [\n {\"op\": \"delete_where\", \"field\": \"z\", \"value\": \"tab-id\"},\n {\"op\": \"add\", \"item\": {\"id\": \"n1\", \"type\": \"inject\", \"z\": \"tab-id\", ...}},\n {\"op\": \"add\", \"item\": {\"id\": \"n2\", \"type\": \"function\", \"z\": \"tab-id\", ...}},\n ]},\n request_headers={\"Node-RED-Deployment-Type\": \"full\"},\n )\n- Custom request headers (proxy mode):\n ha_manage_app(slug=\"...\", path=\"/api/state\",\n request_headers={\"Accept\": \"text/plain\"})",
43+
"description": "Manage a Home Assistant App (formerly known as an add-on, and this tool as ha_manage_addon) — update its configuration or call its internal API.\n\nFive mutually exclusive operating modes:\n\n**Lifecycle mode** (when ``action`` is one of install/uninstall/start/\nstop/restart/rebuild/update):\nRuns a Supervisor add-on action on ``slug``. ``install`` / ``update`` go\nthrough the store (the add-on's repository must be registered — it shows\nup in ``ha_get_app(source=\"available\")``); the rest act on an installed\nadd-on. This is how an assistant brings an add-on online for the user\n(e.g. installing + starting the dashboard screenshot engine).\n\n**Store-repository mode** (when ``action`` is ``add_repository`` or\n``remove_repository``):\nRegisters or unregisters a custom add-on store repository. These actions\noperate on the store rather than an installed add-on, so they take the\n``repository`` param and no ``slug``: ``add_repository`` POSTs the\nrepository URL to ``/store/repositories``; ``remove_repository`` DELETEs\n``/store/repositories/{slug}`` by the repository's slug. Adding a\nrepository (e.g. balloob's add-ons) is the missing step that lets an\nassistant then install an add-on from it via ``action=\"install\"``.\n\n**Config mode** (when any of options/network/boot/auto_update/watchdog is provided):\nUpdates the add-on's Supervisor configuration via POST /addons/{slug}/options.\nAll config parameters are optional; only provided fields are updated — current values\nare fetched and merged automatically (including one level of nested dicts).\n\n**Proxy mode** (when path is provided without array_patch):\nRoutes HTTP or WebSocket requests through Home Assistant's Ingress\nproxy by default (works on HAOS, Supervised, and off-host PyPI/uvx\ninstalls). Pass `port=...` to bypass Ingress and connect directly to\nan add-on's container port — that mode requires the MCP host to\nshare Home Assistant's container network (in practice: the HAOS app\n(add-on) deployment). Apps such as Node-RED may reject direct requests\nunless `leave_front_door_open` is enabled in their options and the app\nis restarted. Authentication errors name the exact\n`ha_manage_app(options=...)` remedy and its security tradeoff; prefer\nIngress when it works.\n\n**ESPHome Device Builder dashboard (current rewrite):** config and log\naccess is a WebSocket JSON-command API, NOT REST. The legacy endpoints\nare gone — `GET /edit?configuration=` now returns the dashboard SPA, and\nthe old `/compile` `/validate` `/logs` WebSocket paths (which took\n`{\"type\": \"spawn\", ...}` bodies) reject the upgrade (HTTP 200). Use\ninstead:\n- HTTP `GET /devices` → JSON list of configured devices; each entry's\n `configuration` field is the YAML filename to pass below.\n- WebSocket `path=\"/ws\"` with body\n `{\"command\": \"<cmd>\", \"message_id\": \"1\", \"args\": {...}}`. The server\n sends a `server_info` message first, then one reply per `message_id`.\n Wire-confirmed commands: `devices/get_config` `{configuration}` → raw\n YAML (in the reply's `result`); `devices/logs` (stream)\n `{configuration, port: \"OTA\"}` → live device logs. Also exposed by the\n dashboard frontend (command/arg names not wire-tested here):\n `devices/update_config` `{configuration, content}` → save,\n `devices/validate`, `firmware/compile`.\n- The `/ws` channel stays open, so for a one-shot read or a bounded log\n capture pass `wait_for_close=False` with `message_limit` (and\n `message_offset` to skip the server_info / config-banner preamble).\n Reach the dashboard through Ingress — omit `port`; direct `port=` does\n not route to it.\n\n**Array-patch mode** (when path AND array_patch are provided):\nAtomic \"GET array, mutate, POST array\" workflow for addon APIs whose write\ncontract is \"send the whole resource collection back\". Operations are applied\nin order to a working copy; if any op fails validation (unknown id, collision,\nmalformed shape) nothing is posted. Returns a compact summary instead of the\nfull array. Designed for Node-RED /flows and similar endpoints.\n\n**Response shaping (proxy mode):**\n- WebSocket streams can be noisy (e.g. the ESPHome dashboard's devices/logs\n dumps the device's full config banner on connect), which is what\n `summarize` is for. INFO/WARNING/ERROR/exit lines always pass through\n it, and pagination via `message_offset` / `message_limit` works on the\n raw collected list before summarize runs.\n- `python_transform` runs after slicing and summarize, and before the\n response size cap, so it can narrow an oversized response back under\n the limit. What `response` binds to and what may be done to it is on\n the parameter itself. Only the interaction is here: undecodable\n WebSocket frames arrive as ANSI-stripped strings, and elision markers\n as `{\"elided\": N, \"note\": \"...\"}` dicts when summarize ran.\n\n**WARNING:** Setting boot=\"auto\"/\"manual\" will fail for add-ons whose Supervisor\nmetadata locks the boot mode. The Supervisor returns an error in this case.\n\n**NOTE:** This tool only works with Home Assistant OS or Supervised installations.\n\n**Examples:**\n- Install an add-on: ha_manage_app(slug=\"...\", action=\"install\")\n- Start an add-on: ha_manage_app(slug=\"...\", action=\"start\")\n- Add a store repository: ha_manage_app(action=\"add_repository\", repository=\"https://github.com/balloob/home-assistant-addons\")\n- Remove a store repository: ha_manage_app(action=\"remove_repository\", repository=\"0f1cc410\")\n- Set add-on option: ha_manage_app(slug=\"...\", options={\"log_level\": \"debug\"})\n Note: only the fields you provide are updated — current values are fetched first\n and merged automatically. Fields not in the add-on's schema are ignored with a warning.\n- Disable auto-update: ha_manage_app(slug=\"...\", auto_update=False)\n- Change host port: ha_manage_app(slug=\"...\", network={\"5800/tcp\": 8082})\n- Set boot mode: ha_manage_app(slug=\"...\", boot=\"manual\")\n- Call HTTP API: ha_manage_app(slug=\"...\", path=\"/api/events\")\n- Direct port: ha_manage_app(slug=\"...\", path=\"/flows\", port=1880) — if the app rejects it, the error names the 'leave_front_door_open' remedy and its security trade-off; prefer Ingress.\n- ESPHome list devices (HTTP): ha_manage_app(slug=\"<prefix>_esphome\", path=\"/devices\")\n- ESPHome read a device's YAML (WS one-shot): ha_manage_app(slug=\"<prefix>_esphome\", path=\"/ws\", websocket=True, wait_for_close=False, message_limit=2, body={\"command\": \"devices/get_config\", \"message_id\": \"1\", \"args\": {\"configuration\": \"device.yaml\"}})\n- ESPHome live logs (WS, bounded): ha_manage_app(slug=\"<prefix>_esphome\", path=\"/ws\", websocket=True, wait_for_close=False, message_limit=60, body={\"command\": \"devices/logs\", \"message_id\": \"1\", \"args\": {\"configuration\": \"device.yaml\", \"port\": \"OTA\"}})\n- Filter WS errors only: ha_manage_app(slug=\"...\", path=\"/ws\", websocket=True, python_transform=\"response = [m for m in response if 'ERROR' in str(m) or 'WARN' in str(m)]\")\n- HTTP subset: ha_manage_app(slug=\"...\", path=\"/flows\", python_transform=\"response = [f['id'] for f in response]\")\n- Array-patch (Node-RED, rename a node):\n ha_manage_app(\n slug=\"a0d7b954_nodered\", path=\"/flows\",\n array_patch={\"operations\": [\n {\"op\": \"patch\", \"id\": \"abc123\", \"patches\": {\"name\": \"New Name\"}},\n ]},\n )\n- Array-patch (Node-RED, replace one tab's nodes atomically):\n ha_manage_app(\n slug=\"a0d7b954_nodered\", path=\"/flows\",\n array_patch={\"operations\": [\n {\"op\": \"delete_where\", \"field\": \"z\", \"value\": \"tab-id\"},\n {\"op\": \"add\", \"item\": {\"id\": \"n1\", \"type\": \"inject\", \"z\": \"tab-id\", ...}},\n {\"op\": \"add\", \"item\": {\"id\": \"n2\", \"type\": \"function\", \"z\": \"tab-id\", ...}},\n ]},\n request_headers={\"Node-RED-Deployment-Type\": \"full\"},\n )\n- Custom request headers (proxy mode):\n ha_manage_app(slug=\"...\", path=\"/api/state\",\n request_headers={\"Accept\": \"text/plain\"})",
4444
"inputSchema": {
4545
"properties": {
4646
"slug": {
@@ -64,7 +64,7 @@
6464
"default": false
6565
},
6666
"port": {
67-
"type": "Annotated[int | None, Field(description=\"Proxy mode only. Connect to this port instead of the Ingress port. Use ha_get_app(slug='...') to find available ports.\", default=None)]",
67+
"type": "Annotated[int | None, Field(description=\"Proxy mode only. Connect to this port instead of the Ingress port. Use ha_get_app(slug='...') to find available ports. Some apps, including Node-RED, reject direct access unless their leave_front_door_open option is enabled and the app is restarted; related errors include an actionable, security-qualified ha_manage_app options command.\", default=None)]",
6868
"default": null
6969
},
7070
"offset": {

0 commit comments

Comments
 (0)