ha-mcp-dev 7.4.1.dev446__tar.gz → 7.4.1.dev447__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 (108) hide show
  1. {ha_mcp_dev-7.4.1.dev446/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.4.1.dev447}/PKG-INFO +4 -4
  2. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/README.md +3 -3
  3. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/pyproject.toml +1 -1
  4. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/tools/tools_resources.py +233 -83
  5. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447/src/ha_mcp_dev.egg-info}/PKG-INFO +4 -4
  6. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/LICENSE +0 -0
  7. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/MANIFEST.in +0 -0
  8. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/setup.cfg +0 -0
  9. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/__init__.py +0 -0
  10. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/__main__.py +0 -0
  11. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/_pypi_marker +0 -0
  12. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/_version.py +0 -0
  13. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/auth/__init__.py +0 -0
  14. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/auth/consent_form.py +0 -0
  15. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/auth/provider.py +0 -0
  16. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/client/__init__.py +0 -0
  17. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/client/rest_client.py +0 -0
  18. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/client/websocket_client.py +0 -0
  19. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/client/websocket_listener.py +0 -0
  20. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/config.py +0 -0
  21. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/errors.py +0 -0
  22. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/py.typed +0 -0
  23. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
  24. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
  25. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
  26. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
  27. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
  28. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
  29. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
  30. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
  31. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
  32. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
  33. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
  34. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
  35. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
  36. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
  37. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
  38. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
  39. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
  40. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
  41. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
  42. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
  43. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/server.py +0 -0
  44. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/settings_ui.py +0 -0
  45. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/smoke_test.py +0 -0
  46. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/tools/__init__.py +0 -0
  47. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/tools/backup.py +0 -0
  48. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/tools/best_practice_checker.py +0 -0
  49. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/tools/device_control.py +0 -0
  50. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/tools/enhanced.py +0 -0
  51. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/tools/helpers.py +0 -0
  52. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/tools/reference_validator.py +0 -0
  53. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/tools/registry.py +0 -0
  54. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/tools/smart_search.py +0 -0
  55. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/tools/tools_addons.py +0 -0
  56. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/tools/tools_areas.py +0 -0
  57. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/tools/tools_blueprints.py +0 -0
  58. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/tools/tools_bug_report.py +0 -0
  59. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/tools/tools_calendar.py +0 -0
  60. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/tools/tools_camera.py +0 -0
  61. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/tools/tools_categories.py +0 -0
  62. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/tools/tools_code.py +0 -0
  63. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/tools/tools_config_automations.py +0 -0
  64. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
  65. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/tools/tools_config_entry_flow.py +0 -0
  66. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
  67. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
  68. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/tools/tools_energy.py +0 -0
  69. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/tools/tools_entities.py +0 -0
  70. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/tools/tools_filesystem.py +0 -0
  71. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/tools/tools_groups.py +0 -0
  72. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/tools/tools_hacs.py +0 -0
  73. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/tools/tools_history.py +0 -0
  74. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/tools/tools_integrations.py +0 -0
  75. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/tools/tools_labels.py +0 -0
  76. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/tools/tools_mcp_component.py +0 -0
  77. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/tools/tools_registry.py +0 -0
  78. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/tools/tools_search.py +0 -0
  79. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/tools/tools_service.py +0 -0
  80. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/tools/tools_services.py +0 -0
  81. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/tools/tools_system.py +0 -0
  82. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/tools/tools_todo.py +0 -0
  83. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/tools/tools_traces.py +0 -0
  84. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/tools/tools_updates.py +0 -0
  85. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/tools/tools_utility.py +0 -0
  86. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
  87. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
  88. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/tools/tools_zones.py +0 -0
  89. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/tools/util_helpers.py +0 -0
  90. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/transforms/__init__.py +0 -0
  91. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/transforms/categorized_search.py +0 -0
  92. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/utils/__init__.py +0 -0
  93. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/utils/config_hash.py +0 -0
  94. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/utils/data_paths.py +0 -0
  95. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/utils/domain_handlers.py +0 -0
  96. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/utils/fuzzy_search.py +0 -0
  97. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
  98. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/utils/operation_manager.py +0 -0
  99. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/utils/python_sandbox.py +0 -0
  100. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp/utils/usage_logger.py +0 -0
  101. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
  102. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
  103. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
  104. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
  105. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
  106. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/tests/__init__.py +0 -0
  107. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/tests/test_constants.py +0 -0
  108. {ha_mcp_dev-7.4.1.dev446 → ha_mcp_dev-7.4.1.dev447}/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.4.1.dev446
3
+ Version: 7.4.1.dev447
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-86-blue" alt="95+ Tools">
41
+ <img src="https://img.shields.io/badge/tools-87-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 (86 tools)</b></summary>
184
+ <summary><b>Complete Tool List (87 tools)</b></summary>
185
185
 
186
186
  | Category | Tools |
187
187
  |----------|-------|
@@ -205,7 +205,7 @@ Spend less time configuring, more time enjoying your smart home.
205
205
  | **Scripts** | `ha_config_get_script`, `ha_config_remove_script`, `ha_config_set_script` |
206
206
  | **Search & Discovery** | `ha_deep_search`, `ha_get_overview`, `ha_get_state`, `ha_search_entities` |
207
207
  | **Service & Device Control** | `ha_bulk_control`, `ha_call_service`, `ha_get_operation_status`, `ha_list_services` |
208
- | **System** | `ha_backup_create`, `ha_backup_restore`, `ha_check_config`, `ha_config_set_yaml` *(beta)*, `ha_get_system_health`, `ha_get_updates`, `ha_reload_core`, `ha_restart` |
208
+ | **System** | `ha_backup_create`, `ha_backup_restore`, `ha_check_config`, `ha_config_set_yaml` *(beta)*, `ha_get_system_health`, `ha_get_updates`, `ha_manage_custom_tool` *(beta)*, `ha_reload_core`, `ha_restart` |
209
209
  | **Todo Lists** | `ha_get_todo`, `ha_remove_todo_item`, `ha_set_todo_item` |
210
210
  | **Utilities** | `ha_eval_template`, `ha_install_mcp_tools` *(beta)*, `ha_report_issue` |
211
211
  | **Zones** | `ha_get_zone`, `ha_remove_zone`, `ha_set_zone` |
@@ -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-86-blue" alt="95+ Tools">
11
+ <img src="https://img.shields.io/badge/tools-87-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 (86 tools)</b></summary>
154
+ <summary><b>Complete Tool List (87 tools)</b></summary>
155
155
 
156
156
  | Category | Tools |
157
157
  |----------|-------|
@@ -175,7 +175,7 @@ Spend less time configuring, more time enjoying your smart home.
175
175
  | **Scripts** | `ha_config_get_script`, `ha_config_remove_script`, `ha_config_set_script` |
176
176
  | **Search & Discovery** | `ha_deep_search`, `ha_get_overview`, `ha_get_state`, `ha_search_entities` |
177
177
  | **Service & Device Control** | `ha_bulk_control`, `ha_call_service`, `ha_get_operation_status`, `ha_list_services` |
178
- | **System** | `ha_backup_create`, `ha_backup_restore`, `ha_check_config`, `ha_config_set_yaml` *(beta)*, `ha_get_system_health`, `ha_get_updates`, `ha_reload_core`, `ha_restart` |
178
+ | **System** | `ha_backup_create`, `ha_backup_restore`, `ha_check_config`, `ha_config_set_yaml` *(beta)*, `ha_get_system_health`, `ha_get_updates`, `ha_manage_custom_tool` *(beta)*, `ha_reload_core`, `ha_restart` |
179
179
  | **Todo Lists** | `ha_get_todo`, `ha_remove_todo_item`, `ha_set_todo_item` |
180
180
  | **Utilities** | `ha_eval_template`, `ha_install_mcp_tools` *(beta)*, `ha_report_issue` |
181
181
  | **Zones** | `ha_get_zone`, `ha_remove_zone`, `ha_set_zone` |
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ha-mcp-dev"
7
- version = "7.4.1.dev446"
7
+ version = "7.4.1.dev447"
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"
@@ -36,6 +36,79 @@ MAX_ENCODED_LENGTH = 32000
36
36
  # Base64 encoding increases size by ~33%, so 24KB * 1.33 ≈ 32KB
37
37
  MAX_CONTENT_SIZE = 24000
38
38
 
39
+ # Top-level HA-config YAML keys that LLMs sometimes emit when they pick this
40
+ # tool (`ha_config_set_dashboard_resource`) to "create a scene/automation/...".
41
+ # This tool only stores Lovelace JS/CSS resources — the YAML payload would land
42
+ # as a Lovelace module, creating orphaned, unreachable HA entities (see #1072).
43
+ # Map: top-level key → suggested replacement tool (None where no direct tool
44
+ # exists yet — e.g. scenes are tracked in #995).
45
+ _HA_CONFIG_YAML_MARKERS: dict[str, str | None] = {
46
+ "automation": "ha_config_set_automation",
47
+ "script": "ha_config_set_script",
48
+ "scene": None, # Tracked in #995 — scene CRUD tools not yet shipped
49
+ "group": "ha_config_set_group",
50
+ "input_boolean": "ha_config_set_helper(helper_type='input_boolean', ...)",
51
+ "input_number": "ha_config_set_helper(helper_type='input_number', ...)",
52
+ "input_select": "ha_config_set_helper(helper_type='input_select', ...)",
53
+ "input_text": "ha_config_set_helper(helper_type='input_text', ...)",
54
+ "input_datetime": "ha_config_set_helper(helper_type='input_datetime', ...)",
55
+ "input_button": "ha_config_set_helper(helper_type='input_button', ...)",
56
+ "template": None,
57
+ "homeassistant": None,
58
+ "sensor": None,
59
+ "binary_sensor": None,
60
+ "light": None,
61
+ "switch": None,
62
+ "cover": None,
63
+ "climate": None,
64
+ "media_player": None,
65
+ "notify": None,
66
+ }
67
+
68
+
69
+ def _detect_ha_config_yaml(content: str) -> str | None:
70
+ """Detect HA-config YAML at the start of inline-resource content.
71
+
72
+ Returns the matching top-level key (without the colon) when content's
73
+ first significant line opens an HA-config block, else None. Plain JS/CSS
74
+ never starts with ``<word>:`` followed by whitespace/EOL/YAML-marker —
75
+ JS opens with ``import``/``export``/``const``/``//``/``/*``/``function``
76
+ or similar, CSS with selectors/at-rules/``/*``. False-positive surface
77
+ is therefore narrow.
78
+
79
+ Skips a leading BOM (``\\ufeff``), blank lines, full-line ``#`` comments,
80
+ and YAML doc-start markers (``---``) before reading the first content
81
+ line — these decorations are common in real-world HA YAML files and
82
+ would otherwise let misrouted content slip past the first-line check.
83
+
84
+ See #1072 for the misroute pattern this guards against.
85
+ """
86
+ # `str.strip()` does not remove U+FEFF (BOM) — it is not Unicode-whitespace.
87
+ stripped = content.lstrip("\ufeff")
88
+ first_line = ""
89
+ for raw_line in stripped.splitlines():
90
+ bare = raw_line.strip()
91
+ if not bare or bare == "---" or bare.startswith("#"):
92
+ continue
93
+ first_line = bare
94
+ break
95
+ if not first_line:
96
+ return None
97
+ for domain in _HA_CONFIG_YAML_MARKERS:
98
+ prefix = f"{domain}:"
99
+ if first_line == prefix:
100
+ # Block-form `automation:` exactly — unambiguous.
101
+ return domain
102
+ if first_line.startswith(prefix):
103
+ # `automation: <something>` — only count it as YAML if the char
104
+ # after the colon is whitespace, EOL, or a YAML marker. CSS
105
+ # selectors like `automation:hover` (hypothetical) would have
106
+ # an alpha char after the colon and not match.
107
+ sep = first_line[len(prefix)]
108
+ if sep in (" ", "\t", "|", ">", "-", "[", "{", "#"):
109
+ return domain
110
+ return None
111
+
39
112
 
40
113
  def _encode_content(content: str) -> tuple[str, int, int]:
41
114
  """Encode content to URL-safe base64. Returns (encoded, content_size, encoded_size)."""
@@ -113,7 +186,9 @@ class ResourceTools:
113
186
  management through the UI, but API access works regardless.
114
187
  """
115
188
  try:
116
- result = await self._client.send_websocket_message({"type": "lovelace/resources"})
189
+ result = await self._client.send_websocket_message(
190
+ {"type": "lovelace/resources"}
191
+ )
117
192
 
118
193
  # Handle WebSocket response format
119
194
  if isinstance(result, dict) and "result" in result:
@@ -270,24 +345,28 @@ class ResourceTools:
270
345
  """
271
346
  # Validate: exactly one of content or url must be provided
272
347
  if content is not None and url is not None:
273
- raise_tool_error(create_error_response(
274
- code=ErrorCode.VALIDATION_INVALID_PARAMETER,
275
- message="Provide either 'content' (inline code) or 'url' (external), not both",
276
- suggestions=[
277
- "Use content= for inline JavaScript/CSS code",
278
- "Use url= for /local/, /hacsfiles/, or https:// resources",
279
- ],
280
- ))
348
+ raise_tool_error(
349
+ create_error_response(
350
+ code=ErrorCode.VALIDATION_INVALID_PARAMETER,
351
+ message="Provide either 'content' (inline code) or 'url' (external), not both",
352
+ suggestions=[
353
+ "Use content= for inline JavaScript/CSS code",
354
+ "Use url= for /local/, /hacsfiles/, or https:// resources",
355
+ ],
356
+ )
357
+ )
281
358
 
282
359
  if content is None and url is None:
283
- raise_tool_error(create_error_response(
284
- code=ErrorCode.VALIDATION_INVALID_PARAMETER,
285
- message="Either 'content' (inline code) or 'url' (external) is required",
286
- suggestions=[
287
- "Use content= for inline JavaScript/CSS code",
288
- "Use url= for /local/, /hacsfiles/, or https:// resources",
289
- ],
290
- ))
360
+ raise_tool_error(
361
+ create_error_response(
362
+ code=ErrorCode.VALIDATION_INVALID_PARAMETER,
363
+ message="Either 'content' (inline code) or 'url' (external) is required",
364
+ suggestions=[
365
+ "Use content= for inline JavaScript/CSS code",
366
+ "Use url= for /local/, /hacsfiles/, or https:// resources",
367
+ ],
368
+ )
369
+ )
291
370
 
292
371
  if content is not None:
293
372
  return await self._set_inline_resource(content, resource_type, resource_id)
@@ -301,57 +380,111 @@ class ResourceTools:
301
380
  ) -> dict[str, Any]:
302
381
  """Create or update an inline dashboard resource."""
303
382
  if not content.strip():
304
- raise_tool_error(create_error_response(
305
- code=ErrorCode.VALIDATION_INVALID_PARAMETER,
306
- message="Content cannot be empty",
307
- ))
383
+ raise_tool_error(
384
+ create_error_response(
385
+ code=ErrorCode.VALIDATION_INVALID_PARAMETER,
386
+ message="Content cannot be empty",
387
+ )
388
+ )
308
389
 
309
390
  if resource_type == "js":
310
- raise_tool_error(create_error_response(
311
- code=ErrorCode.VALIDATION_INVALID_PARAMETER,
312
- message="Inline content does not support resource_type='js'",
313
- suggestions=[
314
- "Use resource_type='module' for ES6 JavaScript (recommended)",
315
- "Use url= mode with resource_type='js' for legacy files",
316
- ],
317
- ))
391
+ raise_tool_error(
392
+ create_error_response(
393
+ code=ErrorCode.VALIDATION_INVALID_PARAMETER,
394
+ message="Inline content does not support resource_type='js'",
395
+ suggestions=[
396
+ "Use resource_type='module' for ES6 JavaScript (recommended)",
397
+ "Use url= mode with resource_type='js' for legacy files",
398
+ ],
399
+ )
400
+ )
401
+
402
+ # Catch the misroute where LLMs pick this tool to create a scene /
403
+ # automation / helper / ... by passing HA-config YAML as `content`.
404
+ # The tool only stores Lovelace JS/CSS — YAML lands as a Lovelace
405
+ # module, creating orphaned, unreachable entities. See #1072.
406
+ detected_yaml = _detect_ha_config_yaml(content)
407
+ if detected_yaml is not None:
408
+ right_tool = _HA_CONFIG_YAML_MARKERS[detected_yaml]
409
+ suggestions = ["This tool stores Lovelace JavaScript/CSS resources only"]
410
+ if right_tool:
411
+ suggestions.insert(
412
+ 0,
413
+ f"For `{detected_yaml}:` configuration, use {right_tool} instead",
414
+ )
415
+ elif detected_yaml == "scene":
416
+ suggestions.insert(
417
+ 0,
418
+ "Scene configuration tools are tracked in #995; "
419
+ "until they ship, scenes can only be created via the HA UI",
420
+ )
421
+ else:
422
+ suggestions.insert(
423
+ 0,
424
+ f"No direct tool exists for `{detected_yaml}:` config; "
425
+ "configure it via the HA UI or YAML packages",
426
+ )
427
+ raise_tool_error(
428
+ create_error_response(
429
+ code=ErrorCode.VALIDATION_INVALID_PARAMETER,
430
+ message=(
431
+ f"Content starts with HA-configuration YAML "
432
+ f"(`{detected_yaml}:`) — this tool only accepts Lovelace "
433
+ f"JavaScript or CSS resources, not Home Assistant config "
434
+ f"(see issue #1072)."
435
+ ),
436
+ context={
437
+ "detected_marker": f"{detected_yaml}:",
438
+ "resource_type": resource_type,
439
+ },
440
+ suggestions=suggestions,
441
+ )
442
+ )
318
443
 
319
444
  content_bytes = content.encode("utf-8")
320
445
  content_size = len(content_bytes)
321
446
 
322
447
  if content_size > MAX_CONTENT_SIZE:
323
- raise_tool_error(create_error_response(
324
- code=ErrorCode.VALIDATION_INVALID_PARAMETER,
325
- message=f"Content too large: {content_size:,} bytes (max {MAX_CONTENT_SIZE:,})",
326
- context={"size": content_size},
327
- suggestions=[
328
- "Minify the code to reduce size",
329
- "Split into multiple smaller modules",
330
- "Use url= with a /local/ path for larger files",
331
- ],
332
- ))
448
+ raise_tool_error(
449
+ create_error_response(
450
+ code=ErrorCode.VALIDATION_INVALID_PARAMETER,
451
+ message=f"Content too large: {content_size:,} bytes (max {MAX_CONTENT_SIZE:,})",
452
+ context={"size": content_size},
453
+ suggestions=[
454
+ "Minify the code to reduce size",
455
+ "Split into multiple smaller modules",
456
+ "Use url= with a /local/ path for larger files",
457
+ ],
458
+ )
459
+ )
333
460
 
334
461
  encoded, _, encoded_size = _encode_content(content)
335
462
 
336
463
  if encoded_size > MAX_ENCODED_LENGTH:
337
- raise_tool_error(create_error_response(
338
- code=ErrorCode.VALIDATION_INVALID_PARAMETER,
339
- message=f"Encoded content too large: {encoded_size:,} chars (max {MAX_ENCODED_LENGTH:,})",
340
- context={"size": content_size},
341
- ))
464
+ raise_tool_error(
465
+ create_error_response(
466
+ code=ErrorCode.VALIDATION_INVALID_PARAMETER,
467
+ message=f"Encoded content too large: {encoded_size:,} chars (max {MAX_ENCODED_LENGTH:,})",
468
+ context={"size": content_size},
469
+ )
470
+ )
342
471
 
343
472
  resource_url = f"{WORKER_BASE_URL}/{encoded}?type={resource_type}"
344
473
 
345
474
  try:
346
- result, action = await self._upsert_resource(resource_id, resource_url, resource_type)
475
+ result, action = await self._upsert_resource(
476
+ resource_id, resource_url, resource_type
477
+ )
347
478
 
348
479
  error_msg = _check_ws_error(result)
349
480
  if error_msg:
350
- raise_tool_error(create_error_response(
351
- code=ErrorCode.SERVICE_CALL_FAILED,
352
- message=str(error_msg),
353
- context={"action": action},
354
- ))
481
+ raise_tool_error(
482
+ create_error_response(
483
+ code=ErrorCode.SERVICE_CALL_FAILED,
484
+ message=str(error_msg),
485
+ context={"action": action},
486
+ )
487
+ )
355
488
 
356
489
  new_resource_id = _extract_resource_id(result, resource_id)
357
490
 
@@ -374,7 +507,10 @@ class ResourceTools:
374
507
  logger.error(f"Error setting inline dashboard resource: {e}")
375
508
  exception_to_structured_error(
376
509
  e,
377
- context={"tool": "ha_config_set_dashboard_resource", "action": "update" if resource_id else "create"},
510
+ context={
511
+ "tool": "ha_config_set_dashboard_resource",
512
+ "action": "update" if resource_id else "create",
513
+ },
378
514
  suggestions=[
379
515
  "Ensure Home Assistant is running and accessible",
380
516
  "Check that you have admin permissions",
@@ -389,26 +525,32 @@ class ResourceTools:
389
525
  ) -> dict[str, Any]:
390
526
  """Create or update an external URL dashboard resource."""
391
527
  try:
392
- result, action = await self._upsert_resource(resource_id, url, resource_type)
528
+ result, action = await self._upsert_resource(
529
+ resource_id, url, resource_type
530
+ )
393
531
 
394
532
  error_msg = _check_ws_error(result)
395
533
  if error_msg:
396
534
  error_str = str(error_msg).lower()
397
535
  if "already exists" in error_str or "duplicate" in error_str:
398
- raise_tool_error(create_error_response(
536
+ raise_tool_error(
537
+ create_error_response(
538
+ code=ErrorCode.SERVICE_CALL_FAILED,
539
+ message="Resource with this URL already exists",
540
+ context={"action": action, "url": url},
541
+ suggestions=[
542
+ "Use ha_config_list_dashboard_resources() to find existing resource",
543
+ "Provide resource_id to update the existing resource",
544
+ ],
545
+ )
546
+ )
547
+ raise_tool_error(
548
+ create_error_response(
399
549
  code=ErrorCode.SERVICE_CALL_FAILED,
400
- message="Resource with this URL already exists",
550
+ message=str(error_msg),
401
551
  context={"action": action, "url": url},
402
- suggestions=[
403
- "Use ha_config_list_dashboard_resources() to find existing resource",
404
- "Provide resource_id to update the existing resource",
405
- ],
406
- ))
407
- raise_tool_error(create_error_response(
408
- code=ErrorCode.SERVICE_CALL_FAILED,
409
- message=str(error_msg),
410
- context={"action": action, "url": url},
411
- ))
552
+ )
553
+ )
412
554
 
413
555
  new_resource_id = _extract_resource_id(result, resource_id)
414
556
 
@@ -431,7 +573,11 @@ class ResourceTools:
431
573
  logger.error(f"Error setting dashboard resource: {e}")
432
574
  exception_to_structured_error(
433
575
  e,
434
- context={"tool": "ha_config_set_dashboard_resource", "action": "update" if resource_id else "create", "url": url},
576
+ context={
577
+ "tool": "ha_config_set_dashboard_resource",
578
+ "action": "update" if resource_id else "create",
579
+ "url": url,
580
+ },
435
581
  suggestions=[
436
582
  "Ensure Home Assistant is running and accessible",
437
583
  "Check that you have admin permissions",
@@ -512,24 +658,28 @@ class ResourceTools:
512
658
  if error_msg:
513
659
  error_str = str(error_msg).lower()
514
660
  if "not found" in error_str or "unable to find" in error_str:
515
- raise_tool_error(create_resource_not_found_error(
516
- "Dashboard resource",
517
- resource_id,
518
- details=(
519
- f"Resource '{resource_id}' not found. "
520
- "Use ha_config_list_dashboard_resources() to see available resources."
521
- ),
522
- ))
523
-
524
- raise_tool_error(create_error_response(
525
- code=ErrorCode.SERVICE_CALL_FAILED,
526
- message=f"Failed to delete dashboard resource: {error_msg}",
527
- context={"action": "delete", "resource_id": resource_id},
528
- suggestions=[
529
- "Verify resource ID using ha_config_list_dashboard_resources()",
530
- "Check that you have admin permissions",
531
- ],
532
- ))
661
+ raise_tool_error(
662
+ create_resource_not_found_error(
663
+ "Dashboard resource",
664
+ resource_id,
665
+ details=(
666
+ f"Resource '{resource_id}' not found. "
667
+ "Use ha_config_list_dashboard_resources() to see available resources."
668
+ ),
669
+ )
670
+ )
671
+
672
+ raise_tool_error(
673
+ create_error_response(
674
+ code=ErrorCode.SERVICE_CALL_FAILED,
675
+ message=f"Failed to delete dashboard resource: {error_msg}",
676
+ context={"action": "delete", "resource_id": resource_id},
677
+ suggestions=[
678
+ "Verify resource ID using ha_config_list_dashboard_resources()",
679
+ "Check that you have admin permissions",
680
+ ],
681
+ )
682
+ )
533
683
 
534
684
  logger.info(f"Dashboard resource deleted: id={resource_id}")
535
685
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ha-mcp-dev
3
- Version: 7.4.1.dev446
3
+ Version: 7.4.1.dev447
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-86-blue" alt="95+ Tools">
41
+ <img src="https://img.shields.io/badge/tools-87-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 (86 tools)</b></summary>
184
+ <summary><b>Complete Tool List (87 tools)</b></summary>
185
185
 
186
186
  | Category | Tools |
187
187
  |----------|-------|
@@ -205,7 +205,7 @@ Spend less time configuring, more time enjoying your smart home.
205
205
  | **Scripts** | `ha_config_get_script`, `ha_config_remove_script`, `ha_config_set_script` |
206
206
  | **Search & Discovery** | `ha_deep_search`, `ha_get_overview`, `ha_get_state`, `ha_search_entities` |
207
207
  | **Service & Device Control** | `ha_bulk_control`, `ha_call_service`, `ha_get_operation_status`, `ha_list_services` |
208
- | **System** | `ha_backup_create`, `ha_backup_restore`, `ha_check_config`, `ha_config_set_yaml` *(beta)*, `ha_get_system_health`, `ha_get_updates`, `ha_reload_core`, `ha_restart` |
208
+ | **System** | `ha_backup_create`, `ha_backup_restore`, `ha_check_config`, `ha_config_set_yaml` *(beta)*, `ha_get_system_health`, `ha_get_updates`, `ha_manage_custom_tool` *(beta)*, `ha_reload_core`, `ha_restart` |
209
209
  | **Todo Lists** | `ha_get_todo`, `ha_remove_todo_item`, `ha_set_todo_item` |
210
210
  | **Utilities** | `ha_eval_template`, `ha_install_mcp_tools` *(beta)*, `ha_report_issue` |
211
211
  | **Zones** | `ha_get_zone`, `ha_remove_zone`, `ha_set_zone` |