ha-mcp-dev 7.5.0.dev587__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.dev587/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.5.0.dev589}/PKG-INFO +5 -5
  2. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/README.md +4 -4
  3. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/pyproject.toml +1 -1
  4. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/smart_search.py +17 -20
  5. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_areas.py +80 -144
  6. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_config_helpers.py +1 -1
  7. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_groups.py +3 -3
  8. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_integrations.py +309 -109
  9. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589/src/ha_mcp_dev.egg-info}/PKG-INFO +5 -5
  10. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/LICENSE +0 -0
  11. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/MANIFEST.in +0 -0
  12. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/setup.cfg +0 -0
  13. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/__init__.py +0 -0
  14. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/__main__.py +0 -0
  15. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/_pypi_marker +0 -0
  16. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/_version.py +0 -0
  17. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/auth/__init__.py +0 -0
  18. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/auth/consent_form.py +0 -0
  19. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/auth/provider.py +0 -0
  20. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/backup_manager.py +0 -0
  21. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/client/__init__.py +0 -0
  22. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/client/rest_client.py +0 -0
  23. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/client/supervisor_client.py +0 -0
  24. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/client/websocket_client.py +0 -0
  25. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/client/websocket_listener.py +0 -0
  26. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/config.py +0 -0
  27. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/errors.py +0 -0
  28. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/py.typed +0 -0
  29. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
  30. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
  31. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
  32. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
  33. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
  34. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
  35. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
  36. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
  37. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
  38. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
  39. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
  40. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
  41. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
  42. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
  43. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
  44. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
  45. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
  46. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
  47. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
  48. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
  49. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
  50. {ha_mcp_dev-7.5.0.dev587 → 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
  51. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/server.py +0 -0
  52. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/settings_ui.py +0 -0
  53. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/smoke_test.py +0 -0
  54. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
  55. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/__init__.py +0 -0
  56. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/auto_backup.py +0 -0
  57. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/backup.py +0 -0
  58. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/best_practice_checker.py +0 -0
  59. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/device_control.py +0 -0
  60. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/enhanced.py +0 -0
  61. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/helpers.py +0 -0
  62. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/reference_validator.py +0 -0
  63. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/registry.py +0 -0
  64. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_addons.py +0 -0
  65. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_blueprints.py +0 -0
  66. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_bug_report.py +0 -0
  67. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_calendar.py +0 -0
  68. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_camera.py +0 -0
  69. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_categories.py +0 -0
  70. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_code.py +0 -0
  71. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_config_automations.py +0 -0
  72. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
  73. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_config_entry_flow.py +0 -0
  74. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
  75. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
  76. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_energy.py +0 -0
  77. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_entities.py +0 -0
  78. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_filesystem.py +0 -0
  79. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_hacs.py +0 -0
  80. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_history.py +0 -0
  81. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_labels.py +0 -0
  82. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_mcp_component.py +0 -0
  83. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_registry.py +0 -0
  84. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_resources.py +0 -0
  85. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_search.py +0 -0
  86. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_service.py +0 -0
  87. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_services.py +0 -0
  88. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_system.py +0 -0
  89. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_todo.py +0 -0
  90. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_traces.py +0 -0
  91. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_updates.py +0 -0
  92. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_utility.py +0 -0
  93. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
  94. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
  95. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/tools_zones.py +0 -0
  96. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/tools/util_helpers.py +0 -0
  97. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/transforms/__init__.py +0 -0
  98. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/transforms/categorized_search.py +0 -0
  99. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
  100. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/utils/__init__.py +0 -0
  101. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/utils/config_hash.py +0 -0
  102. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/utils/data_paths.py +0 -0
  103. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/utils/domain_handlers.py +0 -0
  104. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/utils/fuzzy_search.py +0 -0
  105. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
  106. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/utils/operation_manager.py +0 -0
  107. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/utils/python_sandbox.py +0 -0
  108. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp/utils/usage_logger.py +0 -0
  109. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
  110. {ha_mcp_dev-7.5.0.dev587 → 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.dev587 → 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.dev587 → ha_mcp_dev-7.5.0.dev589}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
  113. {ha_mcp_dev-7.5.0.dev587 → 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.dev587 → ha_mcp_dev-7.5.0.dev589}/tests/__init__.py +0 -0
  115. {ha_mcp_dev-7.5.0.dev587 → ha_mcp_dev-7.5.0.dev589}/tests/test_constants.py +0 -0
  116. {ha_mcp_dev-7.5.0.dev587 → 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.dev587
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.dev587"
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"
@@ -197,14 +197,13 @@ class SmartSearchTools:
197
197
  aliases_map: dict[str, list[str]] = {}
198
198
  if survivor_ids:
199
199
  try:
200
- entries_resp = await self.client.send_websocket_message({
201
- "type": "config/entity_registry/get_entries",
202
- "entity_ids": survivor_ids,
203
- })
204
- if (
205
- isinstance(entries_resp, dict)
206
- and entries_resp.get("success")
207
- ):
200
+ entries_resp = await self.client.send_websocket_message(
201
+ {
202
+ "type": "config/entity_registry/get_entries",
203
+ "entity_ids": survivor_ids,
204
+ }
205
+ )
206
+ if isinstance(entries_resp, dict) and entries_resp.get("success"):
208
207
  for eid, entry in (
209
208
  entries_resp.get("result", {}) or {}
210
209
  ).items():
@@ -232,11 +231,13 @@ class SmartSearchTools:
232
231
  # Shallow copy + private-prefixed keys so downstream
233
232
  # consumers that round-trip these dicts don't ship
234
233
  # internal fields back to clients.
235
- enriched.append({
236
- **entity,
237
- "_aliases": aliases_map.get(eid, []),
238
- "_hidden_by": slim.get("hidden_by"),
239
- })
234
+ enriched.append(
235
+ {
236
+ **entity,
237
+ "_aliases": aliases_map.get(eid, []),
238
+ "_hidden_by": slim.get("hidden_by"),
239
+ }
240
+ )
240
241
 
241
242
  entities = enriched
242
243
  if domain_filter:
@@ -389,7 +390,7 @@ class SmartSearchTools:
389
390
  # Two-pass area resolution. Pass 1 collects exact id / name /
390
391
  # alias matches; if any are found, fuzzy aggregation is
391
392
  # skipped entirely. This makes ``area_filter`` honor a
392
- # literal area_id from ``ha_config_list_areas`` — pre-fix a
393
+ # literal area_id from ``ha_list_floors_areas`` — pre-fix a
393
394
  # query like ``"bedroom_kids"`` would also fuzzy-match its
394
395
  # parent ``"bedroom"`` (partial_ratio=100) and aggregate
395
396
  # sibling areas' entities. Aliases (per-area registry, used
@@ -519,9 +520,7 @@ class SmartSearchTools:
519
520
  ),
520
521
  "state": state_info.get("state", "unknown"),
521
522
  "_hidden_by": (
522
- "hidden"
523
- if entity_id in hidden_entity_ids
524
- else None
523
+ "hidden" if entity_id in hidden_entity_ids else None
525
524
  ),
526
525
  }
527
526
  )
@@ -538,9 +537,7 @@ class SmartSearchTools:
538
537
  "domain": entity_id.split(".")[0],
539
538
  "state": state_info.get("state", "unknown"),
540
539
  "_hidden_by": (
541
- "hidden"
542
- if entity_id in hidden_entity_ids
543
- else None
540
+ "hidden" if entity_id in hidden_entity_ids else None
544
541
  ),
545
542
  }
546
543
  for entity_id in area_entities
@@ -5,6 +5,7 @@ This module provides tools for listing, creating, updating, and deleting
5
5
  Home Assistant areas and floors - essential organizational features for smart homes.
6
6
  """
7
7
 
8
+ import asyncio
8
9
  import logging
9
10
  from typing import Annotated, Any, Literal
10
11
 
@@ -130,20 +131,20 @@ class AreaTools:
130
131
  return message
131
132
 
132
133
  # ============================================================
133
- # AREA TOOLS
134
+ # AREA & FLOOR LISTING
134
135
  # ============================================================
135
136
 
136
137
  @tool(
137
- name="ha_config_list_areas",
138
+ name="ha_list_floors_areas",
138
139
  tags={"Areas & Floors"},
139
140
  annotations={
140
141
  "idempotentHint": True,
141
142
  "readOnlyHint": True,
142
- "title": "List Areas",
143
+ "title": "List Floors and Areas",
143
144
  },
144
145
  )
145
146
  @log_tool_usage
146
- async def ha_config_list_areas(
147
+ async def ha_list_floors_areas(
147
148
  self,
148
149
  fields: Annotated[
149
150
  str | list[str] | None,
@@ -151,9 +152,11 @@ class AreaTools:
151
152
  default=None,
152
153
  description=(
153
154
  "Return only the specified top-level response keys to reduce "
154
- 'response size (e.g. ["areas"]). '
155
+ 'response size (e.g. ["floors"]). '
155
156
  "None = full response (default). "
156
- "Available keys: success, count, areas, message."
157
+ "Available keys: success, floor_count, area_count, "
158
+ "unassigned_count, orphaned_count, floors, unassigned_areas, "
159
+ "orphaned_areas, message."
157
160
  ),
158
161
  ),
159
162
  ] = None,
@@ -162,25 +165,32 @@ class AreaTools:
162
165
  Field(
163
166
  default=None,
164
167
  description=(
165
- "Project each area record to only the specified keys. "
166
- 'E.g. ["area_id", "name"] returns slim area records. '
167
- "None = full records (default). Unknown keys yield empty records. "
168
- "Available keys: area_id, name, icon, floor_id, aliases, picture, labels."
168
+ "Project each area record (in floors[].areas, unassigned_areas, "
169
+ 'and orphaned_areas) to only the specified keys. E.g. ["area_id", '
170
+ '"name"] returns slim area records. None = full records (default). '
171
+ "Unknown keys yield empty records. Available keys: area_id, name, "
172
+ "icon, floor_id, aliases, picture, labels."
169
173
  ),
170
174
  ),
171
175
  ] = None,
172
176
  ) -> dict[str, Any]:
173
177
  """
174
- List all Home Assistant areas (rooms).
178
+ List floors sorted by level ascending, each with their assigned areas nested, plus areas without a floor.
179
+
180
+ Use for location-based reasoning where floor-to-area relationships matter, such as "which rooms are on the ground floor" or operations scoped to a level. Optionally project the response with fields= (top-level keys) or area_fields= (per-area-record keys, applied uniformly across nested, unassigned, and orphaned buckets).
175
181
 
176
- Returns area ID, name, icon, floor assignment, aliases, and picture URL.
182
+ Floors with level=None sort alongside level 0 (ground floor). Areas without a floor assignment appear in unassigned_areas; areas whose floor_id points to a non-existent floor appear in orphaned_areas — a topology snapshot may diverge from individual list calls if the registries change between reads.
177
183
  """
184
+ # Validate projection params before any WS round-trips so a bad shape
185
+ # fails fast without burning two registry reads.
178
186
  parsed_fields: list[str] | None = None
179
187
  if fields is not None:
180
188
  try:
181
189
  parsed_fields = parse_string_list_param(
182
190
  fields, "fields", allow_csv=True
183
191
  )
192
+ if parsed_fields is not None and len(parsed_fields) == 0:
193
+ raise ValueError("fields must contain at least one key")
184
194
  except ValueError as exc:
185
195
  raise_tool_error(create_validation_error(str(exc), parameter="fields"))
186
196
  parsed_area_fields: list[str] | None = None
@@ -195,141 +205,39 @@ class AreaTools:
195
205
  raise_tool_error(
196
206
  create_validation_error(str(exc), parameter="area_fields")
197
207
  )
198
- try:
199
- message: dict[str, Any] = {
200
- "type": "config/area_registry/list",
201
- }
202
-
203
- result = await self._client.send_websocket_message(message)
204
-
205
- if result.get("success"):
206
- areas = result.get("result", [])
207
- _orig_areas = areas
208
- if parsed_area_fields is not None:
209
- areas = project_records(areas, parsed_area_fields)
210
- response: dict[str, Any] = {
211
- "success": True,
212
- "count": len(areas),
213
- "areas": areas,
214
- "message": f"Found {len(areas)} area(s)",
215
- }
216
- if parsed_area_fields is not None:
217
- _warn = result_fields_warning(
218
- _orig_areas, areas, parsed_area_fields, param_name="area_fields"
219
- )
220
- if _warn:
221
- response.setdefault("warnings", []).append(_warn)
222
- return project_fields(response, parsed_fields)
223
- else:
224
- raise_tool_error(
225
- create_error_response(
226
- ErrorCode.SERVICE_CALL_FAILED,
227
- result.get("error", "Failed to list areas"),
228
- )
229
- )
230
-
231
- except ToolError:
232
- raise
233
- except Exception as e:
234
- logger.error(f"Error listing areas: {e}")
235
- exception_to_structured_error(
236
- e,
237
- context={"operation": "list_areas"},
238
- suggestions=[
239
- "Check Home Assistant connection",
240
- "Verify WebSocket connection is active",
241
- ],
242
- )
243
-
244
- # ============================================================
245
- # FLOOR TOOLS
246
- # ============================================================
247
-
248
- @tool(
249
- name="ha_config_list_floors",
250
- tags={"Areas & Floors"},
251
- annotations={
252
- "idempotentHint": True,
253
- "readOnlyHint": True,
254
- "title": "List Floors",
255
- },
256
- )
257
- @log_tool_usage
258
- async def ha_config_list_floors(self) -> dict[str, Any]:
259
- """
260
- List all Home Assistant floors.
261
-
262
- Returns floor ID, name, icon, level (0=ground, 1=first, -1=basement), and aliases.
263
- """
264
- try:
265
- message: dict[str, Any] = {
266
- "type": "config/floor_registry/list",
267
- }
268
-
269
- result = await self._client.send_websocket_message(message)
270
-
271
- if result.get("success"):
272
- floors = result.get("result", [])
273
- return {
274
- "success": True,
275
- "count": len(floors),
276
- "floors": floors,
277
- "message": f"Found {len(floors)} floor(s)",
278
- }
279
- else:
280
- raise_tool_error(
281
- create_error_response(
282
- ErrorCode.SERVICE_CALL_FAILED,
283
- result.get("error", "Failed to list floors"),
284
- )
285
- )
286
208
 
287
- except ToolError:
288
- raise
289
- except Exception as e:
290
- logger.error(f"Error listing floors: {e}")
291
- exception_to_structured_error(
292
- e,
293
- context={"operation": "list_floors"},
294
- suggestions=[
295
- "Check Home Assistant connection",
296
- "Verify WebSocket connection is active",
297
- ],
298
- )
299
-
300
- @tool(
301
- name="ha_list_floors_areas",
302
- tags={"Areas & Floors"},
303
- annotations={
304
- "idempotentHint": True,
305
- "readOnlyHint": True,
306
- "title": "List Floors and Areas",
307
- },
308
- )
309
- @log_tool_usage
310
- async def ha_list_floors_areas(self) -> dict[str, Any]:
311
- """
312
- List floors sorted by level ascending, each with their assigned areas nested, plus areas without a floor.
313
-
314
- Do not use for flat listings — ha_config_list_areas and ha_config_list_floors cover those.
315
-
316
- Use for location-based reasoning where floor-to-area relationships matter, such as "which rooms are on the ground floor" or operations scoped to a level.
317
-
318
- Floors with level=None sort alongside level 0 (ground floor). Areas without a floor assignment appear in unassigned_areas; areas whose floor_id points to a non-existent floor appear in orphaned_areas — a topology snapshot may diverge from individual list calls if the registries change between reads.
319
- """
320
209
  progress: dict[str, Any] = {
321
210
  "operation": "list_floors_areas",
322
211
  "phase": "start",
323
212
  }
324
213
  try:
325
- areas_result = await self._client.send_websocket_message(
326
- {"type": "config/area_registry/list"}
327
- )
328
- progress["phase"] = "areas_fetched"
329
- floors_result = await self._client.send_websocket_message(
330
- {"type": "config/floor_registry/list"}
214
+ # Fetch both registries concurrently. Sequential awaits add a
215
+ # round-trip per call on the WS transport; gather halves the
216
+ # tool-side latency. Use return_exceptions=True so a failure on
217
+ # one side doesn't cancel the other — the post-fetch guard
218
+ # below reports both registries' state in the error context for
219
+ # diagnosis. Indexed access + explicit annotations rather than
220
+ # tuple-unpack — gather returns list[Any] which mypy can't
221
+ # statically narrow to a 2-tuple.
222
+ results = await asyncio.gather(
223
+ self._client.send_websocket_message(
224
+ {"type": "config/area_registry/list"}
225
+ ),
226
+ self._client.send_websocket_message(
227
+ {"type": "config/floor_registry/list"}
228
+ ),
229
+ return_exceptions=True,
331
230
  )
332
- progress["phase"] = "floors_fetched"
231
+ progress["phase"] = "registries_fetched"
232
+
233
+ # Re-raise transport-level exceptions from either fetch so the
234
+ # outer except handler classifies them via exception_to_structured_error.
235
+ if isinstance(results[0], BaseException):
236
+ raise results[0]
237
+ if isinstance(results[1], BaseException):
238
+ raise results[1]
239
+ areas_result: dict[str, Any] = results[0]
240
+ floors_result: dict[str, Any] = results[1]
333
241
 
334
242
  # A response with success=True but no "result" key is malformed —
335
243
  # treat it as a service call failure rather than silently returning
@@ -360,7 +268,7 @@ class AreaTools:
360
268
  # Partition areas into three disjoint sets:
361
269
  # - nested: floor_id present AND points to a known floor
362
270
  # - orphaned: floor_id present BUT points to a non-existent floor
363
- # (race between the two sequential reads, or manual
271
+ # (race between the concurrent reads, or manual
364
272
  # .storage inconsistency)
365
273
  # - unassigned: no floor_id at all
366
274
  # Orphaned is surfaced as a separate key so the LLM can diagnose
@@ -410,22 +318,50 @@ class AreaTools:
410
318
  topology.sort(key=_floor_sort_key)
411
319
  progress["phase"] = "sorted"
412
320
 
413
- return {
321
+ # Apply per-area projection across all 3 buckets uniformly.
322
+ # Snapshot pre-projection areas for the typo-guard warning.
323
+ _orig_all_areas = list(areas)
324
+ if parsed_area_fields is not None:
325
+ for floor in topology:
326
+ floor["areas"] = project_records(floor["areas"], parsed_area_fields)
327
+ unassigned_areas = project_records(unassigned_areas, parsed_area_fields)
328
+ orphaned_areas = project_records(orphaned_areas, parsed_area_fields)
329
+
330
+ response: dict[str, Any] = {
414
331
  "success": True,
415
332
  "floor_count": len(topology),
416
- "area_count": len(areas),
333
+ "area_count": len(_orig_all_areas),
417
334
  "unassigned_count": len(unassigned_areas),
418
335
  "orphaned_count": len(orphaned_areas),
419
336
  "floors": topology,
420
337
  "unassigned_areas": unassigned_areas,
421
338
  "orphaned_areas": orphaned_areas,
422
339
  "message": (
423
- f"Found {len(topology)} floor(s), {len(areas)} area(s), "
340
+ f"Found {len(topology)} floor(s), {len(_orig_all_areas)} area(s), "
424
341
  f"{len(unassigned_areas)} unassigned, "
425
342
  f"{len(orphaned_areas)} orphaned"
426
343
  ),
427
344
  }
428
345
 
346
+ # Typo-guard: combine projected areas across buckets to detect the
347
+ # all-empty-records situation that signals an unknown area_fields key.
348
+ if parsed_area_fields is not None:
349
+ _projected_all = (
350
+ [a for f in topology for a in f["areas"]]
351
+ + unassigned_areas
352
+ + orphaned_areas
353
+ )
354
+ _warn = result_fields_warning(
355
+ _orig_all_areas,
356
+ _projected_all,
357
+ parsed_area_fields,
358
+ param_name="area_fields",
359
+ )
360
+ if _warn:
361
+ response.setdefault("warnings", []).append(_warn)
362
+
363
+ return project_fields(response, parsed_fields)
364
+
429
365
  except ToolError:
430
366
  raise
431
367
  except Exception as e:
@@ -1154,7 +1154,7 @@ async def _validate_registry_ids(
1154
1154
  f"area_id={area_id!r} does not exist in the area registry.",
1155
1155
  context={"area_id": area_id},
1156
1156
  suggestions=[
1157
- "Use ha_config_list_areas() to list valid area IDs.",
1157
+ "Use ha_list_floors_areas() to list valid area IDs.",
1158
1158
  'Pass area_id="" to clear the area assignment.',
1159
1159
  f"Available area_ids: {sorted(valid_area_ids)}",
1160
1160
  ],
@@ -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