Skip to content

Commit c45ba5a

Browse files
feat: introduce ha_delete_helpers_integrations to consolidate helper/config-entry deletion (#1007) (#1056)
* feat: add _get_entry_id_for_flow_helper lookup for #1007 phase 2 Adds a module-private async helper in tools_integrations.py that resolves a flow-helper target (entity_id or bare ID) to its config_entry_id via the config/entity_registry/get WebSocket API. Foundation for the upcoming ha_delete_helpers_integrations tool that unifies ha_config_remove_helper and ha_delete_config_entry (#1007). Routing in the upcoming tool: - helper_type in SIMPLE_HELPER_TYPES -> existing SIMPLE WebSocket delete - helper_type in FLOW_HELPER_TYPES -> this lookup, then delete_config_entry - helper_type is None -> direct delete_config_entry by entry_id Follows the warnings: list[str] | None convention from _get_entities_for_config_entry for surfacing partial WebSocket failures (addresses adversarial finding A2 from the design phase). Refs: #1007 * feat: add ha_delete_helpers_integrations tool for #1007 phase 2 Adds a new MCP tool that unifies helper deletion (12 SIMPLE types via WebSocket) and config-entry deletion (15 FLOW types + generic integrations) into a single tool with three routing paths driven by helper_type. Routing: - SIMPLE helper_type -> WebSocket {type}/delete (mirrors ha_config_remove_helper including 3-retry registry lookup, direct-id fallback, already-deleted check) - FLOW helper_type -> entity_id -> config_entry_id lookup -> delete_config_entry with parallel multi-entity wait (utility_meter pattern) - helper_type=None -> direct config_entry delete (mirrors ha_delete_config_entry) Coexists with ha_config_remove_helper and ha_delete_config_entry; those will be removed in subsequent commits after test migration. Includes 15 unit tests covering all three paths plus confirm gate, fallback strategies, and error mode mapping (ENTITY_NOT_FOUND vs RESOURCE_NOT_FOUND vs SERVICE_CALL_FAILED). Tool count: 87 -> 88. Generated docs (tools.json, README.md, DOCS.md) included. Refs: #1007 * test: migrate to ha_delete_helpers_integrations for #1007 phase 2 Migrate 63 call sites in 12 test files from ha_config_remove_helper and ha_delete_config_entry to the new ha_delete_helpers_integrations tool. Two register_tools[...] sites in tests/src/unit/test_wait_parameter.py intentionally left on ha_config_remove_helper - they test wait-behaviour of the legacy tool and will be removed together with the tool itself in the next commit (phase 2 commit 3). Production code untouched (handled in subsequent commits). #1007 * fix: address Gemini review on PR #1056 (G1 + G3) - _get_entry_id_for_flow_helper: return None for bare IDs instead of guessing helper_type as domain prefix. Flow helpers often have entity domains that differ from helper_type (utility_meter→sensor.*, switch_as_x→switch/light.*); guessing produced incorrect entity_ids that always failed the registry lookup. Caller must provide full entity_id for flow helpers. - _get_entry_id_for_flow_helper: log WebSocket exceptions at debug level for observability before appending to warnings list. - Update unit test to verify bare-id rejection. - Update docstring to reflect entity_id requirement. * fix: improve ENTITY_NOT_FOUND diagnostic for flow helpers (G5) Adds the constructed entity_id to the error message and an actionable suggestion pointing at ha_search_entities() with the rationale that flow helper types often expose entities under a different domain than the helper_type (utility_meter → sensor.*, switch_as_x → switch.* / light.*). Addresses Gemini review G5 on PR #1056. * fix(integrations): address PR #1056 review + remove predecessors per AGENTS.md Adopts the consolidated end-state in this PR: removes ha_config_remove_helper and ha_delete_config_entry rather than shipping them alongside the new tool for one release. Aligns with AGENTS.md "Tool Consolidation" ("remove rather than deprecate") and the direction kingpanther13 outlined in #1007. Required items from review: 1. Narrowed `except Exception: pass` and `except Exception` in the already-deleted state-check and the retry-loop state-check to `except HomeAssistantAPIError`. Auth/connection errors now propagate instead of being silently re-reported as ENTITY_NOT_FOUND. 2. Removed catch-all `except Exception` from the three wait_for_entity_removed sites. Typed exceptions (HomeAssistantConnectionError/AuthError) now reach the outer handler. Reworded "may still appear briefly" → "is still present after the wait window" to match what `False` actually means. Note: wait_for_entity_removed does not raise asyncio.TimeoutError — its internal loop returns False on timeout. The relevant fix was removing the broad except, not adding a TimeoutError catch. 3. Dropped the hallucinated "Z.1860 vs Z.192 convention in this codebase" comment. 4. Removed user-facing references to the predecessors from the new tool's docstring (no "previous ha_config_remove_helper / ha_delete_config_entry" leaking into tools.json / DOCS.md). 5. Tag changed from {"Helpers", "Integrations"} to {"Helper Entities", "Integrations"} — drops the duplicate one-tool "Helpers" README category. 6. Added "WHEN NOT TO USE" section to the docstring per AGENTS.md tool docstring structure. 7. Added wait=True coverage in tests/src/unit/test_tools_integrations.py: happy-path (no warning), timeout (warning, success=True), typed ConnectionError propagation, and the multi-entity utility_meter partial-timeout case. 8. Replaced string-sniffing `"404" in error_msg` at three sites with typed dispatch via `exception_to_structured_error(e, context=..., suggestions=...)`. Suggestions preserved via the kwarg. 9. Changed `_get_entry_id_for_flow_helper` to return a discriminated `(entry_id | None, FlowLookupReason)` tuple. The caller uses `reason` to disambiguate; the extra entity_registry/get round-trip on the error path is gone. Connection/Auth errors propagate from the lookup helper directly. While in there: - Removed unused `client = self._client` and `assert wait_bool is not None` from the dispatcher. - Documented the `bool | str` widening for `confirm`/`wait` in the Field(description=...) per the existing repo convention (15+ sites). - Extracted the inline `helper_type` Literal to a module-level `HelperTypeLiteral` constant with a one-line drift assertion against `SIMPLE_HELPER_TYPES | FLOW_HELPER_TYPES`. - Reworded "Mirrors X" / "1:1 from X" private-method banners to behavior-only summaries (the predecessors are removed in this PR, so the reference would have rotted immediately). Phase 3 in the same PR (per AGENTS.md "Tool Consolidation"): - Removed `ha_config_remove_helper` from tools_config_helpers.py and the now-unused `wait_for_entity_removed` import there. - Removed `ha_delete_config_entry` from tools_integrations.py. - Updated 3 cross-references in tools_groups.py docstrings to point at `ha_delete_helpers_integrations`. - Updated 2 docstring references in tests/src/e2e/workflows/integrations/test_integration_management.py and 1 comment in tests/src/e2e/workflows/config/test_helper_crud.py (call sites already migrated; only stale prose remained). - Removed 2 obsolete `ha_config_remove_helper` wait-tests from tests/src/unit/test_wait_parameter.py — coverage is now provided by the new tests in test_tools_integrations.py against the consolidated tool. - Regenerated README.md, homeassistant-addon/DOCS.md, and site/src/data/tools.json via scripts/extract_tools.py. Tool count: 88 → 86. Closes #1007. * fix(helpers): exception_to_structured_error schema-consistent with create_error_response The R8 refactor in cef316c routed three call sites through exception_to_structured_error(suggestions=...) for typed-dispatch error classification. The kwarg path set 'suggestions' (plural) only, while create_error_response (errors.py) sets 'suggestion' (singular) for the first item and 'suggestions' (plural) only when more than one is present. This caused the existing E2E test test_get_integration_nonexistent_entry_id (introduced in #1058 after this branch was opened) to fail because it asserts on 'suggestion' (singular). Mirror the create_error_response schema in exception_to_structured_error so the response shape is identical regardless of which entry point the caller uses. * fix(helpers): exception_to_structured_error sets both suggestion keys The previous fix (6d9662d) over-corrected: it dropped the plural 'suggestions' key when the caller-supplied list had a single element. That regressed test_caller_suggestions_still_override_on_non_darwin in test_macos_connection_hints.py, which asserts on the plural key for any caller suggestion list. The two helpers do not actually share a strict schema: create_error_response (errors.py) sets only 'suggestion' (singular) for single-element lists, while exception_to_structured_error has historically always set 'suggestions' (plural). Set both keys unconditionally when caller suggestions are present so consumers on either code path work. * fix(integrations): address PR #1056 R8 review - Narrow bare except at registry-lookup retry (:968) to HomeAssistantAPIError, matching the :938 state-check fix from R1. Auth/connection errors now propagate to the outer handler instead of falling through to ENTITY_NOT_FOUND. - Branch FlowLookupReason "lookup_failed" to WEBSOCKET_DISCONNECTED before the catch-all ENTITY_NOT_FOUND, so transient WebSocket failures during entity_registry/get surface as a retryable error rather than misleading the caller into chasing a non-existent entity_id. - Replace the # wrong_helper_type cannot occur here comment with assert reason != "wrong_helper_type" to enforce the dispatcher's filtering contract at runtime. - Tighten _delete_flow_helper/_delete_simple_helper inner signatures from helper_type: str to helper_type: HelperTypeLiteral; cosmetic but keeps the type contract visible at the inner method level. - Reword the test file header to behavior-only ("module-level helpers in tools_integrations and IntegrationTools.ha_delete_helpers_ integrations dispatch"); the Phase-2-of-#1007 framing dated the file to a transitional state that no longer applies. - Add two unit tests covering the R8 paths: test_flow_path_lookup_failed_maps_to_websocket_disconnected and test_simple_path_registry_lookup_connection_error_propagates. --------- Co-authored-by: kingpanther13 <25392815+kingpanther13@users.noreply.github.com>
1 parent a4d54c6 commit c45ba5a

21 files changed

Lines changed: 1626 additions & 549 deletions

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
<!-- mcp-name: io.github.homeassistant-ai/ha-mcp -->
99

1010
<p align="center">
11-
<img src="https://img.shields.io/badge/tools-87-blue" alt="95+ Tools">
11+
<img src="https://img.shields.io/badge/tools-86-blue" alt="95+ Tools">
1212
<a href="https://github.com/homeassistant-ai/ha-mcp/releases"><img src="https://img.shields.io/github/v/release/homeassistant-ai/ha-mcp" alt="Release"></a>
1313
<a href="https://github.com/homeassistant-ai/ha-mcp/actions/workflows/e2e-tests.yml"><img src="https://img.shields.io/github/actions/workflow/status/homeassistant-ai/ha-mcp/e2e-tests.yml?branch=master&label=E2E%20Tests" alt="E2E Tests"></a>
1414
<a href="LICENSE.md"><img src="https://img.shields.io/github/license/homeassistant-ai/ha-mcp.svg" alt="License"></a>
@@ -151,7 +151,7 @@ Spend less time configuring, more time enjoying your smart home.
151151
<details>
152152
<!-- TOOLS_TABLE_START -->
153153

154-
<summary><b>Complete Tool List (87 tools)</b></summary>
154+
<summary><b>Complete Tool List (86 tools)</b></summary>
155155

156156
| Category | Tools |
157157
|----------|-------|
@@ -168,9 +168,9 @@ Spend less time configuring, more time enjoying your smart home.
168168
| **Files** | `ha_delete_file` *(beta)*, `ha_list_files` *(beta)*, `ha_read_file` *(beta)*, `ha_write_file` *(beta)* |
169169
| **Groups** | `ha_config_list_groups`, `ha_config_remove_group`, `ha_config_set_group` |
170170
| **HACS** | `ha_hacs_add_repository`, `ha_hacs_download`, `ha_hacs_repository_info`, `ha_hacs_search` |
171-
| **Helper Entities** | `ha_config_list_helpers`, `ha_config_remove_helper`, `ha_config_set_helper`, `ha_get_helper_schema` |
171+
| **Helper Entities** | `ha_config_list_helpers`, `ha_config_set_helper`, `ha_delete_helpers_integrations`, `ha_get_helper_schema` |
172172
| **History & Statistics** | `ha_get_automation_traces`, `ha_get_history`, `ha_get_logs` |
173-
| **Integrations** | `ha_delete_config_entry`, `ha_get_integration`, `ha_set_integration_enabled` |
173+
| **Integrations** | `ha_get_integration`, `ha_set_integration_enabled` |
174174
| **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` |
175175
| **Scripts** | `ha_config_get_script`, `ha_config_remove_script`, `ha_config_set_script` |
176176
| **Search & Discovery** | `ha_deep_search`, `ha_get_overview`, `ha_get_state`, `ha_search_entities` |

homeassistant-addon/DOCS.md

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ AI assistant integration for Home Assistant via Model Context Protocol (MCP).
44

55
## About
66

7-
This add-on enables AI assistants (Claude, ChatGPT, etc.) to control your Home Assistant installation through the Model Context Protocol (MCP). It provides 87+ tools for device control, automation management, entity search, calendars, todo lists, dashboards, backup/restore, history/statistics, camera snapshots, and system queries.
7+
This add-on enables AI assistants (Claude, ChatGPT, etc.) to control your Home Assistant installation through the Model Context Protocol (MCP). It provides 86+ tools for device control, automation management, entity search, calendars, todo lists, dashboards, backup/restore, history/statistics, camera snapshots, and system queries.
88

99
**Key Features:**
1010
- **Zero Configuration** - Automatically discovers Home Assistant connection
@@ -231,7 +231,7 @@ Custom secret path override. **Leave empty for auto-generation** (recommended).
231231

232232
**Default:** `false`
233233

234-
Replaces the full tool catalog (~87 tools, ~46K tokens) with search-based discovery (~4 proxy tools, ~5K tokens). When enabled, tools are found via `ha_search_tools` and executed through categorized proxies (read/write/delete).
234+
Replaces the full tool catalog (~86 tools, ~46K tokens) with search-based discovery (~4 proxy tools, ~5K tokens). When enabled, tools are found via `ha_search_tools` and executed through categorized proxies (read/write/delete).
235235

236236
**When to enable:**
237237
- Models **without native deferred tool support** — this includes OpenAI-compatible local models, and also **Claude Haiku** which does not use Claude's built-in deferred tool loading. Haiku users will see significant token savings with this enabled.
@@ -329,7 +329,7 @@ If the add-on is slow or unresponsive:
329329

330330
<!-- ADDON_TOOLS_START -->
331331

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

334334
> 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.
335335

@@ -404,8 +404,8 @@ The add-on provides 87+ MCP tools for controlling Home Assistant:
404404

405405
### Helper Entities
406406
- `ha_config_list_helpers` — List all Home Assistant helpers of a specific type with their configurations.
407-
- `ha_config_remove_helper` — Delete a Home Assistant helper entity.
408407
- `ha_config_set_helper` — Create or update Home Assistant helper entities (27 types, unified interface).
408+
- `ha_delete_helpers_integrations` — Delete a Home Assistant helper or integration config entry.
409409
- `ha_get_helper_schema` — Get configuration schema for a helper type.
410410

411411
### History & Statistics
@@ -414,7 +414,6 @@ The add-on provides 87+ MCP tools for controlling Home Assistant:
414414
- `ha_get_logs` — Get Home Assistant logs from various sources.
415415

416416
### Integrations
417-
- `ha_delete_config_entry` — Delete config entry permanently. Requires confirm=True.
418417
- `ha_get_integration` — Get integration (config entry) information with pagination.
419418
- `ha_set_integration_enabled` — Enable/disable integration (config entry).
420419

site/src/data/tools.json

Lines changed: 37 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1174,7 +1174,7 @@
11741174
{
11751175
"name": "ha_config_remove_group",
11761176
"title": "Remove Group",
1177-
"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_config_entry`. Those config-entry-backed groups are not reachable via the\ngroup.remove service, and `ha_config_remove_helper` does not support helper_type=\"group\".\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.",
1177+
"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.",
11781178
"inputSchema": {
11791179
"properties": {
11801180
"object_id": {
@@ -1201,7 +1201,7 @@
12011201
{
12021202
"name": "ha_config_set_group",
12031203
"title": "Create or Update Group",
1204-
"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_config_entry`.\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` — neither\n`ha_config_remove_helper` nor `ha_delete_config_entry` will 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.",
1204+
"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.",
12051205
"inputSchema": {
12061206
"properties": {
12071207
"object_id": {
@@ -1383,37 +1383,6 @@
13831383
],
13841384
"source_file": "tools_config_helpers.py"
13851385
},
1386-
{
1387-
"name": "ha_config_remove_helper",
1388-
"title": "Remove Helper",
1389-
"description": "Delete a Home Assistant helper entity.\n\nSUPPORTED HELPER TYPES:\n- input_button, input_boolean, input_select, input_number, input_text, input_datetime\n- counter, timer, schedule, zone, person, tag\n\nFor flow-based helper types (template, group, utility_meter, derivative,\nmin_max, threshold, integration, statistics, trend, random, filter, tod,\ngeneric_thermostat, switch_as_x, generic_hygrostat) use ha_delete_config_entry.\n\nEXAMPLES:\n- Delete button: ha_config_remove_helper(\"input_button\", \"my_button\")\n- Delete counter: ha_config_remove_helper(\"counter\", \"my_counter\")\n- Delete timer: ha_config_remove_helper(\"timer\", \"my_timer\")\n- Delete schedule: ha_config_remove_helper(\"schedule\", \"work_hours\")\n\n**WARNING:** Deleting a helper that is used by automations or scripts may cause those automations/scripts to fail.\nUse ha_search_entities() to verify the helper exists before attempting to delete it.",
1390-
"inputSchema": {
1391-
"properties": {
1392-
"helper_type": {
1393-
"type": "Annotated[Literal['input_button', 'input_boolean', 'input_select', 'input_number', 'input_text', 'input_datetime', 'counter', 'timer', 'schedule', 'zone', 'person', 'tag'], Field(description='Type of helper entity to delete')]"
1394-
},
1395-
"helper_id": {
1396-
"type": "Annotated[str, Field(description=\"Helper ID to delete (e.g., 'my_button' or 'input_button.my_button')\")]"
1397-
},
1398-
"wait": {
1399-
"type": "Annotated[bool | str, Field(description='Wait for helper entity to be fully removed before returning. Default: True.', default=True)]",
1400-
"default": true
1401-
}
1402-
},
1403-
"required": [
1404-
"helper_type",
1405-
"helper_id"
1406-
]
1407-
},
1408-
"annotations": {
1409-
"destructiveHint": true,
1410-
"idempotentHint": true
1411-
},
1412-
"tags": [
1413-
"Helper Entities"
1414-
],
1415-
"source_file": "tools_config_helpers.py"
1416-
},
14171386
{
14181387
"name": "ha_config_set_helper",
14191388
"title": "Create or Update Helper",
@@ -1576,6 +1545,41 @@
15761545
],
15771546
"source_file": "tools_config_helpers.py"
15781547
},
1548+
{
1549+
"name": "ha_delete_helpers_integrations",
1550+
"title": "Delete Helper or Integration",
1551+
"description": "Delete a Home Assistant helper or integration config entry.\n\nCombines simple-helper websocket deletion and config-entry deletion\nunder one entry point with three 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\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\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.",
1552+
"inputSchema": {
1553+
"properties": {
1554+
"target": {
1555+
"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().\")]"
1556+
},
1557+
"helper_type": {
1558+
"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.', default=None)]",
1559+
"default": null
1560+
},
1561+
"confirm": {
1562+
"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)]",
1563+
"default": false
1564+
},
1565+
"wait": {
1566+
"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)]",
1567+
"default": true
1568+
}
1569+
},
1570+
"required": [
1571+
"target"
1572+
]
1573+
},
1574+
"annotations": {
1575+
"destructiveHint": true
1576+
},
1577+
"tags": [
1578+
"Helper Entities",
1579+
"Integrations"
1580+
],
1581+
"source_file": "tools_integrations.py"
1582+
},
15791583
{
15801584
"name": "ha_get_helper_schema",
15811585
"title": "Get Helper Schema",
@@ -1761,32 +1765,6 @@
17611765
],
17621766
"source_file": "tools_utility.py"
17631767
},
1764-
{
1765-
"name": "ha_delete_config_entry",
1766-
"title": "Delete Config Entry",
1767-
"description": "Delete config entry permanently. Requires confirm=True.\n\nUse ha_get_integration() to find entry IDs.",
1768-
"inputSchema": {
1769-
"properties": {
1770-
"entry_id": {
1771-
"type": "Annotated[str, Field(description='Config entry ID')]"
1772-
},
1773-
"confirm": {
1774-
"type": "Annotated[bool | str, Field(description='Must be True to confirm deletion')]",
1775-
"default": false
1776-
}
1777-
},
1778-
"required": [
1779-
"entry_id"
1780-
]
1781-
},
1782-
"annotations": {
1783-
"destructiveHint": true
1784-
},
1785-
"tags": [
1786-
"Integrations"
1787-
],
1788-
"source_file": "tools_integrations.py"
1789-
},
17901768
{
17911769
"name": "ha_get_integration",
17921770
"title": "Get Integration",

src/ha_mcp/tools/helpers.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,13 @@ def exception_to_structured_error(
308308
error_response = _classify_exception(error, error_str, error_msg, context)
309309

310310
if suggestions and "error" in error_response and isinstance(error_response["error"], dict):
311+
# Set both `suggestion` (singular, first item) and `suggestions`
312+
# (plural, full list). create_error_response (errors.py) sets the
313+
# singular key; existing tests for exception_to_structured_error
314+
# rely on the plural key being present even for single-item caller
315+
# suggestions. Setting both keeps response consumers on both code
316+
# paths working.
317+
error_response["error"]["suggestion"] = suggestions[0]
311318
error_response["error"]["suggestions"] = suggestions
312319

313320
# Append macOS-specific hints for connection failures (after all other processing

0 commit comments

Comments
 (0)