ha-mcp-dev 7.5.0.dev524__tar.gz → 7.5.0.dev526__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 (111) hide show
  1. {ha_mcp_dev-7.5.0.dev524/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.5.0.dev526}/PKG-INFO +4 -4
  2. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/README.md +3 -3
  3. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/pyproject.toml +1 -1
  4. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/server.py +5 -5
  5. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/tools/helpers.py +84 -0
  6. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/tools/tools_addons.py +19 -4
  7. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/tools/tools_areas.py +35 -23
  8. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/tools/tools_calendar.py +13 -0
  9. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/tools/tools_categories.py +35 -0
  10. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/tools/tools_config_automations.py +10 -0
  11. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/tools/tools_config_entry_flow.py +63 -349
  12. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/tools/tools_config_helpers.py +112 -44
  13. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/tools/tools_config_scripts.py +10 -0
  14. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/tools/tools_energy.py +24 -0
  15. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/tools/tools_entities.py +17 -0
  16. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/tools/tools_groups.py +28 -1
  17. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/tools/tools_hacs.py +16 -0
  18. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/tools/tools_integrations.py +29 -0
  19. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/tools/tools_labels.py +35 -0
  20. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/tools/tools_registry.py +24 -0
  21. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/tools/tools_resources.py +24 -0
  22. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/tools/tools_todo.py +31 -0
  23. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/tools/tools_zones.py +22 -0
  24. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526/src/ha_mcp_dev.egg-info}/PKG-INFO +4 -4
  25. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/LICENSE +0 -0
  26. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/MANIFEST.in +0 -0
  27. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/setup.cfg +0 -0
  28. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/__init__.py +0 -0
  29. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/__main__.py +0 -0
  30. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/_pypi_marker +0 -0
  31. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/_version.py +0 -0
  32. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/auth/__init__.py +0 -0
  33. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/auth/consent_form.py +0 -0
  34. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/auth/provider.py +0 -0
  35. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/client/__init__.py +0 -0
  36. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/client/rest_client.py +0 -0
  37. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/client/supervisor_client.py +0 -0
  38. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/client/websocket_client.py +0 -0
  39. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/client/websocket_listener.py +0 -0
  40. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/config.py +0 -0
  41. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/errors.py +0 -0
  42. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/py.typed +0 -0
  43. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
  44. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
  45. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
  46. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
  47. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
  48. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
  49. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
  50. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
  51. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
  52. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
  53. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
  54. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
  55. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
  56. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
  57. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
  58. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
  59. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
  60. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
  61. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
  62. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
  63. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/settings_ui.py +0 -0
  64. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/smoke_test.py +0 -0
  65. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/tools/__init__.py +0 -0
  66. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/tools/backup.py +0 -0
  67. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/tools/best_practice_checker.py +0 -0
  68. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/tools/device_control.py +0 -0
  69. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/tools/enhanced.py +0 -0
  70. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/tools/reference_validator.py +0 -0
  71. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/tools/registry.py +0 -0
  72. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/tools/smart_search.py +0 -0
  73. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/tools/tools_blueprints.py +0 -0
  74. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/tools/tools_bug_report.py +0 -0
  75. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/tools/tools_camera.py +0 -0
  76. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/tools/tools_code.py +0 -0
  77. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
  78. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
  79. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/tools/tools_filesystem.py +0 -0
  80. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/tools/tools_history.py +0 -0
  81. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/tools/tools_mcp_component.py +0 -0
  82. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/tools/tools_search.py +0 -0
  83. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/tools/tools_service.py +0 -0
  84. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/tools/tools_services.py +0 -0
  85. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/tools/tools_system.py +0 -0
  86. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/tools/tools_traces.py +0 -0
  87. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/tools/tools_updates.py +0 -0
  88. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/tools/tools_utility.py +0 -0
  89. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
  90. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
  91. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/tools/util_helpers.py +0 -0
  92. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/transforms/__init__.py +0 -0
  93. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/transforms/categorized_search.py +0 -0
  94. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
  95. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/utils/__init__.py +0 -0
  96. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/utils/config_hash.py +0 -0
  97. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/utils/data_paths.py +0 -0
  98. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/utils/domain_handlers.py +0 -0
  99. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/utils/fuzzy_search.py +0 -0
  100. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
  101. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/utils/operation_manager.py +0 -0
  102. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/utils/python_sandbox.py +0 -0
  103. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp/utils/usage_logger.py +0 -0
  104. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
  105. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
  106. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
  107. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
  108. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
  109. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/tests/__init__.py +0 -0
  110. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/tests/test_constants.py +0 -0
  111. {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev526}/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.dev524
3
+ Version: 7.5.0.dev526
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-89-blue" alt="95+ Tools">
41
+ <img src="https://img.shields.io/badge/tools-88-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,7 +181,7 @@ 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 (89 tools)</b></summary>
184
+ <summary><b>Complete Tool List (88 tools)</b></summary>
185
185
 
186
186
  | Category | Tools |
187
187
  |----------|-------|
@@ -198,7 +198,7 @@ Spend less time configuring, more time enjoying your smart home.
198
198
  | **Files** | `ha_delete_file` *(beta)*, `ha_list_files` *(beta)*, `ha_read_file` *(beta)*, `ha_write_file` *(beta)* |
199
199
  | **Groups** | `ha_config_list_groups`, `ha_config_remove_group`, `ha_config_set_group` |
200
200
  | **HACS** | `ha_hacs_add_repository`, `ha_hacs_download`, `ha_hacs_repository_info`, `ha_hacs_search` |
201
- | **Helper Entities** | `ha_config_list_helpers`, `ha_config_set_helper`, `ha_delete_helpers_integrations`, `ha_get_helper_schema` |
201
+ | **Helper Entities** | `ha_config_list_helpers`, `ha_config_set_helper`, `ha_delete_helpers_integrations` |
202
202
  | **History & Statistics** | `ha_get_automation_traces`, `ha_get_history`, `ha_get_logs` |
203
203
  | **Integrations** | `ha_get_integration`, `ha_set_integration_enabled` |
204
204
  | **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-89-blue" alt="95+ Tools">
11
+ <img src="https://img.shields.io/badge/tools-88-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,7 +151,7 @@ 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 (89 tools)</b></summary>
154
+ <summary><b>Complete Tool List (88 tools)</b></summary>
155
155
 
156
156
  | Category | Tools |
157
157
  |----------|-------|
@@ -168,7 +168,7 @@ Spend less time configuring, more time enjoying your smart home.
168
168
  | **Files** | `ha_delete_file` *(beta)*, `ha_list_files` *(beta)*, `ha_read_file` *(beta)*, `ha_write_file` *(beta)* |
169
169
  | **Groups** | `ha_config_list_groups`, `ha_config_remove_group`, `ha_config_set_group` |
170
170
  | **HACS** | `ha_hacs_add_repository`, `ha_hacs_download`, `ha_hacs_repository_info`, `ha_hacs_search` |
171
- | **Helper Entities** | `ha_config_list_helpers`, `ha_config_set_helper`, `ha_delete_helpers_integrations`, `ha_get_helper_schema` |
171
+ | **Helper Entities** | `ha_config_list_helpers`, `ha_config_set_helper`, `ha_delete_helpers_integrations` |
172
172
  | **History & Statistics** | `ha_get_automation_traces`, `ha_get_history`, `ha_get_logs` |
173
173
  | **Integrations** | `ha_get_integration`, `ha_set_integration_enabled` |
174
174
  | **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.dev524"
7
+ version = "7.5.0.dev526"
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"
@@ -548,7 +548,7 @@ class HomeAssistantSmartMCPServer(EnhancedToolsMixin):
548
548
  "utility_meter, derivative, statistics, trend, threshold, "
549
549
  "filter, switch_as_x, etc.) cannot be listed through this "
550
550
  "tool — use ha_search_entities or ha_deep_search.\n\n"
551
- "For per-type schemas, see ha_get_helper_schema and "
551
+ "For per-type schemas and decision guidance, see "
552
552
  "ha_get_skill_guide."
553
553
  ),
554
554
  "ha_config_set_helper": (
@@ -558,10 +558,10 @@ class HomeAssistantSmartMCPServer(EnhancedToolsMixin):
558
558
  "flow-based types (template, group, utility_meter, "
559
559
  "derivative, statistics, trend, threshold, filter, "
560
560
  "switch_as_x, and others).\n\n"
561
- "For per-type config schemas, call "
562
- "ha_get_helper_schema(helper_type) first. For decision "
563
- "matrix and worked examples (which helper type for which "
564
- "use case), see ha_get_skill_guide."
561
+ "Field set is delivered as `data_schema` on the first "
562
+ "validation error — submit once and self-correct. For "
563
+ "decision matrix and worked examples (which helper type "
564
+ "for which use case), see ha_get_skill_guide."
565
565
  ),
566
566
  "ha_config_get_dashboard": (
567
567
  "Get Home Assistant dashboard info (list mode, search "
@@ -78,6 +78,90 @@ def extract_tool_error_message(te: ToolError) -> str:
78
78
  return str(te)
79
79
 
80
80
 
81
+ def validate_identifier_not_empty(
82
+ value: str | None,
83
+ param_name: str,
84
+ *,
85
+ message: str | None = None,
86
+ suggestions: list[str] | None = None,
87
+ context: dict[str, Any] | None = None,
88
+ ) -> str:
89
+ """Reject ``None``, empty, or whitespace-only identifier values.
90
+
91
+ Surfaces a structured ``VALIDATION_INVALID_PARAMETER`` response so
92
+ CRUD-style tools fail loudly on missing identifiers instead of silently
93
+ routing on Python falsy semantics or relying on Home Assistant to
94
+ translate a missing identifier into a generic ``RESOURCE_NOT_FOUND``.
95
+
96
+ The destructive class this protects against:
97
+ ``action = "update" if label_id else "create"`` — passing ``""`` as
98
+ ``label_id`` silently routes to ``create`` when the caller intended
99
+ ``update``. The whitespace class this protects against: ``" "`` is truthy
100
+ in Python so ``if not value:`` lets it through, but Home Assistant has
101
+ no entry with id ``" "``.
102
+
103
+ The value is checked but not normalised: ``" abc "`` (a real id wrapped
104
+ in spaces) is accepted as-is and returned untouched. Only purely empty or
105
+ purely whitespace strings are rejected.
106
+
107
+ ``None`` is also rejected by this helper. Callers for whom ``None`` is a
108
+ documented sentinel (e.g. ``label_id=None`` meaning "list all" or
109
+ "create new") must gate the call themselves with
110
+ ``if value is not None: validate_identifier_not_empty(value, ...)``.
111
+
112
+ Returning ``str`` (rather than ``None``) lets call sites use the helper
113
+ in narrowing position — ``name = validate_identifier_not_empty(name, …)``
114
+ re-binds ``name`` from ``str | None`` to ``str`` so mypy can prove later
115
+ uses are safe without a duplicate inline check.
116
+
117
+ Args:
118
+ value: Identifier string supplied by the caller. ``None`` is
119
+ rejected — see the ``None``-sentinel note above for the caller
120
+ pattern that permits the sentinel.
121
+ param_name: Name of the parameter, used in the structured error
122
+ response's ``context`` and the human-readable message.
123
+ message: Optional override for the human-readable error message.
124
+ Defaults to ``"{param_name} must be a non-empty, non-whitespace
125
+ string"`` when omitted.
126
+ suggestions: Optional list of guidance strings for the response's
127
+ ``suggestions`` field. Defaults to a generic
128
+ "provide a non-empty value" hint.
129
+ context: Optional additional context fields merged into the error
130
+ response (e.g. ``{"action": "update"}``). The keys ``parameter``
131
+ and ``value`` are always set and take precedence.
132
+
133
+ Returns:
134
+ The validated ``value`` unchanged (typed as ``str``).
135
+
136
+ Raises:
137
+ ToolError: When ``value`` is ``None``, empty, or whitespace-only —
138
+ carrying a structured ``VALIDATION_INVALID_PARAMETER`` response
139
+ with the parameter name, the offending value (for diagnostics),
140
+ and the suggestions.
141
+
142
+ Example:
143
+ >>> validate_identifier_not_empty(label_id, "label_id",
144
+ ... suggestions=["Omit label_id to create a new label"])
145
+ """
146
+ if value is not None and value.strip():
147
+ return value
148
+
149
+ final_context: dict[str, Any] = {}
150
+ if context:
151
+ final_context.update(context)
152
+ final_context["parameter"] = param_name
153
+ final_context["value"] = value
154
+ raise_tool_error(
155
+ create_error_response(
156
+ ErrorCode.VALIDATION_INVALID_PARAMETER,
157
+ message or f"{param_name} must be a non-empty, non-whitespace string",
158
+ suggestions=suggestions
159
+ or [f"Provide a valid non-empty value for {param_name}"],
160
+ context=final_context,
161
+ )
162
+ )
163
+
164
+
81
165
  async def get_connected_ws_client(
82
166
  base_url: str, token: str, verify_ssl: bool | None = None
83
167
  ) -> tuple[HomeAssistantWebSocketClient | None, dict[str, Any] | None]:
@@ -38,6 +38,7 @@ from .helpers import (
38
38
  get_connected_ws_client,
39
39
  log_tool_usage,
40
40
  raise_tool_error,
41
+ validate_identifier_not_empty,
41
42
  )
42
43
  from .util_helpers import ANSI_ESCAPE_RE
43
44
 
@@ -1208,14 +1209,16 @@ def _apply_array_ops(
1208
1209
  )
1209
1210
  )
1210
1211
  new_id = new_item[id_field]
1211
- if new_id is None or new_id == "":
1212
+ if new_id is None or (isinstance(new_id, str) and not new_id.strip()):
1212
1213
  # Items missing the id field have `dict.get(id_field) == None`
1213
- # by default, so allowing None/"" ids would let later patch /
1214
- # delete ops match unrelated items.
1214
+ # by default, so allowing None/""/whitespace-only ids would
1215
+ # let later patch / delete ops match unrelated items.
1216
+ # Non-string ids (e.g. integers) stay valid by design — see
1217
+ # ``test_add_with_integer_zero_id_is_accepted``.
1215
1218
  raise_tool_error(
1216
1219
  create_validation_error(
1217
1220
  f"array_patch add op #{index} item {id_field!r} cannot be "
1218
- "None or an empty string",
1221
+ "None, empty, or whitespace-only",
1219
1222
  parameter=f"array_patch.operations[{index}].item.{id_field}",
1220
1223
  )
1221
1224
  )
@@ -1954,6 +1957,18 @@ def register_addon_tools(mcp: Any, client: HomeAssistantClient, **kwargs: Any) -
1954
1957
  ha_manage_addon(slug="...", path="/api/state",
1955
1958
  request_headers={"Accept": "text/plain"})
1956
1959
  """
1960
+ # Empty/whitespace slug would propagate to every dispatch arm
1961
+ # (Supervisor API, ingress proxy, websocket bridge) and surface as a
1962
+ # misleading "addon not found" or 404 from the Supervisor. Reject
1963
+ # up-front so the caller learns the slug was unusable before any
1964
+ # backend call.
1965
+ validate_identifier_not_empty(
1966
+ slug,
1967
+ "slug",
1968
+ suggestions=[
1969
+ "Use ha_get_addon() to discover installed add-on slugs",
1970
+ ],
1971
+ )
1957
1972
  # Build config payload from provided config parameters
1958
1973
  config_data: dict[str, Any] = {}
1959
1974
  if options:
@@ -18,6 +18,7 @@ from .helpers import (
18
18
  log_tool_usage,
19
19
  raise_tool_error,
20
20
  register_tool_methods,
21
+ validate_identifier_not_empty,
21
22
  )
22
23
  from .util_helpers import parse_string_list_param
23
24
 
@@ -465,19 +466,19 @@ class AreaTools:
465
466
  ],
466
467
  ))
467
468
 
468
- # Reject empty-string id explicitly. `if id:` below treats it as
469
- # falsy and would silently route to the create branch — destructive
470
- # if the caller intended an update.
471
- if id == "":
472
- raise_tool_error(create_error_response(
473
- ErrorCode.VALIDATION_INVALID_PARAMETER,
474
- "id must be a non-empty string when provided (omit to create)",
475
- context={"kind": kind},
469
+ # ``None`` stays the documented "create-new" sentinel; explicit
470
+ # empty/whitespace would silently route to the ``if id:`` create
471
+ # branch below and lose update intent.
472
+ if id is not None:
473
+ validate_identifier_not_empty(
474
+ id,
475
+ "id",
476
476
  suggestions=[
477
477
  "Omit id entirely to create a new entry",
478
478
  "Pass a real area_id/floor_id to update an existing entry",
479
479
  ],
480
- ))
480
+ context={"kind": kind},
481
+ )
481
482
 
482
483
  if kind == "area":
483
484
  if id:
@@ -486,13 +487,15 @@ class AreaTools:
486
487
  )
487
488
  operation = "update"
488
489
  else:
489
- if not name:
490
- raise_tool_error(create_error_response(
491
- ErrorCode.VALIDATION_MISSING_PARAMETER,
492
- "name is required when creating a new area",
493
- context={"operation": "create_area"},
494
- suggestions=["Provide a name for the new area"],
495
- ))
490
+ # Reassignment narrows ``name`` from ``str | None`` to
491
+ # ``str`` for the build-message call below.
492
+ name = validate_identifier_not_empty(
493
+ name,
494
+ "name",
495
+ message="name is required when creating a new area",
496
+ context={"operation": "create_area"},
497
+ suggestions=["Provide a non-empty name for the new area"],
498
+ )
496
499
  message = self._build_area_create_message(
497
500
  name, floor_id, icon, parsed_aliases, picture,
498
501
  )
@@ -506,13 +509,13 @@ class AreaTools:
506
509
  )
507
510
  operation = "update"
508
511
  else:
509
- if not name:
510
- raise_tool_error(create_error_response(
511
- ErrorCode.VALIDATION_MISSING_PARAMETER,
512
- "name is required when creating a new floor",
513
- context={"operation": "create_floor"},
514
- suggestions=["Provide a name for the new floor"],
515
- ))
512
+ name = validate_identifier_not_empty(
513
+ name,
514
+ "name",
515
+ message="name is required when creating a new floor",
516
+ context={"operation": "create_floor"},
517
+ suggestions=["Provide a non-empty name for the new floor"],
518
+ )
516
519
  message = self._build_floor_create_message(
517
520
  name, level, icon, parsed_aliases,
518
521
  )
@@ -590,6 +593,15 @@ class AreaTools:
590
593
  registry = "area_registry" if kind == "area" else "floor_registry"
591
594
  id_key = "area_id" if kind == "area" else "floor_id"
592
595
  try:
596
+ # Empty/whitespace would surface as a misleading HA delete-failure.
597
+ validate_identifier_not_empty(
598
+ id,
599
+ "id",
600
+ suggestions=[
601
+ f"Pass a valid {id_key} (use ha_list_floors_areas() to list)",
602
+ ],
603
+ context={"action": "remove", "kind": kind},
604
+ )
593
605
  message: dict[str, Any] = {
594
606
  "type": f"config/{registry}/delete",
595
607
  id_key: id,
@@ -21,6 +21,7 @@ from .helpers import (
21
21
  log_tool_usage,
22
22
  raise_tool_error,
23
23
  register_tool_methods,
24
+ validate_identifier_not_empty,
24
25
  )
25
26
 
26
27
  logger = logging.getLogger(__name__)
@@ -354,6 +355,18 @@ class CalendarTools:
354
355
  ],
355
356
  ))
356
357
 
358
+ # entity_id format-check above does not cover the ``uid`` parameter.
359
+ # Empty/whitespace uid would flow through to ``calendar.delete_event``
360
+ # and HA returns a misleading "event not found".
361
+ validate_identifier_not_empty(
362
+ uid,
363
+ "uid",
364
+ suggestions=[
365
+ "Use ha_config_get_calendar_events() to list events and obtain valid UIDs",
366
+ ],
367
+ context={"entity_id": entity_id},
368
+ )
369
+
357
370
  # Build service data
358
371
  service_data: dict[str, Any] = {
359
372
  "entity_id": entity_id,
@@ -21,6 +21,7 @@ from .helpers import (
21
21
  log_tool_usage,
22
22
  raise_tool_error,
23
23
  register_tool_methods,
24
+ validate_identifier_not_empty,
24
25
  )
25
26
 
26
27
  logger = logging.getLogger(__name__)
@@ -76,6 +77,18 @@ class CategoryTools:
76
77
  Use ha_set_entity(categories={"automation": "category_id"}) to assign categories to entities.
77
78
  """
78
79
  try:
80
+ # ``None`` stays the documented "list-all" sentinel; explicit
81
+ # empty/whitespace is rejected by ``validate_identifier_not_empty``.
82
+ if category_id is not None:
83
+ validate_identifier_not_empty(
84
+ category_id,
85
+ "category_id",
86
+ suggestions=[
87
+ "Omit category_id to list all categories for the scope",
88
+ "Pass a valid non-empty category_id",
89
+ ],
90
+ context={"action": "get", "scope": scope},
91
+ )
79
92
  message: dict[str, Any] = {
80
93
  "type": "config/category_registry/list",
81
94
  "scope": scope,
@@ -194,6 +207,19 @@ class CategoryTools:
194
207
  After creating a category, use ha_set_entity(categories={"automation": "category_id"}) to assign it.
195
208
  """
196
209
  try:
210
+ # ``None`` stays the documented "create-new" sentinel; explicit
211
+ # empty/whitespace is rejected so the create/update discriminator
212
+ # below cannot silently route an intended update to create.
213
+ if category_id is not None:
214
+ validate_identifier_not_empty(
215
+ category_id,
216
+ "category_id",
217
+ suggestions=[
218
+ "Omit category_id entirely to create a new category",
219
+ "Pass a valid category_id to update an existing category",
220
+ ],
221
+ context={"action": "set", "scope": scope, "name": name},
222
+ )
197
223
  action = "update" if category_id else "create"
198
224
 
199
225
  message: dict[str, Any] = {
@@ -282,6 +308,15 @@ class CategoryTools:
282
308
  This action cannot be undone.
283
309
  """
284
310
  try:
311
+ # Empty/whitespace would surface as a misleading HA delete-failure.
312
+ validate_identifier_not_empty(
313
+ category_id,
314
+ "category_id",
315
+ suggestions=[
316
+ "Pass a valid category_id (use ha_config_get_category() to list)",
317
+ ],
318
+ context={"action": "remove", "scope": scope},
319
+ )
285
320
  message: dict[str, Any] = {
286
321
  "type": "config/category_registry/delete",
287
322
  "scope": scope,
@@ -33,6 +33,7 @@ from .helpers import (
33
33
  log_tool_usage,
34
34
  raise_tool_error,
35
35
  register_tool_methods,
36
+ validate_identifier_not_empty,
36
37
  )
37
38
  from .reference_validator import validate_config_references
38
39
  from .util_helpers import (
@@ -965,6 +966,15 @@ class AutomationConfigTools:
965
966
  **WARNING:** Deleting an automation removes it permanently from your Home Assistant configuration.
966
967
  """
967
968
  try:
969
+ # Empty/whitespace would surface as a misleading HA delete-failure.
970
+ validate_identifier_not_empty(
971
+ identifier,
972
+ "identifier",
973
+ suggestions=[
974
+ "Use ha_search_entities(domain_filter='automation') to find existing automations"
975
+ ],
976
+ context={"operation": "remove_automation"},
977
+ )
968
978
  # Resolve entity_id for wait verification (identifier may be a unique_id)
969
979
  entity_id_for_wait = await self._resolve_automation_entity_id(identifier)
970
980
  if not entity_id_for_wait: