Skip to content

Commit ce5539c

Browse files
Patch76claude
andcommitted
feat: rename ha_delete_helpers_integrations → ha_remove_helpers_integrations + raise on missing target
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 65b03f3 commit ce5539c

28 files changed

Lines changed: 1134 additions & 677 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
@@ -445,7 +445,7 @@ The add-on provides 88+ MCP tools for controlling Home Assistant:
445445
### Helper Entities
446446
- `ha_config_list_helpers` — List all Home Assistant helpers of a specific type with their configurations.
447447
- `ha_config_set_helper` — Create or update Home Assistant helper entities and config subentries
448-
- `ha_delete_helpers_integrations` Delete a Home Assistant helper or integration config entry.
448+
- `ha_remove_helpers_integrations` Remove a Home Assistant helper or integration config entry.
449449

450450
### History & Statistics
451451
- `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
@@ -1276,7 +1276,7 @@
12761276
{
12771277
"name": "ha_config_remove_group",
12781278
"title": "Remove Group",
1279-
"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.",
1279+
"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.",
12801280
"inputSchema": {
12811281
"properties": {
12821282
"object_id": {
@@ -1303,7 +1303,7 @@
13031303
{
13041304
"name": "ha_config_set_group",
13051305
"title": "Create or Update Group",
1306-
"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.",
1306+
"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.",
13071307
"inputSchema": {
13081308
"properties": {
13091309
"object_id": {
@@ -1668,28 +1668,28 @@
16681668
"source_file": "tools_config_helpers.py"
16691669
},
16701670
{
1671-
"name": "ha_delete_helpers_integrations",
1672-
"title": "Delete Helper or Integration",
1673-
"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.",
1671+
"name": "ha_remove_helpers_integrations",
1672+
"title": "Remove Helper or Integration",
1673+
"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.",
16741674
"inputSchema": {
16751675
"properties": {
16761676
"target": {
1677-
"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).\")]"
1677+
"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).\")]"
16781678
},
16791679
"helper_type": {
1680-
"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)]",
1680+
"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)]",
16811681
"default": null
16821682
},
16831683
"subentry_id": {
1684-
"type": "Annotated[str | None, Field(description=\"Config subentry ID to delete when helper_type='config_subentry'.\", default=None)]",
1684+
"type": "Annotated[str | None, Field(description=\"Config subentry ID to remove when helper_type='config_subentry'.\", default=None)]",
16851685
"default": null
16861686
},
16871687
"confirm": {
1688-
"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)]",
1688+
"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)]",
16891689
"default": false
16901690
},
16911691
"wait": {
1692-
"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)]",
1692+
"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)]",
16931693
"default": true
16941694
}
16951695
},
@@ -1698,7 +1698,8 @@
16981698
]
16991699
},
17001700
"annotations": {
1701-
"destructiveHint": true
1701+
"destructiveHint": true,
1702+
"idempotentHint": true
17021703
},
17031704
"tags": [
17041705
"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)