Skip to content

Commit e16809b

Browse files
Patch76claude
andauthored
feat: rename ha_delete_helpers_integrations → ha_remove_helpers_integrations + raise on missing target (#1424)
Closes #1412. Renames `ha_delete_helpers_integrations` → `ha_remove_helpers_integrations` and unifies the tool's behavioural contract across all four routing paths under a single missing-target contract that raises a structured error on confirmed-absent targets — silent `success=True` on absent would be a false-success for wrappers that branch on `success` (typo / hallucinated identifier case). Missing-target contract per path: - Path 1 SIMPLE (helper_id / entity_id): state-machine empty AND entity registry empty → raises ENTITY_NOT_FOUND. Matches sibling `ha_remove_entity`. - Path 2 FLOW step-1 not_in_registry (entity_id): raises ENTITY_NOT_FOUND. Matches Path 2's existing `bare_id_not_supported` branch. - Path 2 FLOW step-3 TOCTOU 404 on resolved entry_id: raises RESOURCE_NOT_FOUND with a distinct "concurrent removal" diagnostic hint. - Path 3 Direct REST 404: raises RESOURCE_NOT_FOUND. - Path 4 Subentry `code: "not_found"`: raises RESOURCE_NOT_FOUND. Idempotency at the contract level still holds (N calls produce the same raise). Transient connectivity errors keep their separate codes (WEBSOCKET_DISCONNECTED, CONNECTION_FAILED). Diagnostic-hint wording on each raise is pinned by substring assertions in the raise-branch tests. Squash-then-rebase against current upstream/master to resolve textual conflicts with #1430 (forecast_solar flaky-marker + relative-import sweep on overlapping e2e test files). The conflict resolution is purely textual — semantic content unchanged from the prior 14-commit history that kp13 approved at bd48061. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 4911d46 commit e16809b

28 files changed

Lines changed: 1087 additions & 588 deletions

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ Spend less time configuring, more time enjoying your smart home.
169169
| **Files** | `ha_delete_file` *(beta)*, `ha_list_files` *(beta)*, `ha_read_file` *(beta)*, `ha_write_file` *(beta)* |
170170
| **Groups** | `ha_config_list_groups`, `ha_config_remove_group`, `ha_config_set_group` |
171171
| **HACS** | `ha_hacs_add_repository`, `ha_hacs_download`, `ha_hacs_repository_info`, `ha_hacs_search` |
172-
| **Helper Entities** | `ha_config_list_helpers`, `ha_config_set_helper`, `ha_delete_helpers_integrations` |
172+
| **Helper Entities** | `ha_config_list_helpers`, `ha_config_set_helper`, `ha_remove_helpers_integrations` |
173173
| **History & Statistics** | `ha_get_automation_traces`, `ha_get_history`, `ha_get_logs` |
174174
| **Integrations** | `ha_get_integration`, `ha_get_system_health`, `ha_set_integration_enabled` |
175175
| **Labels & Categories** | `ha_config_get_category`, `ha_config_get_label`, `ha_config_remove_category`, `ha_config_remove_label`, `ha_config_set_category`, `ha_config_set_label` |

docs/superpowers/specs/2026-05-21-1288-auto-backup-design.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ which would restart HA. Layered defenses:
231231
`ha_set_area_or_floor`, `ha_remove_area_or_floor`,
232232
`ha_set_todo_item`, `ha_remove_todo_item`,
233233
`ha_set_entity`,
234-
`ha_set_integration_enabled`, `ha_delete_helpers_integrations`.
234+
`ha_set_integration_enabled`, `ha_remove_helpers_integrations`.
235235

236236
**Explicitly NOT wrapped:** `ha_call_service`, `ha_call_event`, `ha_restart`, `ha_reload_core`, `ha_check_config`, `ha_eval_template`, `ha_delete_file`, `ha_remove_entity`, `ha_remove_device`, `ha_update_device`, `ha_install_mcp_tools`, `ha_hacs_*`, blueprint/import ops.
237237

homeassistant-addon/DOCS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -443,7 +443,7 @@ The add-on provides 86+ MCP tools for controlling Home Assistant:
443443
### Helper Entities
444444
- `ha_config_list_helpers` — List all Home Assistant helpers of a specific type with their configurations.
445445
- `ha_config_set_helper` — Create or update Home Assistant helper entities and config subentries
446-
- `ha_delete_helpers_integrations` Delete a Home Assistant helper or integration config entry.
446+
- `ha_remove_helpers_integrations` Remove a Home Assistant helper or integration config entry.
447447

448448
### History & Statistics
449449
- `ha_get_automation_traces` — Retrieve execution traces for automations and scripts to debug issues.

site/src/data/tools.json

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1248,7 +1248,7 @@
12481248
{
12491249
"name": "ha_config_remove_group",
12501250
"title": "Remove Group",
1251-
"description": "Remove a service-based Home Assistant entity group via the group.remove service.\n\n**When NOT to use:** for groups created through `ha_config_set_helper(helper_type=\"group\", ...)`,\nuse `ha_delete_helpers_integrations`. Those config-entry-backed groups are not reachable via the\ngroup.remove service.\n\n**When to use:** removing groups created with `ha_config_set_group` or defined in YAML\nvia `group:` configuration. Config-entry-backed deletion tools cannot find these.\n\nEXAMPLES:\n- Remove group: ha_config_remove_group(\"living_room_lights\")\n\nUse ha_config_list_groups() to find existing groups.\n\n**WARNING:**\n- Removing a group used in automations may cause those automations to fail.\n- Groups defined in YAML can be removed at runtime but will reappear after restart.\n- This only removes old-style groups, not platform-specific groups.",
1251+
"description": "Remove a service-based Home Assistant entity group via the group.remove service.\n\n**When NOT to use:** for groups created through `ha_config_set_helper(helper_type=\"group\", ...)`,\nuse `ha_remove_helpers_integrations`. Those config-entry-backed groups are not reachable via the\ngroup.remove service.\n\n**When to use:** removing groups created with `ha_config_set_group` or defined in YAML\nvia `group:` configuration. Config-entry-backed deletion tools cannot find these.\n\nEXAMPLES:\n- Remove group: ha_config_remove_group(\"living_room_lights\")\n\nUse ha_config_list_groups() to find existing groups.\n\n**WARNING:**\n- Removing a group used in automations may cause those automations to fail.\n- Groups defined in YAML can be removed at runtime but will reappear after restart.\n- This only removes old-style groups, not platform-specific groups.",
12521252
"inputSchema": {
12531253
"properties": {
12541254
"object_id": {
@@ -1275,7 +1275,7 @@
12751275
{
12761276
"name": "ha_config_set_group",
12771277
"title": "Create or Update Group",
1278-
"description": "Create or update a service-based Home Assistant entity group via the group.set service.\n\n**When NOT to use:** for typical \"combine these entities into one controllable group\"\nrequests, prefer `ha_config_set_helper(helper_type=\"group\", ...)`. Config-entry-backed\ngroups are registered in the entity registry, so `ha_set_entity` can assign them to\nareas and they are deletable via `ha_delete_helpers_integrations`.\n\n**When to use:** compatibility with existing groups already configured via group.set\nor YAML, or the rare case where entity-registry membership is explicitly unwanted.\nGroups created here are only removable via `ha_config_remove_group` —\n`ha_delete_helpers_integrations` will not find them.\n\n**For NEW groups:** Provide object_id and entities (required).\n**For EXISTING groups:** Provide object_id and any fields to update.\n\nEXAMPLES:\n- Create group: ha_config_set_group(\"bedroom_lights\", entities=[\"light.lamp\", \"light.ceiling\"])\n- Create with name: ha_config_set_group(\"sensors\", entities=[\"sensor.temp\"], name=\"All Sensors\")\n- Update name: ha_config_set_group(\"lights\", name=\"Living Room Lights\")\n- Add entities: ha_config_set_group(\"lights\", add_entities=[\"light.extra\"])\n- Remove entities: ha_config_set_group(\"lights\", remove_entities=[\"light.old\"])\n- Replace all entities: ha_config_set_group(\"lights\", entities=[\"light.new1\", \"light.new2\"])\n\n**NOTE:** entities, add_entities, and remove_entities are mutually exclusive.",
1278+
"description": "Create or update a service-based Home Assistant entity group via the group.set service.\n\n**When NOT to use:** for typical \"combine these entities into one controllable group\"\nrequests, prefer `ha_config_set_helper(helper_type=\"group\", ...)`. Config-entry-backed\ngroups are registered in the entity registry, so `ha_set_entity` can assign them to\nareas and they are deletable via `ha_remove_helpers_integrations`.\n\n**When to use:** compatibility with existing groups already configured via group.set\nor YAML, or the rare case where entity-registry membership is explicitly unwanted.\nGroups created here are only removable via `ha_config_remove_group` —\n`ha_remove_helpers_integrations` will not find them.\n\n**For NEW groups:** Provide object_id and entities (required).\n**For EXISTING groups:** Provide object_id and any fields to update.\n\nEXAMPLES:\n- Create group: ha_config_set_group(\"bedroom_lights\", entities=[\"light.lamp\", \"light.ceiling\"])\n- Create with name: ha_config_set_group(\"sensors\", entities=[\"sensor.temp\"], name=\"All Sensors\")\n- Update name: ha_config_set_group(\"lights\", name=\"Living Room Lights\")\n- Add entities: ha_config_set_group(\"lights\", add_entities=[\"light.extra\"])\n- Remove entities: ha_config_set_group(\"lights\", remove_entities=[\"light.old\"])\n- Replace all entities: ha_config_set_group(\"lights\", entities=[\"light.new1\", \"light.new2\"])\n\n**NOTE:** entities, add_entities, and remove_entities are mutually exclusive.",
12791279
"inputSchema": {
12801280
"properties": {
12811281
"object_id": {
@@ -1640,28 +1640,28 @@
16401640
"source_file": "tools_config_helpers.py"
16411641
},
16421642
{
1643-
"name": "ha_delete_helpers_integrations",
1644-
"title": "Delete Helper or Integration",
1645-
"description": "Delete a Home Assistant helper or integration config entry.\n\nCombines simple-helper websocket deletion, config-entry deletion, and\nconfig-subentry deletion under one entry point with four routing paths\ndriven by helper_type.\n\nWHEN NOT TO USE:\n- Removing only an entity (without deleting its underlying helper or\n config entry) — use `ha_remove_entity` instead.\n- YAML-configured helpers — they have no storage backend. Edit the\n YAML file and reload the relevant integration.\n\nSUPPORTED HELPER TYPES:\n- SIMPLE (12, websocket-delete): input_button, input_boolean,\n input_select, input_number, input_text, input_datetime, counter,\n timer, schedule, zone, person, tag.\n- FLOW (15, config-entry-delete via entity lookup): template, group,\n utility_meter, derivative, min_max, threshold, integration,\n statistics, trend, random, filter, tod, generic_thermostat,\n switch_as_x, generic_hygrostat.\n\nROUTING:\n- SIMPLE helper_type + bare helper_id or entity_id → websocket delete.\n- FLOW helper_type + entity_id → resolve entity_id to config_entry_id\n via entity_registry, then delete the config entry. All sub-entities\n (e.g. utility_meter tariffs) are removed together.\n- helper_type=None + entry_id → direct config entry delete (any\n integration).\n- helper_type=\"config_subentry\" + parent entry_id + subentry_id →\n delete one config subentry.\n\nEXAMPLES:\n- Delete SIMPLE button:\n ha_delete_helpers_integrations(\n target=\"my_button\", helper_type=\"input_button\", confirm=True\n )\n- Delete FLOW utility_meter (any sub-entity works):\n ha_delete_helpers_integrations(\n target=\"sensor.energy_peak\",\n helper_type=\"utility_meter\",\n confirm=True,\n )\n- Delete any integration by entry_id:\n ha_delete_helpers_integrations(\n target=\"01HXYZ...\", confirm=True\n )\n- Delete a config subentry:\n ha_delete_helpers_integrations(\n target=\"01HXYZ...\", helper_type=\"config_subentry\",\n subentry_id=\"subentry-123\", confirm=True\n )\n\n**WARNING:** Deleting a helper or integration that is referenced by\nautomations, scripts, or other integrations may cause those to fail.\nUse ha_search_entities() / ha_get_integration() to verify before\ndeletion. Cannot be undone.",
1643+
"name": "ha_remove_helpers_integrations",
1644+
"title": "Remove Helper or Integration",
1645+
"description": "Remove a Home Assistant helper or integration config entry.\n\nUnifies three backend removal mechanisms — simple-helper websocket\ndelete, config-entry delete, and config-subentry delete — behind one\nentry point with four routing paths driven by helper_type.\n\nWHEN NOT TO USE:\n- Removing only an entity (without deleting its underlying helper or\n config entry) — use `ha_remove_entity` instead.\n- YAML-configured helpers — they have no storage backend. Edit the\n YAML file and reload the relevant integration.\n\nSUPPORTED HELPER TYPES:\n- SIMPLE (12, websocket-delete): input_button, input_boolean,\n input_select, input_number, input_text, input_datetime, counter,\n timer, schedule, zone, person, tag.\n- FLOW (15, config-entry-delete via entity lookup): template, group,\n utility_meter, derivative, min_max, threshold, integration,\n statistics, trend, random, filter, tod, generic_thermostat,\n switch_as_x, generic_hygrostat.\n\nROUTING:\n- SIMPLE helper_type + bare helper_id or entity_id → websocket delete.\n- FLOW helper_type + entity_id → resolve entity_id to config_entry_id\n via entity_registry, then delete the config entry. All sub-entities\n (e.g. utility_meter tariffs) are removed together.\n- helper_type=None + entry_id → direct config entry delete (any\n integration).\n- helper_type=\"config_subentry\" + parent entry_id + subentry_id →\n delete one config subentry.\n\nMISSING-TARGET CONTRACT:\nA target that is *confirmed absent* raises a structured error\nrather than returning silent success, so a typo'd or stale\nidentifier surfaces immediately at the caller layer (the\n``success`` boolean is what agent wrappers branch on). The\nerror code per-path follows the target shape:\n- SIMPLE (bare helper_id or entity_id): state-machine empty AND\n entity registry empty → raises ``ENTITY_NOT_FOUND``.\n- FLOW (entity_id): not in entity registry → raises\n ``ENTITY_NOT_FOUND``. YAML-configured helpers (no config entry\n backing) raise ``RESOURCE_NOT_FOUND``. A bare helper_id (no\n ``.``) on a FLOW target raises ``ENTITY_NOT_FOUND`` — FLOW\n resolution needs a full entity_id. TOCTOU 404 on the\n resolved entry_id raises ``RESOURCE_NOT_FOUND``.\n- Direct config entry (helper_type=None): backend returns HTTP\n 404 → raises ``RESOURCE_NOT_FOUND``.\n- Config subentry: backend returns a \"not_found\" error → raises\n ``RESOURCE_NOT_FOUND``.\n\nIdempotency at the contract level still holds (call N times =\nsame response). Transient connectivity failures (WebSocket\ndisconnected, network timeouts) raise their own codes\n(``WEBSOCKET_DISCONNECTED``, ``CONNECTION_FAILED``) so retry\nlogic can branch separately.\n\nEXAMPLES:\n- Remove SIMPLE button:\n ha_remove_helpers_integrations(\n target=\"my_button\", helper_type=\"input_button\", confirm=True\n )\n- Remove FLOW utility_meter (any sub-entity works):\n ha_remove_helpers_integrations(\n target=\"sensor.energy_peak\",\n helper_type=\"utility_meter\",\n confirm=True,\n )\n- Remove any integration by entry_id:\n ha_remove_helpers_integrations(\n target=\"01HXYZ...\", confirm=True\n )\n- Remove a config subentry:\n ha_remove_helpers_integrations(\n target=\"01HXYZ...\", helper_type=\"config_subentry\",\n subentry_id=\"subentry-123\", confirm=True\n )\n\n**WARNING:** Removing a helper or integration that is referenced by\nautomations, scripts, or other integrations may cause those to fail.\nUse ha_search_entities() / ha_get_integration() to verify before\nremoval. Cannot be undone.",
16461646
"inputSchema": {
16471647
"properties": {
16481648
"target": {
1649-
"type": "Annotated[str, Field(description=\"What to delete. One of: (a) bare helper_id for SIMPLE helpers (requires helper_type), e.g. 'my_button'; (b) full entity_id (requires helper_type), e.g. 'input_button.my_button' or 'sensor.my_meter'; (c) config entry_id for any integration (helper_type=None), e.g. value from ha_get_integration(); (d) parent config entry_id for config_subentry (requires helper_type='config_subentry' and subentry_id).\")]"
1649+
"type": "Annotated[str, Field(description=\"What to remove. One of: (a) bare helper_id for SIMPLE helpers (requires helper_type), e.g. 'my_button'; (b) full entity_id (requires helper_type), e.g. 'input_button.my_button' or 'sensor.my_meter'; (c) config entry_id for any integration (helper_type=None), e.g. value from ha_get_integration(); (d) parent config entry_id for config_subentry (requires helper_type='config_subentry' and subentry_id).\")]"
16501650
},
16511651
"helper_type": {
1652-
"type": "Annotated[HelperTypeLiteral | None, Field(description=\"Helper type. Required when target is a helper_id (bare) or entity_id. Set to None when target is a config entry_id to delete any integration. Use 'config_subentry' to delete a config subentry under target.\", default=None)]",
1652+
"type": "Annotated[HelperTypeLiteral | None, Field(description=\"Helper type. Required when target is a helper_id (bare) or entity_id. Set to None when target is a config entry_id to remove any integration. Use 'config_subentry' to remove a config subentry under target.\", default=None)]",
16531653
"default": null
16541654
},
16551655
"subentry_id": {
1656-
"type": "Annotated[str | None, Field(description=\"Config subentry ID to delete when helper_type='config_subentry'.\", default=None)]",
1656+
"type": "Annotated[str | None, Field(description=\"Config subentry ID to remove when helper_type='config_subentry'.\", default=None)]",
16571657
"default": null
16581658
},
16591659
"confirm": {
1660-
"type": "Annotated[bool | str, Field(description=\"Must be True to confirm deletion. Accepts bool or string ('true'/'false'/'1'/'0'/'yes'/'no'/'on'/'off', case-insensitive) for transport ergonomics.\", default=False)]",
1660+
"type": "Annotated[bool | str, Field(description=\"Must be True to confirm removal. Accepts bool or string ('true'/'false'/'1'/'0'/'yes'/'no'/'on'/'off', case-insensitive) for transport ergonomics.\", default=False)]",
16611661
"default": false
16621662
},
16631663
"wait": {
1664-
"type": "Annotated[bool | str, Field(description=\"Wait for entity removal. Default: True. Ignored when helper_type=None (no entity poll, require_restart returned). Accepts bool or string ('true'/'false'/'1'/'0'/'yes'/'no'/'on'/'off', case-insensitive).\", default=True)]",
1664+
"type": "Annotated[bool | str, Field(description=\"Wait for entity removal. Default: True. Ignored when helper_type=None or helper_type='config_subentry' (no entity poll, require_restart returned). Accepts bool or string ('true'/'false'/'1'/'0'/'yes'/'no'/'on'/'off', case-insensitive).\", default=True)]",
16651665
"default": true
16661666
}
16671667
},
@@ -1670,7 +1670,8 @@
16701670
]
16711671
},
16721672
"annotations": {
1673-
"destructiveHint": true
1673+
"destructiveHint": true,
1674+
"idempotentHint": true
16741675
},
16751676
"tags": [
16761677
"Helper Entities",

src/ha_mcp/tools/tools_groups.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -277,12 +277,12 @@ async def ha_config_set_group(
277277
**When NOT to use:** for typical "combine these entities into one controllable group"
278278
requests, prefer `ha_config_set_helper(helper_type="group", ...)`. Config-entry-backed
279279
groups are registered in the entity registry, so `ha_set_entity` can assign them to
280-
areas and they are deletable via `ha_delete_helpers_integrations`.
280+
areas and they are deletable via `ha_remove_helpers_integrations`.
281281
282282
**When to use:** compatibility with existing groups already configured via group.set
283283
or YAML, or the rare case where entity-registry membership is explicitly unwanted.
284284
Groups created here are only removable via `ha_config_remove_group` —
285-
`ha_delete_helpers_integrations` will not find them.
285+
`ha_remove_helpers_integrations` will not find them.
286286
287287
**For NEW groups:** Provide object_id and entities (required).
288288
**For EXISTING groups:** Provide object_id and any fields to update.
@@ -414,7 +414,7 @@ async def ha_config_remove_group(
414414
Remove a service-based Home Assistant entity group via the group.remove service.
415415
416416
**When NOT to use:** for groups created through `ha_config_set_helper(helper_type="group", ...)`,
417-
use `ha_delete_helpers_integrations`. Those config-entry-backed groups are not reachable via the
417+
use `ha_remove_helpers_integrations`. Those config-entry-backed groups are not reachable via the
418418
group.remove service.
419419
420420
**When to use:** removing groups created with `ha_config_set_group` or defined in YAML

0 commit comments

Comments
 (0)