ha-mcp-dev 7.5.0.dev588__tar.gz → 7.5.0.dev589__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. {ha_mcp_dev-7.5.0.dev588/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.5.0.dev589}/PKG-INFO +5 -5
  2. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/README.md +4 -4
  3. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/pyproject.toml +1 -1
  4. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_groups.py +3 -3
  5. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_integrations.py +309 -109
  6. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589/src/ha_mcp_dev.egg-info}/PKG-INFO +5 -5
  7. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/LICENSE +0 -0
  8. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/MANIFEST.in +0 -0
  9. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/setup.cfg +0 -0
  10. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/__init__.py +0 -0
  11. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/__main__.py +0 -0
  12. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/_pypi_marker +0 -0
  13. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/_version.py +0 -0
  14. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/auth/__init__.py +0 -0
  15. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/auth/consent_form.py +0 -0
  16. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/auth/provider.py +0 -0
  17. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/backup_manager.py +0 -0
  18. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/client/__init__.py +0 -0
  19. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/client/rest_client.py +0 -0
  20. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/client/supervisor_client.py +0 -0
  21. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/client/websocket_client.py +0 -0
  22. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/client/websocket_listener.py +0 -0
  23. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/config.py +0 -0
  24. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/errors.py +0 -0
  25. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/py.typed +0 -0
  26. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
  27. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
  28. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
  29. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
  30. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
  31. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
  32. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
  33. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
  34. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
  35. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
  36. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
  37. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
  38. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
  39. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
  40. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
  41. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
  42. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
  43. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
  44. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
  45. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
  46. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
  47. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/yaml-only-integrations.md +0 -0
  48. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/server.py +0 -0
  49. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/settings_ui.py +0 -0
  50. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/smoke_test.py +0 -0
  51. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
  52. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/__init__.py +0 -0
  53. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/auto_backup.py +0 -0
  54. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/backup.py +0 -0
  55. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/best_practice_checker.py +0 -0
  56. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/device_control.py +0 -0
  57. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/enhanced.py +0 -0
  58. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/helpers.py +0 -0
  59. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/reference_validator.py +0 -0
  60. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/registry.py +0 -0
  61. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/smart_search.py +0 -0
  62. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_addons.py +0 -0
  63. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_areas.py +0 -0
  64. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_blueprints.py +0 -0
  65. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_bug_report.py +0 -0
  66. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_calendar.py +0 -0
  67. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_camera.py +0 -0
  68. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_categories.py +0 -0
  69. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_code.py +0 -0
  70. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_config_automations.py +0 -0
  71. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
  72. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_config_entry_flow.py +0 -0
  73. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
  74. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
  75. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
  76. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_energy.py +0 -0
  77. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_entities.py +0 -0
  78. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_filesystem.py +0 -0
  79. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_hacs.py +0 -0
  80. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_history.py +0 -0
  81. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_labels.py +0 -0
  82. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_mcp_component.py +0 -0
  83. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_registry.py +0 -0
  84. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_resources.py +0 -0
  85. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_search.py +0 -0
  86. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_service.py +0 -0
  87. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_services.py +0 -0
  88. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_system.py +0 -0
  89. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_todo.py +0 -0
  90. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_traces.py +0 -0
  91. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_updates.py +0 -0
  92. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_utility.py +0 -0
  93. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
  94. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
  95. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_zones.py +0 -0
  96. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/util_helpers.py +0 -0
  97. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/transforms/__init__.py +0 -0
  98. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/transforms/categorized_search.py +0 -0
  99. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
  100. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/utils/__init__.py +0 -0
  101. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/utils/config_hash.py +0 -0
  102. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/utils/data_paths.py +0 -0
  103. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/utils/domain_handlers.py +0 -0
  104. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/utils/fuzzy_search.py +0 -0
  105. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
  106. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/utils/operation_manager.py +0 -0
  107. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/utils/python_sandbox.py +0 -0
  108. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/utils/usage_logger.py +0 -0
  109. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
  110. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
  111. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
  112. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
  113. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
  114. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/tests/__init__.py +0 -0
  115. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/tests/test_constants.py +0 -0
  116. {ha_mcp_dev-7.5.0.dev588 → ha_mcp_dev-7.5.0.dev589}/tests/test_env_manager.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ha-mcp-dev
3
- Version: 7.5.0.dev588
3
+ Version: 7.5.0.dev589
4
4
  Summary: Home Assistant MCP Server - Complete control of Home Assistant through MCP
5
5
  Author-email: Julien <github@qc-h.net>
6
6
  License: MIT
@@ -38,7 +38,7 @@ Dynamic: license-file
38
38
  <!-- mcp-name: io.github.homeassistant-ai/ha-mcp -->
39
39
 
40
40
  <p align="center">
41
- <img src="https://img.shields.io/badge/tools-88-blue" alt="95+ Tools">
41
+ <img src="https://img.shields.io/badge/tools-86-blue" alt="95+ Tools">
42
42
  <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>
43
43
  <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>
44
44
  <a href="LICENSE.md"><img src="https://img.shields.io/github/license/homeassistant-ai/ha-mcp.svg" alt="License"></a>
@@ -181,12 +181,12 @@ Spend less time configuring, more time enjoying your smart home.
181
181
  <details>
182
182
  <!-- TOOLS_TABLE_START -->
183
183
 
184
- <summary><b>Complete Tool List (88 tools)</b></summary>
184
+ <summary><b>Complete Tool List (86 tools)</b></summary>
185
185
 
186
186
  | Category | Tools |
187
187
  |----------|-------|
188
188
  | **Add-ons** | `ha_get_addon`, `ha_manage_addon` |
189
- | **Areas & Floors** | `ha_config_list_areas`, `ha_config_list_floors`, `ha_list_floors_areas`, `ha_remove_area_or_floor`, `ha_set_area_or_floor` |
189
+ | **Areas & Floors** | `ha_list_floors_areas`, `ha_remove_area_or_floor`, `ha_set_area_or_floor` |
190
190
  | **Assist** | `ha_manage_pipeline` |
191
191
  | **Automations** | `ha_config_get_automation`, `ha_config_remove_automation`, `ha_config_set_automation` |
192
192
  | **Blueprints** | `ha_get_blueprint`, `ha_import_blueprint` |
@@ -199,7 +199,7 @@ Spend less time configuring, more time enjoying your smart home.
199
199
  | **Files** | `ha_delete_file` *(beta)*, `ha_list_files` *(beta)*, `ha_read_file` *(beta)*, `ha_write_file` *(beta)* |
200
200
  | **Groups** | `ha_config_list_groups`, `ha_config_remove_group`, `ha_config_set_group` |
201
201
  | **HACS** | `ha_hacs_add_repository`, `ha_hacs_download`, `ha_hacs_repository_info`, `ha_hacs_search` |
202
- | **Helper Entities** | `ha_config_list_helpers`, `ha_config_set_helper`, `ha_delete_helpers_integrations` |
202
+ | **Helper Entities** | `ha_config_list_helpers`, `ha_config_set_helper`, `ha_remove_helpers_integrations` |
203
203
  | **History & Statistics** | `ha_get_automation_traces`, `ha_get_history`, `ha_get_logs` |
204
204
  | **Integrations** | `ha_get_integration`, `ha_get_system_health`, `ha_set_integration_enabled` |
205
205
  | **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` |
@@ -8,7 +8,7 @@
8
8
  <!-- mcp-name: io.github.homeassistant-ai/ha-mcp -->
9
9
 
10
10
  <p align="center">
11
- <img src="https://img.shields.io/badge/tools-88-blue" alt="95+ Tools">
11
+ <img src="https://img.shields.io/badge/tools-86-blue" alt="95+ Tools">
12
12
  <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>
13
13
  <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>
14
14
  <a href="LICENSE.md"><img src="https://img.shields.io/github/license/homeassistant-ai/ha-mcp.svg" alt="License"></a>
@@ -151,12 +151,12 @@ Spend less time configuring, more time enjoying your smart home.
151
151
  <details>
152
152
  <!-- TOOLS_TABLE_START -->
153
153
 
154
- <summary><b>Complete Tool List (88 tools)</b></summary>
154
+ <summary><b>Complete Tool List (86 tools)</b></summary>
155
155
 
156
156
  | Category | Tools |
157
157
  |----------|-------|
158
158
  | **Add-ons** | `ha_get_addon`, `ha_manage_addon` |
159
- | **Areas & Floors** | `ha_config_list_areas`, `ha_config_list_floors`, `ha_list_floors_areas`, `ha_remove_area_or_floor`, `ha_set_area_or_floor` |
159
+ | **Areas & Floors** | `ha_list_floors_areas`, `ha_remove_area_or_floor`, `ha_set_area_or_floor` |
160
160
  | **Assist** | `ha_manage_pipeline` |
161
161
  | **Automations** | `ha_config_get_automation`, `ha_config_remove_automation`, `ha_config_set_automation` |
162
162
  | **Blueprints** | `ha_get_blueprint`, `ha_import_blueprint` |
@@ -169,7 +169,7 @@ Spend less time configuring, more time enjoying your smart home.
169
169
  | **Files** | `ha_delete_file` *(beta)*, `ha_list_files` *(beta)*, `ha_read_file` *(beta)*, `ha_write_file` *(beta)* |
170
170
  | **Groups** | `ha_config_list_groups`, `ha_config_remove_group`, `ha_config_set_group` |
171
171
  | **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` |
173
173
  | **History & Statistics** | `ha_get_automation_traces`, `ha_get_history`, `ha_get_logs` |
174
174
  | **Integrations** | `ha_get_integration`, `ha_get_system_health`, `ha_set_integration_enabled` |
175
175
  | **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` |
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ha-mcp-dev"
7
- version = "7.5.0.dev588"
7
+ version = "7.5.0.dev589"
8
8
  description = "Home Assistant MCP Server - Complete control of Home Assistant through MCP"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.13,<3.14"
@@ -277,12 +277,12 @@ class GroupTools:
277
277
  **When NOT to use:** for typical "combine these entities into one controllable group"
278
278
  requests, prefer `ha_config_set_helper(helper_type="group", ...)`. Config-entry-backed
279
279
  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`.
281
281
 
282
282
  **When to use:** compatibility with existing groups already configured via group.set
283
283
  or YAML, or the rare case where entity-registry membership is explicitly unwanted.
284
284
  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.
286
286
 
287
287
  **For NEW groups:** Provide object_id and entities (required).
288
288
  **For EXISTING groups:** Provide object_id and any fields to update.
@@ -414,7 +414,7 @@ class GroupTools:
414
414
  Remove a service-based Home Assistant entity group via the group.remove service.
415
415
 
416
416
  **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
418
418
  group.remove service.
419
419
 
420
420
  **When to use:** removing groups created with `ha_config_set_group` or defined in YAML
@@ -56,7 +56,7 @@ FlowLookupReason = Literal[
56
56
  ]
57
57
 
58
58
 
59
- # Tool parameter type for ha_delete_helpers_integrations.helper_type.
59
+ # Tool parameter type for ha_remove_helpers_integrations.helper_type.
60
60
  # Must match SIMPLE_HELPER_TYPES | FLOW_HELPER_TYPES plus config_subentry —
61
61
  # the drift assertion below catches accidental divergence at import time.
62
62
  HelperTypeLiteral = Literal[
@@ -109,7 +109,7 @@ async def _get_entry_id_for_flow_helper(
109
109
  ) -> tuple[str | None, FlowLookupReason]:
110
110
  """Resolve a flow-helper target to its config_entry_id via entity_registry.
111
111
 
112
- Used by ha_delete_helpers_integrations when target is an entity_id
112
+ Used by ha_remove_helpers_integrations when target is an entity_id
113
113
  (contains a '.') and helper_type is a known flow-helper type.
114
114
 
115
115
  Args:
@@ -141,7 +141,13 @@ async def _get_entry_id_for_flow_helper(
141
141
  except (HomeAssistantConnectionError, HomeAssistantAuthError):
142
142
  # Typed errors must reach the outer handler — do not swallow.
143
143
  raise
144
- except Exception as e:
144
+ except (OSError, TimeoutError) as e:
145
+ # Network / transport errors from the WS layer (ConnectionError,
146
+ # BrokenPipeError, TimeoutError, …). Programmer-bug-shape
147
+ # exceptions (KeyError, AttributeError, TypeError) intentionally
148
+ # propagate — the response is shape-checked at the dict guard
149
+ # below, and a raise here would otherwise mask the bug as a
150
+ # transient WEBSOCKET_DISCONNECTED.
145
151
  logger.debug(f"entity_registry/get failed for {entity_id}: {e}")
146
152
  if warnings is not None:
147
153
  warnings.append(f"entity_registry/get failed for {entity_id}: {e}")
@@ -1102,11 +1108,12 @@ class IntegrationTools:
1102
1108
  exception_to_structured_error(e, context={"entry_id": entry_id})
1103
1109
 
1104
1110
  @tool(
1105
- name="ha_delete_helpers_integrations",
1111
+ name="ha_remove_helpers_integrations",
1106
1112
  tags={"Helper Entities", "Integrations"},
1107
1113
  annotations={
1108
1114
  "destructiveHint": True,
1109
- "title": "Delete Helper or Integration",
1115
+ "idempotentHint": True,
1116
+ "title": "Remove Helper or Integration",
1110
1117
  },
1111
1118
  )
1112
1119
  @with_auto_backup(
@@ -1124,13 +1131,13 @@ class IntegrationTools:
1124
1131
  id_param="target",
1125
1132
  )
1126
1133
  @log_tool_usage
1127
- async def ha_delete_helpers_integrations(
1134
+ async def ha_remove_helpers_integrations(
1128
1135
  self,
1129
1136
  target: Annotated[
1130
1137
  str,
1131
1138
  Field(
1132
1139
  description=(
1133
- "What to delete. One of: "
1140
+ "What to remove. One of: "
1134
1141
  "(a) bare helper_id for SIMPLE helpers (requires helper_type), "
1135
1142
  "e.g. 'my_button'; "
1136
1143
  "(b) full entity_id (requires helper_type), "
@@ -1148,7 +1155,7 @@ class IntegrationTools:
1148
1155
  description=(
1149
1156
  "Helper type. Required when target is a helper_id (bare) "
1150
1157
  "or entity_id. Set to None when target is a config entry_id "
1151
- "to delete any integration. Use 'config_subentry' to delete "
1158
+ "to remove any integration. Use 'config_subentry' to remove "
1152
1159
  "a config subentry under target."
1153
1160
  ),
1154
1161
  default=None,
@@ -1158,7 +1165,7 @@ class IntegrationTools:
1158
1165
  str | None,
1159
1166
  Field(
1160
1167
  description=(
1161
- "Config subentry ID to delete when helper_type='config_subentry'."
1168
+ "Config subentry ID to remove when helper_type='config_subentry'."
1162
1169
  ),
1163
1170
  default=None,
1164
1171
  ),
@@ -1167,7 +1174,7 @@ class IntegrationTools:
1167
1174
  bool | str,
1168
1175
  Field(
1169
1176
  description=(
1170
- "Must be True to confirm deletion. Accepts bool or "
1177
+ "Must be True to confirm removal. Accepts bool or "
1171
1178
  "string ('true'/'false'/'1'/'0'/'yes'/'no'/'on'/'off', "
1172
1179
  "case-insensitive) for transport ergonomics."
1173
1180
  ),
@@ -1179,7 +1186,8 @@ class IntegrationTools:
1179
1186
  Field(
1180
1187
  description=(
1181
1188
  "Wait for entity removal. Default: True. "
1182
- "Ignored when helper_type=None (no entity poll, "
1189
+ "Ignored when helper_type=None or "
1190
+ "helper_type='config_subentry' (no entity poll, "
1183
1191
  "require_restart returned). Accepts bool or string "
1184
1192
  "('true'/'false'/'1'/'0'/'yes'/'no'/'on'/'off', "
1185
1193
  "case-insensitive)."
@@ -1188,11 +1196,11 @@ class IntegrationTools:
1188
1196
  ),
1189
1197
  ] = True,
1190
1198
  ) -> dict[str, Any]:
1191
- """Delete a Home Assistant helper or integration config entry.
1199
+ """Remove a Home Assistant helper or integration config entry.
1192
1200
 
1193
- Combines simple-helper websocket deletion, config-entry deletion, and
1194
- config-subentry deletion under one entry point with four routing paths
1195
- driven by helper_type.
1201
+ Unifies three backend removal mechanisms — simple-helper websocket
1202
+ delete, config-entry delete, and config-subentry delete behind one
1203
+ entry point with four routing paths driven by helper_type.
1196
1204
 
1197
1205
  WHEN NOT TO USE:
1198
1206
  - Removing only an entity (without deleting its underlying helper or
@@ -1219,33 +1227,58 @@ class IntegrationTools:
1219
1227
  - helper_type="config_subentry" + parent entry_id + subentry_id →
1220
1228
  delete one config subentry.
1221
1229
 
1230
+ MISSING-TARGET CONTRACT:
1231
+ A target that is *confirmed absent* raises a structured error
1232
+ rather than returning silent success, so a typo'd or stale
1233
+ identifier surfaces immediately at the caller layer (the
1234
+ ``success`` boolean is what agent wrappers branch on). The
1235
+ error code per-path follows the target shape:
1236
+ - SIMPLE (bare helper_id or entity_id): state-machine empty AND
1237
+ entity registry empty → raises ``ENTITY_NOT_FOUND``.
1238
+ - FLOW (entity_id): not in entity registry → raises
1239
+ ``ENTITY_NOT_FOUND``. YAML-configured helpers (no config entry
1240
+ backing) raise ``RESOURCE_NOT_FOUND``. A bare helper_id (no
1241
+ ``.``) on a FLOW target raises ``ENTITY_NOT_FOUND`` — FLOW
1242
+ resolution needs a full entity_id. TOCTOU 404 on the
1243
+ resolved entry_id raises ``RESOURCE_NOT_FOUND``.
1244
+ - Direct config entry (helper_type=None): backend returns HTTP
1245
+ 404 → raises ``RESOURCE_NOT_FOUND``.
1246
+ - Config subentry: backend returns a "not_found" error → raises
1247
+ ``RESOURCE_NOT_FOUND``.
1248
+
1249
+ Idempotency at the contract level still holds (call N times =
1250
+ same response). Transient connectivity failures (WebSocket
1251
+ disconnected, network timeouts) raise their own codes
1252
+ (``WEBSOCKET_DISCONNECTED``, ``CONNECTION_FAILED``) so retry
1253
+ logic can branch separately.
1254
+
1222
1255
  EXAMPLES:
1223
- - Delete SIMPLE button:
1224
- ha_delete_helpers_integrations(
1256
+ - Remove SIMPLE button:
1257
+ ha_remove_helpers_integrations(
1225
1258
  target="my_button", helper_type="input_button", confirm=True
1226
1259
  )
1227
- - Delete FLOW utility_meter (any sub-entity works):
1228
- ha_delete_helpers_integrations(
1260
+ - Remove FLOW utility_meter (any sub-entity works):
1261
+ ha_remove_helpers_integrations(
1229
1262
  target="sensor.energy_peak",
1230
1263
  helper_type="utility_meter",
1231
1264
  confirm=True,
1232
1265
  )
1233
- - Delete any integration by entry_id:
1234
- ha_delete_helpers_integrations(
1266
+ - Remove any integration by entry_id:
1267
+ ha_remove_helpers_integrations(
1235
1268
  target="01HXYZ...", confirm=True
1236
1269
  )
1237
- - Delete a config subentry:
1238
- ha_delete_helpers_integrations(
1270
+ - Remove a config subentry:
1271
+ ha_remove_helpers_integrations(
1239
1272
  target="01HXYZ...", helper_type="config_subentry",
1240
1273
  subentry_id="subentry-123", confirm=True
1241
1274
  )
1242
1275
 
1243
- **WARNING:** Deleting a helper or integration that is referenced by
1276
+ **WARNING:** Removing a helper or integration that is referenced by
1244
1277
  automations, scripts, or other integrations may cause those to fail.
1245
1278
  Use ha_search_entities() / ha_get_integration() to verify before
1246
- deletion. Cannot be undone.
1279
+ removal. Cannot be undone.
1247
1280
  """
1248
- # === Confirm gate (uniform for all three paths) ===
1281
+ # === Confirm gate (uniform for all four paths) ===
1249
1282
  confirm_bool = coerce_bool_param(confirm, "confirm", default=False)
1250
1283
  if not confirm_bool:
1251
1284
  raise_tool_error(
@@ -1263,14 +1296,15 @@ class IntegrationTools:
1263
1296
  )
1264
1297
  )
1265
1298
 
1266
- # === Empty/whitespace target gate (uniform for all three paths) ===
1299
+ # === Empty/whitespace target gate (uniform for all four paths) ===
1267
1300
  # Empty/whitespace ``target`` would reach the destructive backend call
1268
1301
  # on every path: Path 1 (simple-helper websocket delete), Path 2
1269
1302
  # (flow-helper entity-resolution → entry_id delete), Path 3
1270
- # (_delete_direct_entry → client.delete_config_entry("")). Each path
1271
- # surfaces a different misleading error from HA. Reject up-front so
1272
- # the caller learns the identifier was unusable before any backend
1273
- # call.
1303
+ # (_delete_direct_entry → client.delete_config_entry("")), Path 4
1304
+ # (_delete_config_subentry ws delete on empty parent entry_id).
1305
+ # Each path surfaces a different misleading error from HA. Reject
1306
+ # up-front so the caller learns the identifier was unusable before
1307
+ # any backend call.
1274
1308
  validate_identifier_not_empty(
1275
1309
  target,
1276
1310
  "target",
@@ -1323,9 +1357,15 @@ class IntegrationTools:
1323
1357
  )
1324
1358
  )
1325
1359
 
1360
+ # Private helpers keep the ``_delete_*`` prefix because they wrap HA's
1361
+ # own backend verb — the WebSocket API is ``<type>/delete`` and the
1362
+ # REST API is HTTP DELETE. The public tool surface uses ``remove`` to
1363
+ # join the ``ha_remove_*`` behavioural family; the prefix asymmetry is
1364
+ # intentional and prevents future renames pulled by either side.
1365
+
1326
1366
  # === Path 3: Direct config entry delete (any integration) ===
1327
1367
  async def _delete_direct_entry(self, entry_id: str) -> dict[str, Any]:
1328
- """Delete a config entry directly via the websocket delete API."""
1368
+ """Delete a config entry directly via the REST delete API."""
1329
1369
  try:
1330
1370
  result = await self._client.delete_config_entry(entry_id)
1331
1371
  require_restart = result.get("require_restart", False)
@@ -1346,8 +1386,43 @@ class IntegrationTools:
1346
1386
  }
1347
1387
  except ToolError:
1348
1388
  raise
1389
+ except HomeAssistantAPIError as e:
1390
+ # HA returns 404 for missing config entries (see
1391
+ # RestClient.delete_config_entry — the REST DELETE on a
1392
+ # nonexistent entry surfaces as HomeAssistantAPIError with
1393
+ # status_code=404). Surface as RESOURCE_NOT_FOUND so callers
1394
+ # can distinguish absent target from real failures; the typo
1395
+ # case (agent passed the wrong entry_id) is the failure mode
1396
+ # that "absent → success" would silently mask. Non-404 API
1397
+ # errors are real failures and bubble through
1398
+ # exception_to_structured_error below.
1399
+ if e.status_code == 404:
1400
+ raise_tool_error(
1401
+ create_error_response(
1402
+ ErrorCode.RESOURCE_NOT_FOUND,
1403
+ (
1404
+ f"Config entry {entry_id} not found. May "
1405
+ "indicate it was already removed, never "
1406
+ "existed, or the identifier is a typo. "
1407
+ "Verify with ha_get_integration() before "
1408
+ "retrying."
1409
+ ),
1410
+ context={"entry_id": entry_id},
1411
+ suggestions=[
1412
+ "Use ha_get_integration() without entry_id "
1413
+ "to see all config entries",
1414
+ ],
1415
+ )
1416
+ )
1417
+ exception_to_structured_error(
1418
+ e,
1419
+ context={"entry_id": entry_id},
1420
+ suggestions=[
1421
+ "Use ha_get_integration() without entry_id to "
1422
+ "see all config entries",
1423
+ ],
1424
+ )
1349
1425
  except Exception as e:
1350
- logger.error(f"Failed to delete config entry: {e}")
1351
1426
  exception_to_structured_error(
1352
1427
  e,
1353
1428
  context={"entry_id": entry_id},
@@ -1421,13 +1496,46 @@ class IntegrationTools:
1421
1496
  },
1422
1497
  )
1423
1498
  )
1424
- # Remaining reasons (not_in_registry, bare_id_not_supported,
1425
- # wrong_helper_type) entity not found from the caller's
1426
- # perspective. wrong_helper_type cannot occur here because
1427
- # the dispatcher already checked SIMPLE_HELPER_TYPES /
1428
- # FLOW_HELPER_TYPES; the assertion below enforces that
1429
- # contract at runtime.
1499
+ # wrong_helper_type cannot occur here because the dispatcher
1500
+ # already checked SIMPLE_HELPER_TYPES / FLOW_HELPER_TYPES; the
1501
+ # assertion enforces that contract at runtime.
1430
1502
  assert reason != "wrong_helper_type"
1503
+ if reason == "not_in_registry":
1504
+ # Target is absent from the entity registry. Surface
1505
+ # as ENTITY_NOT_FOUND (entity-shaped target) so the
1506
+ # caller learns the identifier is unusable — the typo
1507
+ # case is the failure mode "absent → success" would
1508
+ # silently mask. Matches the bare_id_not_supported
1509
+ # branch below and sibling ha_remove_entity.
1510
+ raise_tool_error(
1511
+ create_error_response(
1512
+ ErrorCode.ENTITY_NOT_FOUND,
1513
+ (
1514
+ f"Helper {target} not found in entity "
1515
+ f"registry (looked up as {entity_id}). "
1516
+ "May indicate it was already removed, "
1517
+ "never existed, or the identifier is a "
1518
+ "typo. Verify with ha_search_entities() "
1519
+ "before retrying."
1520
+ ),
1521
+ context={
1522
+ "target": target,
1523
+ "helper_type": helper_type,
1524
+ "entity_id": entity_id,
1525
+ },
1526
+ suggestions=[
1527
+ "Use ha_search_entities() — flow helper "
1528
+ "types often expose entities under a "
1529
+ "different domain than the helper_type "
1530
+ "itself (e.g. utility_meter → sensor.*, "
1531
+ "switch_as_x → switch.* / light.*).",
1532
+ ],
1533
+ )
1534
+ )
1535
+ # bare_id_not_supported → caller passed a bare ID where an
1536
+ # entity_id was required. That's a call-shape error, not
1537
+ # missing-target; surface as ENTITY_NOT_FOUND with the
1538
+ # search suggestion so the caller can self-correct.
1431
1539
  raise_tool_error(
1432
1540
  create_error_response(
1433
1541
  ErrorCode.ENTITY_NOT_FOUND,
@@ -1459,6 +1567,38 @@ class IntegrationTools:
1459
1567
  # Step 3: delete the config entry
1460
1568
  try:
1461
1569
  delete_result = await client.delete_config_entry(entry_id)
1570
+ except HomeAssistantAPIError as e:
1571
+ # TOCTOU window: entry_id resolved at step 1 was deleted
1572
+ # before step 3 reached HA. Surface as RESOURCE_NOT_FOUND
1573
+ # so the caller knows the config entry is gone — silent
1574
+ # success would hide the race from any wrapper that
1575
+ # acted on the intermediate state. Non-404 still surfaces.
1576
+ if e.status_code == 404:
1577
+ raise_tool_error(
1578
+ create_error_response(
1579
+ ErrorCode.RESOURCE_NOT_FOUND,
1580
+ (
1581
+ f"Config entry {entry_id} for {target} "
1582
+ "not found at delete time (resolved by "
1583
+ "registry but absent when DELETE reached "
1584
+ "Home Assistant). May indicate a "
1585
+ "concurrent removal."
1586
+ ),
1587
+ context={
1588
+ "entry_id": entry_id,
1589
+ "target": target,
1590
+ "helper_type": helper_type,
1591
+ },
1592
+ )
1593
+ )
1594
+ exception_to_structured_error(
1595
+ e,
1596
+ context={
1597
+ "entry_id": entry_id,
1598
+ "target": target,
1599
+ "helper_type": helper_type,
1600
+ },
1601
+ )
1462
1602
  except Exception as e:
1463
1603
  exception_to_structured_error(
1464
1604
  e,
@@ -1540,7 +1680,42 @@ class IntegrationTools:
1540
1680
  """Delete one config subentry under a parent config entry."""
1541
1681
  result = await self._client.delete_config_subentry(entry_id, subentry_id)
1542
1682
  if not isinstance(result, dict) or not result.get("success"):
1543
- error_msg = websocket_error_message(result.get("error", "Operation failed"))
1683
+ error = result.get("error", "Operation failed")
1684
+ error_msg = websocket_error_message(error)
1685
+ # Detect "subentry already absent" by HA's structured
1686
+ # ``code="not_found"`` only. A generic ``"not found" in
1687
+ # error_msg`` substring match was rejected because it can
1688
+ # collide with unrelated HA error messages (e.g.
1689
+ # "repository not found", "integration not found") and
1690
+ # mis-classify a real failure as a missing target.
1691
+ # The HA ``config_entries/subentries/delete`` handler raises
1692
+ # with ``code="not_found"`` when entry_id or subentry_id is
1693
+ # missing; if a future HA version uses a different code, we
1694
+ # raise SERVICE_CALL_FAILED instead — safer than mis-classifying.
1695
+ error_code = error.get("code") if isinstance(error, dict) else None
1696
+ if error_code == "not_found":
1697
+ # Subentry absent under the parent config entry. Surface
1698
+ # as RESOURCE_NOT_FOUND so the caller learns the target
1699
+ # didn't exist — silent success would mask a typo'd
1700
+ # subentry_id (or wrong parent entry_id) until the user
1701
+ # noticed nothing was removed.
1702
+ raise_tool_error(
1703
+ create_error_response(
1704
+ ErrorCode.RESOURCE_NOT_FOUND,
1705
+ (
1706
+ f"Subentry {subentry_id} not found under "
1707
+ f"config entry {entry_id}. May indicate it "
1708
+ "was already removed, never existed, or one "
1709
+ "of the identifiers is a typo. Verify with "
1710
+ "ha_get_integration(entry_id=..., "
1711
+ "include_subentries=True) before retrying."
1712
+ ),
1713
+ context={
1714
+ "entry_id": entry_id,
1715
+ "subentry_id": subentry_id,
1716
+ },
1717
+ )
1718
+ )
1544
1719
  raise_tool_error(
1545
1720
  create_error_response(
1546
1721
  ErrorCode.SERVICE_CALL_FAILED,
@@ -1556,7 +1731,6 @@ class IntegrationTools:
1556
1731
  "subentry_id": subentry_id,
1557
1732
  "method": "config_subentry_delete",
1558
1733
  "message": f"Successfully deleted config subentry: {subentry_id}",
1559
- "result": result.get("result"),
1560
1734
  }
1561
1735
 
1562
1736
  # === Path 1: SIMPLE helper delete via websocket ===
@@ -1569,8 +1743,8 @@ class IntegrationTools:
1569
1743
  """Delete a SIMPLE helper via the websocket {type}/delete API.
1570
1744
 
1571
1745
  Uses a 3-retry registry lookup with exponential backoff to find the
1572
- helper's unique_id, then falls back to direct-id-delete and an
1573
- already-deleted check if the registry has no record.
1746
+ helper's unique_id, then falls back to direct-id-delete and a
1747
+ confirmed-absent classification if the registry has no record.
1574
1748
  """
1575
1749
  client = self._client
1576
1750
  # Convert to entity_id form
@@ -1682,87 +1856,113 @@ class IntegrationTools:
1682
1856
  )
1683
1857
  return response
1684
1858
 
1685
- # Fallback strategy 2: already-deleted check. Confirm via the
1686
- # registry too — a disabled entity is missing from the state
1687
- # machine but still registry-resident, so state-absence alone
1688
- # is not enough to declare success.
1859
+ # Fallback strategy 2: confirmed-absent classification.
1860
+ # Confirm via the registry too — a disabled entity is
1861
+ # state-absent but still registry-resident, so
1862
+ # state-absence alone is not enough to classify as
1863
+ # confirmed-absent. The APIError-404 branch routes the
1864
+ # never-existed-target case (HA returns 404 on
1865
+ # get_entity_state for unknown entity_ids) into the same
1866
+ # confirmed-absent path so the resulting ENTITY_NOT_FOUND
1867
+ # raise carries the structured "typo or removed" hint
1868
+ # message rather than a raw 404.
1869
+ state_gone = False
1689
1870
  try:
1690
1871
  final_state_check = await client.get_entity_state(entity_id)
1691
- if not final_state_check:
1692
- registry_still_has_entry = False
1693
- try:
1694
- verify_result = await client.send_websocket_message(
1695
- {
1696
- "type": "config/entity_registry/get",
1697
- "entity_id": entity_id,
1698
- }
1699
- )
1700
- if (verify_result or {}).get("success"):
1701
- verify_entry = (verify_result or {}).get("result") or {}
1702
- if verify_entry.get("entity_id"):
1703
- registry_still_has_entry = True
1704
- except HomeAssistantAPIError as verify_err:
1705
- # On verify failure, conservatively assume the
1706
- # entry is still there rather than silently
1707
- # short-circuit to already_deleted.
1708
- logger.debug(
1709
- f"Registry verify for {entity_id} failed: {verify_err}"
1710
- )
1711
- registry_still_has_entry = True
1712
-
1713
- if not registry_still_has_entry:
1714
- logger.info(
1715
- f"Entity {entity_id} absent from state and "
1716
- "registry; treating as already deleted"
1717
- )
1718
- return {
1719
- "success": True,
1720
- "action": "delete",
1721
- "target": target,
1722
- "helper_type": helper_type,
1723
- "method": "websocket_delete",
1724
- "entry_id": None,
1725
- "entity_ids": [entity_id],
1726
- "require_restart": False,
1727
- "message": (
1728
- f"Helper {target} was already deleted or "
1729
- "never properly registered."
1730
- ),
1731
- "fallback_used": "already_deleted",
1872
+ state_gone = not final_state_check
1873
+ except HomeAssistantAPIError as e:
1874
+ # Only 404 confirms the entity is absent from the state
1875
+ # machine. Other API failures (500, 401, …) are transient
1876
+ # or auth issues and must propagate so they aren't
1877
+ # mis-classified as a missing target. Mirrors the
1878
+ # status_code == 404 narrow in _delete_direct_entry.
1879
+ if e.status_code != 404:
1880
+ raise
1881
+ logger.debug(
1882
+ f"State check for {entity_id} raised 404 "
1883
+ f"(treating as state-absent): {e}"
1884
+ )
1885
+ state_gone = True
1886
+
1887
+ if state_gone:
1888
+ registry_still_has_entry = False
1889
+ try:
1890
+ verify_result = await client.send_websocket_message(
1891
+ {
1892
+ "type": "config/entity_registry/get",
1893
+ "entity_id": entity_id,
1732
1894
  }
1895
+ )
1896
+ if (verify_result or {}).get("success"):
1897
+ verify_entry = (verify_result or {}).get("result") or {}
1898
+ if verify_entry.get("entity_id"):
1899
+ registry_still_has_entry = True
1900
+ except HomeAssistantAPIError as verify_err:
1901
+ # On verify failure, conservatively assume the
1902
+ # entry is still there rather than misclassify
1903
+ # a verify failure as confirmed-absent.
1904
+ logger.debug(
1905
+ f"Registry verify for {entity_id} failed: {verify_err}"
1906
+ )
1907
+ registry_still_has_entry = True
1733
1908
 
1734
- logger.warning(
1735
- f"Entity {entity_id} absent from state but still "
1736
- "in registry; not already_deleted"
1909
+ if not registry_still_has_entry:
1910
+ logger.info(
1911
+ f"Entity {entity_id} absent from state and "
1912
+ "registry; surfacing as ENTITY_NOT_FOUND"
1737
1913
  )
1914
+ # Entity-shape target confirmed absent from both
1915
+ # the state machine and the entity registry.
1916
+ # Surface as ENTITY_NOT_FOUND — silent success
1917
+ # would mask the typo case (agent passed the
1918
+ # wrong helper_id / entity_id). Matches sibling
1919
+ # ha_remove_entity.
1738
1920
  raise_tool_error(
1739
1921
  create_error_response(
1740
- ErrorCode.SERVICE_CALL_FAILED,
1922
+ ErrorCode.ENTITY_NOT_FOUND,
1741
1923
  (
1742
- f"Helper {target} could not be deleted: "
1743
- "registry entry exists but unique_id was "
1744
- "absent and the direct-id fallback "
1745
- "delete failed."
1924
+ f"Helper {target} not found (looked "
1925
+ f"up as {entity_id}). May indicate "
1926
+ "it was already removed, never "
1927
+ "existed, or the identifier is a "
1928
+ "typo. Verify with "
1929
+ "ha_search_entities() before "
1930
+ "retrying."
1746
1931
  ),
1747
- suggestions=[
1748
- "Re-enable the entity via "
1749
- "ha_set_entity(enabled=True), then retry "
1750
- "deletion.",
1751
- "Or inspect the entity registry entry "
1752
- "directly to confirm unique_id presence.",
1753
- ],
1754
1932
  context={
1755
1933
  "target": target,
1934
+ "helper_type": helper_type,
1756
1935
  "entity_id": entity_id,
1757
1936
  },
1758
1937
  )
1759
1938
  )
1760
- except HomeAssistantAPIError as e:
1761
- # 404 here means the state-check itself confirmed the
1762
- # entity is gone treat as a soft signal and continue
1763
- # to the "all fallbacks exhausted" path. Auth/connection
1764
- # errors must propagate (handled by outer except).
1765
- logger.debug(f"State check for {entity_id} raised APIError: {e}")
1939
+
1940
+ logger.warning(
1941
+ f"Entity {entity_id} absent from state but still "
1942
+ "in registry; treating as SERVICE_CALL_FAILED"
1943
+ )
1944
+ raise_tool_error(
1945
+ create_error_response(
1946
+ ErrorCode.SERVICE_CALL_FAILED,
1947
+ (
1948
+ f"Helper {target} could not be deleted: "
1949
+ "registry entry exists but unique_id was "
1950
+ "absent and the direct-id fallback "
1951
+ "delete failed."
1952
+ ),
1953
+ suggestions=[
1954
+ "Re-enable the entity via "
1955
+ "ha_set_entity(enabled=True), then retry "
1956
+ "deletion.",
1957
+ "Or inspect the entity registry entry "
1958
+ "directly to confirm unique_id presence.",
1959
+ ],
1960
+ context={
1961
+ "target": target,
1962
+ "entity_id": entity_id,
1963
+ },
1964
+ )
1965
+ )
1766
1966
 
1767
1967
  # All fallbacks exhausted
1768
1968
  err_detail = (
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ha-mcp-dev
3
- Version: 7.5.0.dev588
3
+ Version: 7.5.0.dev589
4
4
  Summary: Home Assistant MCP Server - Complete control of Home Assistant through MCP
5
5
  Author-email: Julien <github@qc-h.net>
6
6
  License: MIT
@@ -38,7 +38,7 @@ Dynamic: license-file
38
38
  <!-- mcp-name: io.github.homeassistant-ai/ha-mcp -->
39
39
 
40
40
  <p align="center">
41
- <img src="https://img.shields.io/badge/tools-88-blue" alt="95+ Tools">
41
+ <img src="https://img.shields.io/badge/tools-86-blue" alt="95+ Tools">
42
42
  <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>
43
43
  <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>
44
44
  <a href="LICENSE.md"><img src="https://img.shields.io/github/license/homeassistant-ai/ha-mcp.svg" alt="License"></a>
@@ -181,12 +181,12 @@ Spend less time configuring, more time enjoying your smart home.
181
181
  <details>
182
182
  <!-- TOOLS_TABLE_START -->
183
183
 
184
- <summary><b>Complete Tool List (88 tools)</b></summary>
184
+ <summary><b>Complete Tool List (86 tools)</b></summary>
185
185
 
186
186
  | Category | Tools |
187
187
  |----------|-------|
188
188
  | **Add-ons** | `ha_get_addon`, `ha_manage_addon` |
189
- | **Areas & Floors** | `ha_config_list_areas`, `ha_config_list_floors`, `ha_list_floors_areas`, `ha_remove_area_or_floor`, `ha_set_area_or_floor` |
189
+ | **Areas & Floors** | `ha_list_floors_areas`, `ha_remove_area_or_floor`, `ha_set_area_or_floor` |
190
190
  | **Assist** | `ha_manage_pipeline` |
191
191
  | **Automations** | `ha_config_get_automation`, `ha_config_remove_automation`, `ha_config_set_automation` |
192
192
  | **Blueprints** | `ha_get_blueprint`, `ha_import_blueprint` |
@@ -199,7 +199,7 @@ Spend less time configuring, more time enjoying your smart home.
199
199
  | **Files** | `ha_delete_file` *(beta)*, `ha_list_files` *(beta)*, `ha_read_file` *(beta)*, `ha_write_file` *(beta)* |
200
200
  | **Groups** | `ha_config_list_groups`, `ha_config_remove_group`, `ha_config_set_group` |
201
201
  | **HACS** | `ha_hacs_add_repository`, `ha_hacs_download`, `ha_hacs_repository_info`, `ha_hacs_search` |
202
- | **Helper Entities** | `ha_config_list_helpers`, `ha_config_set_helper`, `ha_delete_helpers_integrations` |
202
+ | **Helper Entities** | `ha_config_list_helpers`, `ha_config_set_helper`, `ha_remove_helpers_integrations` |
203
203
  | **History & Statistics** | `ha_get_automation_traces`, `ha_get_history`, `ha_get_logs` |
204
204
  | **Integrations** | `ha_get_integration`, `ha_get_system_health`, `ha_set_integration_enabled` |
205
205
  | **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` |