ha-mcp-dev 7.1.0.dev305__tar.gz → 7.1.0.dev307__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 (99) hide show
  1. {ha_mcp_dev-7.1.0.dev305/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.1.0.dev307}/PKG-INFO +29 -25
  2. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/README.md +28 -24
  3. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/pyproject.toml +2 -1
  4. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/tools/backup.py +2 -2
  5. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/tools/tools_addons.py +49 -48
  6. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/tools/tools_areas.py +6 -6
  7. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/tools/tools_blueprints.py +2 -2
  8. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/tools/tools_bug_report.py +2 -2
  9. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/tools/tools_calendar.py +3 -3
  10. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/tools/tools_camera.py +1 -1
  11. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/tools/tools_categories.py +6 -6
  12. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/tools/tools_config_automations.py +6 -6
  13. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/tools/tools_config_dashboards.py +11 -12
  14. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/tools/tools_config_entry_flow.py +4 -4
  15. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/tools/tools_config_helpers.py +6 -6
  16. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/tools/tools_config_scripts.py +6 -6
  17. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/tools/tools_entities.py +8 -8
  18. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/tools/tools_filesystem.py +8 -8
  19. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/tools/tools_groups.py +3 -3
  20. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/tools/tools_hacs.py +6 -6
  21. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/tools/tools_history.py +2 -2
  22. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/tools/tools_integrations.py +6 -6
  23. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/tools/tools_labels.py +3 -3
  24. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/tools/tools_mcp_component.py +2 -2
  25. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/tools/tools_registry.py +8 -7
  26. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/tools/tools_resources.py +9 -10
  27. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/tools/tools_search.py +25 -24
  28. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/tools/tools_service.py +4 -4
  29. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/tools/tools_services.py +1 -1
  30. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/tools/tools_system.py +4 -4
  31. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/tools/tools_todo.py +4 -4
  32. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/tools/tools_traces.py +1 -1
  33. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/tools/tools_updates.py +1 -1
  34. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/tools/tools_utility.py +4 -4
  35. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/tools/tools_voice_assistant.py +2 -1
  36. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/tools/tools_zones.py +3 -3
  37. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307/src/ha_mcp_dev.egg-info}/PKG-INFO +29 -25
  38. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/LICENSE +0 -0
  39. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/MANIFEST.in +0 -0
  40. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/setup.cfg +0 -0
  41. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/__init__.py +0 -0
  42. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/__main__.py +0 -0
  43. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/_pypi_marker +0 -0
  44. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/auth/__init__.py +0 -0
  45. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/auth/consent_form.py +0 -0
  46. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/auth/provider.py +0 -0
  47. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/client/__init__.py +0 -0
  48. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/client/rest_client.py +0 -0
  49. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/client/websocket_client.py +0 -0
  50. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/client/websocket_listener.py +0 -0
  51. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/config.py +0 -0
  52. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/errors.py +0 -0
  53. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/py.typed +0 -0
  54. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
  55. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
  56. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
  57. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
  58. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
  59. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
  60. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
  61. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
  62. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
  63. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
  64. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
  65. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
  66. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
  67. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
  68. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
  69. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
  70. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
  71. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
  72. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
  73. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
  74. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/server.py +0 -0
  75. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/smoke_test.py +0 -0
  76. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/tools/__init__.py +0 -0
  77. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/tools/best_practice_checker.py +0 -0
  78. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/tools/device_control.py +0 -0
  79. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/tools/enhanced.py +0 -0
  80. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/tools/helpers.py +0 -0
  81. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/tools/registry.py +0 -0
  82. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/tools/smart_search.py +0 -0
  83. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/tools/util_helpers.py +0 -0
  84. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/transforms/__init__.py +0 -0
  85. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/transforms/categorized_search.py +0 -0
  86. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/utils/__init__.py +0 -0
  87. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/utils/domain_handlers.py +0 -0
  88. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/utils/fuzzy_search.py +0 -0
  89. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/utils/operation_manager.py +0 -0
  90. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/utils/python_sandbox.py +0 -0
  91. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp/utils/usage_logger.py +0 -0
  92. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
  93. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
  94. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
  95. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
  96. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
  97. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/tests/__init__.py +0 -0
  98. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/tests/test_constants.py +0 -0
  99. {ha_mcp_dev-7.1.0.dev305 → ha_mcp_dev-7.1.0.dev307}/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.1.0.dev305
3
+ Version: 7.1.0.dev307
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
@@ -37,7 +37,7 @@ Dynamic: license-file
37
37
  <!-- mcp-name: io.github.homeassistant-ai/ha-mcp -->
38
38
 
39
39
  <p align="center">
40
- <img src="https://img.shields.io/badge/tools-95+-blue" alt="95+ Tools">
40
+ <img src="https://img.shields.io/badge/tools-93-blue" alt="95+ Tools">
41
41
  <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>
42
42
  <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>
43
43
  <a href="LICENSE.md"><img src="https://img.shields.io/github/license/homeassistant-ai/ha-mcp.svg" alt="License"></a>
@@ -158,33 +158,37 @@ Spend less time configuring, more time enjoying your smart home.
158
158
  | **💾 System** | Backup/restore, updates, add-ons, device registry |
159
159
 
160
160
  <details>
161
- <summary><b>🛠️ Complete Tool List (97 tools)</b></summary>
161
+ <!-- TOOLS_TABLE_START -->
162
+
163
+ <summary><b>Complete Tool List (93 tools)</b></summary>
162
164
 
163
165
  | Category | Tools |
164
166
  |----------|-------|
165
- | **Search & Discovery** | `ha_search_entities`, `ha_deep_search`, `ha_get_overview`, `ha_get_state` |
166
- | **Service & Device Control** | `ha_call_service`, `ha_bulk_control`, `ha_get_operation_status`, `ha_get_bulk_status`, `ha_list_services` |
167
- | **Automations** | `ha_config_get_automation`, `ha_config_set_automation`, `ha_config_remove_automation` |
168
- | **Scripts** | `ha_config_get_script`, `ha_config_set_script`, `ha_config_remove_script` |
169
- | **Helper Entities** | `ha_config_list_helpers`, `ha_config_set_helper`, `ha_config_remove_helper` |
170
- | **Dashboards** | `ha_config_get_dashboard`, `ha_config_set_dashboard`, `ha_config_delete_dashboard` + dashboard skill references |
171
- | **Areas & Floors** | `ha_config_list_areas`, `ha_config_set_area`, `ha_config_remove_area`, `ha_config_list_floors`, `ha_config_set_floor`, `ha_config_remove_floor` |
172
- | **Labels** | `ha_config_get_label`, `ha_config_set_label`, `ha_config_remove_label`, `ha_manage_entity_labels` |
173
- | **Zones** | `ha_get_zone`, `ha_set_zone`, `ha_remove_zone` |
174
- | **Groups** | `ha_config_list_groups`, `ha_config_set_group`, `ha_config_remove_group` |
175
- | **Todo Lists** | `ha_get_todo`, `ha_add_todo_item`, `ha_update_todo_item`, `ha_remove_todo_item` |
176
- | **Calendar** | `ha_config_get_calendar_events`, `ha_config_set_calendar_event`, `ha_config_remove_calendar_event` |
177
- | **Blueprints** | `ha_list_blueprints`, `ha_get_blueprint`, `ha_import_blueprint` |
178
- | **Device Registry** | `ha_get_device`, `ha_update_device`, `ha_remove_device`, `ha_rename_entity` |
179
- | **ZHA & Integrations** | `ha_get_zha_devices`, `ha_get_entity_integration_source` |
180
- | **Add-ons** | `ha_get_addon`, `ha_call_addon_api` (HTTP & WebSocket, direct port access) |
167
+ | **Add-ons** | `ha_call_addon_api`, `ha_get_addon` |
168
+ | **Areas & Floors** | `ha_config_list_areas`, `ha_config_list_floors`, `ha_config_remove_area`, `ha_config_remove_floor`, `ha_config_set_area`, `ha_config_set_floor` |
169
+ | **Automations** | `ha_config_get_automation`, `ha_config_remove_automation`, `ha_config_set_automation` |
170
+ | **Blueprints** | `ha_get_blueprint`, `ha_import_blueprint` |
171
+ | **Calendar** | `ha_config_get_calendar_events`, `ha_config_remove_calendar_event`, `ha_config_set_calendar_event` |
181
172
  | **Camera** | `ha_get_camera_image` |
182
- | **History & Statistics** | `ha_get_history`, `ha_get_statistics` |
183
- | **Automation Traces** | `ha_get_automation_traces` |
184
- | **System & Updates** | `ha_check_config`, `ha_restart`, `ha_reload_core`, `ha_get_system_info`, `ha_get_system_health`, `ha_get_updates` |
185
- | **Backup & Restore** | `ha_backup_create`, `ha_backup_restore` |
186
- | **Utility** | `ha_get_logbook`, `ha_eval_template`, `ha_get_integration` |
187
-
173
+ | **Dashboards** | `ha_config_delete_dashboard_resource`, `ha_config_delete_dashboard`, `ha_config_get_dashboard`, `ha_config_list_dashboard_resources`, `ha_config_set_dashboard_resource`, `ha_config_set_dashboard`, `ha_dashboard_find_card` |
174
+ | **Device Registry** | `ha_get_device`, `ha_remove_device`, `ha_rename_entity_and_device`, `ha_rename_entity`, `ha_update_device` |
175
+ | **Entity Registry** | `ha_get_entity_exposure`, `ha_get_entity`, `ha_set_entity` |
176
+ | **Files** | `ha_delete_file`, `ha_list_files`, `ha_read_file`, `ha_write_file` |
177
+ | **Groups** | `ha_config_list_groups`, `ha_config_remove_group`, `ha_config_set_group` |
178
+ | **HACS** | `ha_hacs_add_repository`, `ha_hacs_download`, `ha_hacs_info`, `ha_hacs_list_installed`, `ha_hacs_repository_info`, `ha_hacs_search` |
179
+ | **Helper Entities** | `ha_config_list_helpers`, `ha_config_remove_helper`, `ha_config_set_helper`, `ha_get_helper_schema`, `ha_set_config_entry_helper` |
180
+ | **History & Statistics** | `ha_get_automation_traces`, `ha_get_history`, `ha_get_logbook`, `ha_get_statistics` |
181
+ | **Integrations** | `ha_delete_config_entry`, `ha_get_integration`, `ha_set_integration_enabled` |
182
+ | **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` |
183
+ | **Scripts** | `ha_config_get_script`, `ha_config_remove_script`, `ha_config_set_script` |
184
+ | **Search & Discovery** | `ha_deep_search`, `ha_get_overview`, `ha_get_state`, `ha_get_states`, `ha_search_entities` |
185
+ | **Service & Device Control** | `ha_bulk_control`, `ha_call_service`, `ha_get_bulk_status`, `ha_get_operation_status`, `ha_list_services` |
186
+ | **System** | `ha_backup_create`, `ha_backup_restore`, `ha_check_config`, `ha_get_system_health`, `ha_get_updates`, `ha_reload_core`, `ha_restart` |
187
+ | **Todo Lists** | `ha_add_todo_item`, `ha_get_todo`, `ha_remove_todo_item`, `ha_update_todo_item` |
188
+ | **Utilities** | `ha_eval_template`, `ha_install_mcp_tools`, `ha_report_issue` |
189
+ | **Zones** | `ha_get_zone`, `ha_remove_zone`, `ha_set_zone` |
190
+
191
+ <!-- TOOLS_TABLE_END -->
188
192
  </details>
189
193
 
190
194
  ---
@@ -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-95+-blue" alt="95+ Tools">
11
+ <img src="https://img.shields.io/badge/tools-93-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>
@@ -129,33 +129,37 @@ Spend less time configuring, more time enjoying your smart home.
129
129
  | **💾 System** | Backup/restore, updates, add-ons, device registry |
130
130
 
131
131
  <details>
132
- <summary><b>🛠️ Complete Tool List (97 tools)</b></summary>
132
+ <!-- TOOLS_TABLE_START -->
133
+
134
+ <summary><b>Complete Tool List (93 tools)</b></summary>
133
135
 
134
136
  | Category | Tools |
135
137
  |----------|-------|
136
- | **Search & Discovery** | `ha_search_entities`, `ha_deep_search`, `ha_get_overview`, `ha_get_state` |
137
- | **Service & Device Control** | `ha_call_service`, `ha_bulk_control`, `ha_get_operation_status`, `ha_get_bulk_status`, `ha_list_services` |
138
- | **Automations** | `ha_config_get_automation`, `ha_config_set_automation`, `ha_config_remove_automation` |
139
- | **Scripts** | `ha_config_get_script`, `ha_config_set_script`, `ha_config_remove_script` |
140
- | **Helper Entities** | `ha_config_list_helpers`, `ha_config_set_helper`, `ha_config_remove_helper` |
141
- | **Dashboards** | `ha_config_get_dashboard`, `ha_config_set_dashboard`, `ha_config_delete_dashboard` + dashboard skill references |
142
- | **Areas & Floors** | `ha_config_list_areas`, `ha_config_set_area`, `ha_config_remove_area`, `ha_config_list_floors`, `ha_config_set_floor`, `ha_config_remove_floor` |
143
- | **Labels** | `ha_config_get_label`, `ha_config_set_label`, `ha_config_remove_label`, `ha_manage_entity_labels` |
144
- | **Zones** | `ha_get_zone`, `ha_set_zone`, `ha_remove_zone` |
145
- | **Groups** | `ha_config_list_groups`, `ha_config_set_group`, `ha_config_remove_group` |
146
- | **Todo Lists** | `ha_get_todo`, `ha_add_todo_item`, `ha_update_todo_item`, `ha_remove_todo_item` |
147
- | **Calendar** | `ha_config_get_calendar_events`, `ha_config_set_calendar_event`, `ha_config_remove_calendar_event` |
148
- | **Blueprints** | `ha_list_blueprints`, `ha_get_blueprint`, `ha_import_blueprint` |
149
- | **Device Registry** | `ha_get_device`, `ha_update_device`, `ha_remove_device`, `ha_rename_entity` |
150
- | **ZHA & Integrations** | `ha_get_zha_devices`, `ha_get_entity_integration_source` |
151
- | **Add-ons** | `ha_get_addon`, `ha_call_addon_api` (HTTP & WebSocket, direct port access) |
138
+ | **Add-ons** | `ha_call_addon_api`, `ha_get_addon` |
139
+ | **Areas & Floors** | `ha_config_list_areas`, `ha_config_list_floors`, `ha_config_remove_area`, `ha_config_remove_floor`, `ha_config_set_area`, `ha_config_set_floor` |
140
+ | **Automations** | `ha_config_get_automation`, `ha_config_remove_automation`, `ha_config_set_automation` |
141
+ | **Blueprints** | `ha_get_blueprint`, `ha_import_blueprint` |
142
+ | **Calendar** | `ha_config_get_calendar_events`, `ha_config_remove_calendar_event`, `ha_config_set_calendar_event` |
152
143
  | **Camera** | `ha_get_camera_image` |
153
- | **History & Statistics** | `ha_get_history`, `ha_get_statistics` |
154
- | **Automation Traces** | `ha_get_automation_traces` |
155
- | **System & Updates** | `ha_check_config`, `ha_restart`, `ha_reload_core`, `ha_get_system_info`, `ha_get_system_health`, `ha_get_updates` |
156
- | **Backup & Restore** | `ha_backup_create`, `ha_backup_restore` |
157
- | **Utility** | `ha_get_logbook`, `ha_eval_template`, `ha_get_integration` |
158
-
144
+ | **Dashboards** | `ha_config_delete_dashboard_resource`, `ha_config_delete_dashboard`, `ha_config_get_dashboard`, `ha_config_list_dashboard_resources`, `ha_config_set_dashboard_resource`, `ha_config_set_dashboard`, `ha_dashboard_find_card` |
145
+ | **Device Registry** | `ha_get_device`, `ha_remove_device`, `ha_rename_entity_and_device`, `ha_rename_entity`, `ha_update_device` |
146
+ | **Entity Registry** | `ha_get_entity_exposure`, `ha_get_entity`, `ha_set_entity` |
147
+ | **Files** | `ha_delete_file`, `ha_list_files`, `ha_read_file`, `ha_write_file` |
148
+ | **Groups** | `ha_config_list_groups`, `ha_config_remove_group`, `ha_config_set_group` |
149
+ | **HACS** | `ha_hacs_add_repository`, `ha_hacs_download`, `ha_hacs_info`, `ha_hacs_list_installed`, `ha_hacs_repository_info`, `ha_hacs_search` |
150
+ | **Helper Entities** | `ha_config_list_helpers`, `ha_config_remove_helper`, `ha_config_set_helper`, `ha_get_helper_schema`, `ha_set_config_entry_helper` |
151
+ | **History & Statistics** | `ha_get_automation_traces`, `ha_get_history`, `ha_get_logbook`, `ha_get_statistics` |
152
+ | **Integrations** | `ha_delete_config_entry`, `ha_get_integration`, `ha_set_integration_enabled` |
153
+ | **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` |
154
+ | **Scripts** | `ha_config_get_script`, `ha_config_remove_script`, `ha_config_set_script` |
155
+ | **Search & Discovery** | `ha_deep_search`, `ha_get_overview`, `ha_get_state`, `ha_get_states`, `ha_search_entities` |
156
+ | **Service & Device Control** | `ha_bulk_control`, `ha_call_service`, `ha_get_bulk_status`, `ha_get_operation_status`, `ha_list_services` |
157
+ | **System** | `ha_backup_create`, `ha_backup_restore`, `ha_check_config`, `ha_get_system_health`, `ha_get_updates`, `ha_reload_core`, `ha_restart` |
158
+ | **Todo Lists** | `ha_add_todo_item`, `ha_get_todo`, `ha_remove_todo_item`, `ha_update_todo_item` |
159
+ | **Utilities** | `ha_eval_template`, `ha_install_mcp_tools`, `ha_report_issue` |
160
+ | **Zones** | `ha_get_zone`, `ha_remove_zone`, `ha_set_zone` |
161
+
162
+ <!-- TOOLS_TABLE_END -->
159
163
  </details>
160
164
 
161
165
  ---
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ha-mcp-dev"
7
- version = "7.1.0.dev305"
7
+ version = "7.1.0.dev307"
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"
@@ -177,6 +177,7 @@ dev = [
177
177
  "lefthook>=1.10.0",
178
178
  "ruff>=0.12.12",
179
179
  "testcontainers>=4.13.0",
180
+ "ast-grep-cli>=0.42.0",
180
181
  ]
181
182
 
182
183
  # Semantic versioning configuration
@@ -391,7 +391,7 @@ def register_backup_tools(mcp: "FastMCP", client: HomeAssistantClient, **kwargs:
391
391
 
392
392
  **Returns:** Backup ID and job status"""
393
393
 
394
- @mcp.tool(description=backup_create_description, annotations={"destructiveHint": True, "title": "Create Backup"})
394
+ @mcp.tool(description=backup_create_description, tags={"System"}, annotations={"destructiveHint": True, "title": "Create Backup"})
395
395
  @log_tool_usage
396
396
  async def ha_backup_create(
397
397
  name: Annotated[
@@ -405,7 +405,7 @@ def register_backup_tools(mcp: "FastMCP", client: HomeAssistantClient, **kwargs:
405
405
  """Create a fast Home Assistant backup (local only)."""
406
406
  return await create_backup(client, name)
407
407
 
408
- @mcp.tool(annotations={"destructiveHint": True, "title": "Restore Backup"})
408
+ @mcp.tool(tags={"System"}, annotations={"destructiveHint": True, "title": "Restore Backup"})
409
409
  @log_tool_usage
410
410
  async def ha_backup_restore(
411
411
  backup_id: Annotated[
@@ -87,19 +87,19 @@ async def _supervisor_api_call(
87
87
  if not result.get("success"):
88
88
  error_msg = str(result.get("error", ""))
89
89
  if "not_found" in error_msg.lower() or "unknown" in error_msg.lower():
90
- return create_error_response(
90
+ raise_tool_error(create_error_response(
91
91
  ErrorCode.RESOURCE_NOT_FOUND,
92
92
  "Supervisor API not available",
93
93
  details=str(result),
94
94
  suggestions=[
95
95
  "This feature requires Home Assistant OS or Supervised installation",
96
96
  ],
97
- )
98
- return create_error_response(
97
+ ))
98
+ raise_tool_error(create_error_response(
99
99
  ErrorCode.SERVICE_CALL_FAILED,
100
100
  f"Supervisor API call failed: {endpoint}",
101
101
  details=str(result),
102
- )
102
+ ))
103
103
 
104
104
  return {"success": True, "result": result.get("result", {})}
105
105
 
@@ -107,10 +107,9 @@ async def _supervisor_api_call(
107
107
  raise
108
108
  except Exception as e:
109
109
  logger.error(f"Error calling Supervisor API {endpoint}: {e}")
110
- return exception_to_structured_error(
110
+ exception_to_structured_error(
111
111
  e,
112
112
  context={"endpoint": endpoint},
113
- raise_error=False,
114
113
  suggestions=["Check Home Assistant connection and Supervisor availability"],
115
114
  )
116
115
  finally:
@@ -311,23 +310,23 @@ async def _call_addon_ws(
311
310
  # 1. Sanitize path
312
311
  normalized = unquote(path).lstrip("/")
313
312
  if ".." in normalized.split("/"):
314
- return create_validation_error(
313
+ raise_tool_error(create_validation_error(
315
314
  "Path contains '..' traversal component",
316
315
  parameter="path",
317
316
  details=f"Rejected path: {path}",
318
- )
317
+ ))
319
318
 
320
319
  # 2. Get add-on info
321
320
  addon_response = await get_addon_info(client, slug)
322
321
  if not addon_response.get("success"):
323
- return addon_response
322
+ raise_tool_error(addon_response)
324
323
 
325
324
  addon = addon_response["addon"]
326
325
  addon_name = addon.get("name", slug)
327
326
 
328
327
  # 3. Verify add-on supports Ingress (unless using direct port override)
329
328
  if not port and not addon.get("ingress"):
330
- return create_error_response(
329
+ raise_tool_error(create_error_response(
331
330
  ErrorCode.VALIDATION_FAILED,
332
331
  f"Add-on '{addon_name}' does not support Ingress",
333
332
  suggestions=[
@@ -335,37 +334,37 @@ async def _call_addon_ws(
335
334
  f"Use ha_get_addon(slug='{slug}') to see available ports",
336
335
  ],
337
336
  context={"slug": slug},
338
- )
337
+ ))
339
338
 
340
339
  # 4. Verify add-on is running
341
340
  if addon.get("state") != "started":
342
- return create_error_response(
341
+ raise_tool_error(create_error_response(
343
342
  ErrorCode.SERVICE_CALL_FAILED,
344
343
  f"Add-on '{addon_name}' is not running (state: {addon.get('state')})",
345
344
  suggestions=[
346
345
  f"Start the add-on first with: ha_call_service('hassio', 'addon_start', {{'addon': '{slug}'}})",
347
346
  ],
348
347
  context={"slug": slug, "state": addon.get("state")},
349
- )
348
+ ))
350
349
 
351
350
  # 5. Build WebSocket URL
352
351
  addon_ip = addon.get("ip_address", "")
353
352
  if port:
354
353
  if not addon_ip:
355
- return create_error_response(
354
+ raise_tool_error(create_error_response(
356
355
  ErrorCode.INTERNAL_ERROR,
357
356
  f"Add-on '{addon_name}' is missing ip_address",
358
357
  context={"slug": slug},
359
- )
358
+ ))
360
359
  target_port = port
361
360
  else:
362
361
  ingress_port = addon.get("ingress_port")
363
362
  if not addon_ip or not ingress_port:
364
- return create_error_response(
363
+ raise_tool_error(create_error_response(
365
364
  ErrorCode.INTERNAL_ERROR,
366
365
  f"Add-on '{addon_name}' is missing network info",
367
366
  context={"slug": slug},
368
- )
367
+ ))
369
368
  target_port = ingress_port
370
369
 
371
370
  ws_url = f"ws://{addon_ip}:{target_port}/{normalized}"
@@ -439,7 +438,7 @@ async def _call_addon_ws(
439
438
  total_size += len(clean)
440
439
 
441
440
  except websockets.exceptions.InvalidHandshake as e:
442
- return create_error_response(
441
+ raise_tool_error(create_error_response(
443
442
  ErrorCode.SERVICE_CALL_FAILED,
444
443
  f"WebSocket handshake failed with '{addon_name}': {e!s}",
445
444
  suggestions=[
@@ -447,9 +446,9 @@ async def _call_addon_ws(
447
446
  f"Use ha_get_addon(slug='{slug}') to inspect available endpoints",
448
447
  ],
449
448
  context={"slug": slug, "path": path},
450
- )
449
+ ))
451
450
  except websockets.exceptions.ConnectionClosed as e:
452
- return create_error_response(
451
+ raise_tool_error(create_error_response(
453
452
  ErrorCode.SERVICE_CALL_FAILED,
454
453
  f"WebSocket connection to '{addon_name}' closed unexpectedly: {e!s}",
455
454
  suggestions=[
@@ -457,20 +456,20 @@ async def _call_addon_ws(
457
456
  "Try again or check add-on logs for errors",
458
457
  ],
459
458
  context={"slug": slug, "path": path},
460
- )
459
+ ))
461
460
  except TimeoutError:
462
- return create_timeout_error(
461
+ raise_tool_error(create_timeout_error(
463
462
  f"WebSocket connection to '{addon_name}'",
464
463
  timeout,
465
464
  details=f"path={path}",
466
465
  context={"slug": slug, "path": path},
467
- )
466
+ ))
468
467
  except OSError as e:
469
- return create_connection_error(
468
+ raise_tool_error(create_connection_error(
470
469
  f"Failed to connect to add-on '{addon_name}' WebSocket: {e!s}",
471
470
  details="Check that the add-on is running and the port is correct",
472
471
  context={"slug": slug},
473
- )
472
+ ))
474
473
 
475
474
  elapsed = round(time.monotonic() - start_time, 2)
476
475
 
@@ -554,23 +553,23 @@ async def _call_addon_api(
554
553
  # 1. Sanitize path to prevent traversal attacks (including URL-encoded)
555
554
  normalized = unquote(path).lstrip("/")
556
555
  if ".." in normalized.split("/"):
557
- return create_validation_error(
556
+ raise_tool_error(create_validation_error(
558
557
  "Path contains '..' traversal component",
559
558
  parameter="path",
560
559
  details=f"Rejected path: {path}",
561
- )
560
+ ))
562
561
 
563
562
  # 2. Get add-on info to verify ingress support and get entry path
564
563
  addon_response = await get_addon_info(client, slug)
565
564
  if not addon_response.get("success"):
566
- return addon_response
565
+ raise_tool_error(addon_response)
567
566
 
568
567
  addon = addon_response["addon"]
569
568
  addon_name = addon.get("name", slug)
570
569
 
571
570
  # 3. Verify add-on supports Ingress (unless using direct port override)
572
571
  if not port and not addon.get("ingress"):
573
- return create_error_response(
572
+ raise_tool_error(create_error_response(
574
573
  ErrorCode.VALIDATION_FAILED,
575
574
  f"Add-on '{addon_name}' does not support Ingress",
576
575
  suggestions=[
@@ -579,18 +578,18 @@ async def _call_addon_api(
579
578
  "Use the 'port' parameter to connect to a direct access port",
580
579
  ],
581
580
  context={"slug": slug},
582
- )
581
+ ))
583
582
 
584
583
  # 4. Verify add-on is running
585
584
  if addon.get("state") != "started":
586
- return create_error_response(
585
+ raise_tool_error(create_error_response(
587
586
  ErrorCode.SERVICE_CALL_FAILED,
588
587
  f"Add-on '{addon_name}' is not running (state: {addon.get('state')})",
589
588
  suggestions=[
590
589
  f"Start the add-on first with: ha_call_service('hassio', 'addon_start', {{'addon': '{slug}'}})",
591
590
  ],
592
591
  context={"slug": slug, "state": addon.get("state")},
593
- )
592
+ ))
594
593
 
595
594
  # 5. Build URL to the add-on container
596
595
  addon_ip = addon.get("ip_address", "")
@@ -600,21 +599,21 @@ async def _call_addon_api(
600
599
  # (e.g., 1880 for Node-RED, 6052 for ESPHome) instead of the ingress port.
601
600
  # Requires 'leave_front_door_open' or equivalent setting on the add-on.
602
601
  if not addon_ip:
603
- return create_error_response(
602
+ raise_tool_error(create_error_response(
604
603
  ErrorCode.INTERNAL_ERROR,
605
604
  f"Add-on '{addon_name}' is missing ip_address",
606
605
  context={"slug": slug, "ip_address": addon_ip},
607
- )
606
+ ))
608
607
  target_port = port
609
608
  else:
610
609
  # Default: use the ingress port for direct container communication
611
610
  ingress_port = addon.get("ingress_port")
612
611
  if not addon_ip or not ingress_port:
613
- return create_error_response(
612
+ raise_tool_error(create_error_response(
614
613
  ErrorCode.INTERNAL_ERROR,
615
614
  f"Add-on '{addon_name}' is missing network info (ip_address or ingress_port)",
616
615
  context={"slug": slug, "ip_address": addon_ip, "ingress_port": ingress_port},
617
- )
616
+ ))
618
617
  target_port = ingress_port
619
618
 
620
619
  url = f"http://{addon_ip}:{target_port}/{normalized}"
@@ -648,18 +647,18 @@ async def _call_addon_api(
648
647
  content=request_content,
649
648
  )
650
649
  except httpx.TimeoutException:
651
- return create_timeout_error(
650
+ raise_tool_error(create_timeout_error(
652
651
  f"add-on API call to '{addon_name}'",
653
652
  timeout,
654
653
  details=f"path={path}, method={method}",
655
654
  context={"slug": slug, "path": path},
656
- )
655
+ ))
657
656
  except httpx.ConnectError as e:
658
- return create_connection_error(
657
+ raise_tool_error(create_connection_error(
659
658
  f"Failed to connect to add-on '{addon_name}': {e!s}",
660
659
  details="Check that the add-on is running and Home Assistant Ingress is working",
661
660
  context={"slug": slug},
662
- )
661
+ ))
663
662
 
664
663
  # 7. Parse response
665
664
  content_type = response.headers.get("content-type", "")
@@ -783,7 +782,7 @@ def register_addon_tools(mcp: Any, client: HomeAssistantClient, **kwargs: Any) -
783
782
  **kwargs: Additional arguments (ignored, for auto-discovery compatibility)
784
783
  """
785
784
 
786
- @mcp.tool(annotations={"idempotentHint": True, "readOnlyHint": True, "tags": ["addon"], "title": "Get Add-ons"})
785
+ @mcp.tool(tags={"Add-ons"}, annotations={"idempotentHint": True, "readOnlyHint": True, "title": "Get Add-ons"})
787
786
  @log_tool_usage
788
787
  async def ha_get_addon(
789
788
  source: Annotated[
@@ -878,13 +877,15 @@ def register_addon_tools(mcp: Any, client: HomeAssistantClient, **kwargs: Any) -
878
877
  raise_tool_error(result)
879
878
  return result
880
879
 
881
- @mcp.tool(annotations={
882
- "destructiveHint": True,
883
- "idempotentHint": False,
884
- "readOnlyHint": False,
885
- "tags": ["addon"],
886
- "title": "Call Add-on API",
887
- })
880
+ @mcp.tool(
881
+ tags={"Add-ons"},
882
+ annotations={
883
+ "destructiveHint": True,
884
+ "idempotentHint": False,
885
+ "readOnlyHint": False,
886
+ "title": "Call Add-on API",
887
+ },
888
+ )
888
889
  @log_tool_usage
889
890
  async def ha_call_addon_api(
890
891
  slug: Annotated[
@@ -25,7 +25,7 @@ def register_area_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
25
25
  # AREA TOOLS
26
26
  # ============================================================
27
27
 
28
- @mcp.tool(annotations={"idempotentHint": True, "readOnlyHint": True, "tags": ["area"], "title": "List Areas"})
28
+ @mcp.tool(tags={"Areas & Floors"}, annotations={"idempotentHint": True, "readOnlyHint": True, "title": "List Areas"})
29
29
  @log_tool_usage
30
30
  async def ha_config_list_areas() -> dict[str, Any]:
31
31
  """
@@ -63,7 +63,7 @@ def register_area_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
63
63
  "Verify WebSocket connection is active",
64
64
  ])
65
65
 
66
- @mcp.tool(annotations={"destructiveHint": True, "tags": ["area"], "title": "Create or Update Area"})
66
+ @mcp.tool(tags={"Areas & Floors"}, annotations={"destructiveHint": True, "title": "Create or Update Area"})
67
67
  @log_tool_usage
68
68
  async def ha_config_set_area(
69
69
  name: Annotated[
@@ -208,7 +208,7 @@ def register_area_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
208
208
  "If assigning to a floor, verify floor_id exists",
209
209
  ])
210
210
 
211
- @mcp.tool(annotations={"destructiveHint": True, "idempotentHint": True, "tags": ["area"], "title": "Remove Area"})
211
+ @mcp.tool(tags={"Areas & Floors"}, annotations={"destructiveHint": True, "idempotentHint": True, "title": "Remove Area"})
212
212
  @log_tool_usage
213
213
  async def ha_config_remove_area(
214
214
  area_id: Annotated[
@@ -258,7 +258,7 @@ def register_area_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
258
258
  # FLOOR TOOLS
259
259
  # ============================================================
260
260
 
261
- @mcp.tool(annotations={"idempotentHint": True, "readOnlyHint": True, "tags": ["floor"], "title": "List Floors"})
261
+ @mcp.tool(tags={"Areas & Floors"}, annotations={"idempotentHint": True, "readOnlyHint": True, "title": "List Floors"})
262
262
  @log_tool_usage
263
263
  async def ha_config_list_floors() -> dict[str, Any]:
264
264
  """
@@ -296,7 +296,7 @@ def register_area_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
296
296
  "Verify WebSocket connection is active",
297
297
  ])
298
298
 
299
- @mcp.tool(annotations={"destructiveHint": True, "tags": ["floor"], "title": "Create or Update Floor"})
299
+ @mcp.tool(tags={"Areas & Floors"}, annotations={"destructiveHint": True, "title": "Create or Update Floor"})
300
300
  @log_tool_usage
301
301
  async def ha_config_set_floor(
302
302
  name: Annotated[
@@ -429,7 +429,7 @@ def register_area_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
429
429
  "For update: Verify the floor_id exists using ha_config_list_floors()",
430
430
  ])
431
431
 
432
- @mcp.tool(annotations={"destructiveHint": True, "idempotentHint": True, "tags": ["floor"], "title": "Remove Floor"})
432
+ @mcp.tool(tags={"Areas & Floors"}, annotations={"destructiveHint": True, "idempotentHint": True, "title": "Remove Floor"})
433
433
  @log_tool_usage
434
434
  async def ha_config_remove_floor(
435
435
  floor_id: Annotated[
@@ -56,7 +56,7 @@ def register_blueprint_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
56
56
  "blueprints": blueprints,
57
57
  }
58
58
 
59
- @mcp.tool(annotations={"idempotentHint": True, "readOnlyHint": True, "tags": ["blueprint"], "title": "Get Blueprint"})
59
+ @mcp.tool(tags={"Blueprints"}, annotations={"idempotentHint": True, "readOnlyHint": True, "title": "Get Blueprint"})
60
60
  @log_tool_usage
61
61
  async def ha_get_blueprint(
62
62
  path: Annotated[
@@ -183,7 +183,7 @@ def register_blueprint_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
183
183
  ],
184
184
  )
185
185
 
186
- @mcp.tool(annotations={"destructiveHint": True, "tags": ["blueprint"], "title": "Import Blueprint"})
186
+ @mcp.tool(tags={"Blueprints"}, annotations={"destructiveHint": True, "title": "Import Blueprint"})
187
187
  @log_tool_usage
188
188
  async def ha_import_blueprint(
189
189
  url: Annotated[
@@ -85,11 +85,11 @@ def register_bug_report_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
85
85
  """Register bug report tools with the MCP server."""
86
86
 
87
87
  @mcp.tool(
88
+ tags={"Utilities"},
88
89
  annotations={
89
90
  "idempotentHint": True,
90
91
  "readOnlyHint": True,
91
- "tags": ["system", "diagnostics", "feedback"],
92
- "title": "Report Issue or Feedback",
92
+ "title": "Report Issue or Feedback"
93
93
  }
94
94
  )
95
95
  @log_tool_usage
@@ -23,7 +23,7 @@ logger = logging.getLogger(__name__)
23
23
  def register_calendar_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
24
24
  """Register calendar management tools with the MCP server."""
25
25
 
26
- @mcp.tool(annotations={"idempotentHint": True, "readOnlyHint": True, "tags": ["calendar"], "title": "Get Calendar Events"})
26
+ @mcp.tool(tags={"Calendar"}, annotations={"idempotentHint": True, "readOnlyHint": True, "title": "Get Calendar Events"})
27
27
  @log_tool_usage
28
28
  async def ha_config_get_calendar_events(
29
29
  entity_id: Annotated[
@@ -144,7 +144,7 @@ def register_calendar_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
144
144
 
145
145
  exception_to_structured_error(error, context={"entity_id": entity_id}, suggestions=suggestions)
146
146
 
147
- @mcp.tool(annotations={"destructiveHint": True, "tags": ["calendar"], "title": "Create or Update Calendar Event"})
147
+ @mcp.tool(tags={"Calendar"}, annotations={"destructiveHint": True, "title": "Create or Update Calendar Event"})
148
148
  @log_tool_usage
149
149
  async def ha_config_set_calendar_event(
150
150
  entity_id: Annotated[
@@ -263,7 +263,7 @@ def register_calendar_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
263
263
 
264
264
  exception_to_structured_error(error, context={"entity_id": entity_id}, suggestions=suggestions)
265
265
 
266
- @mcp.tool(annotations={"destructiveHint": True, "idempotentHint": True, "tags": ["calendar"], "title": "Remove Calendar Event"})
266
+ @mcp.tool(tags={"Calendar"}, annotations={"destructiveHint": True, "idempotentHint": True, "title": "Remove Calendar Event"})
267
267
  @log_tool_usage
268
268
  async def ha_config_remove_calendar_event(
269
269
  entity_id: Annotated[
@@ -18,7 +18,7 @@ logger = logging.getLogger(__name__)
18
18
  def register_camera_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
19
19
  """Register Home Assistant camera tools."""
20
20
 
21
- @mcp.tool(annotations={"idempotentHint": True, "readOnlyHint": True, "tags": ["camera"], "title": "Get Camera Image"})
21
+ @mcp.tool(tags={"Camera"}, annotations={"idempotentHint": True, "readOnlyHint": True, "title": "Get Camera Image"})
22
22
  @log_tool_usage
23
23
  async def ha_get_camera_image(
24
24
  entity_id: str,
@@ -24,11 +24,11 @@ def register_category_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
24
24
  """Register Home Assistant category management tools."""
25
25
 
26
26
  @mcp.tool(
27
+ tags={"Labels & Categories"},
27
28
  annotations={
28
29
  "idempotentHint": True,
29
30
  "readOnlyHint": True,
30
- "tags": ["category"],
31
- "title": "Get Category",
31
+ "title": "Get Category"
32
32
  }
33
33
  )
34
34
  @log_tool_usage
@@ -143,10 +143,10 @@ def register_category_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
143
143
  )
144
144
 
145
145
  @mcp.tool(
146
+ tags={"Labels & Categories"},
146
147
  annotations={
147
148
  "destructiveHint": True,
148
- "tags": ["category"],
149
- "title": "Create or Update Category",
149
+ "title": "Create or Update Category"
150
150
  }
151
151
  )
152
152
  @log_tool_usage
@@ -246,11 +246,11 @@ def register_category_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
246
246
  )
247
247
 
248
248
  @mcp.tool(
249
+ tags={"Labels & Categories"},
249
250
  annotations={
250
251
  "destructiveHint": True,
251
252
  "idempotentHint": True,
252
- "tags": ["category"],
253
- "title": "Remove Category",
253
+ "title": "Remove Category"
254
254
  }
255
255
  )
256
256
  @log_tool_usage