ha-mcp-dev 7.3.0.dev382__tar.gz → 7.3.0.dev384__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 (102) hide show
  1. {ha_mcp_dev-7.3.0.dev382/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.3.0.dev384}/PKG-INFO +4 -4
  2. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/README.md +3 -3
  3. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/pyproject.toml +1 -1
  4. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/__main__.py +6 -3
  5. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/client/rest_client.py +11 -0
  6. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/client/websocket_client.py +6 -5
  7. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/server.py +7 -3
  8. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/helpers.py +49 -2
  9. ha_mcp_dev-7.3.0.dev384/src/ha_mcp/tools/tools_config_entry_flow.py +539 -0
  10. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/tools_config_helpers.py +460 -59
  11. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/tools_integrations.py +1 -1
  12. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/tools_yaml_config.py +5 -5
  13. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384/src/ha_mcp_dev.egg-info}/PKG-INFO +4 -4
  14. ha_mcp_dev-7.3.0.dev382/src/ha_mcp/tools/tools_config_entry_flow.py +0 -574
  15. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/LICENSE +0 -0
  16. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/MANIFEST.in +0 -0
  17. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/setup.cfg +0 -0
  18. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/__init__.py +0 -0
  19. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/_pypi_marker +0 -0
  20. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/auth/__init__.py +0 -0
  21. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/auth/consent_form.py +0 -0
  22. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/auth/provider.py +0 -0
  23. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/client/__init__.py +0 -0
  24. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/client/websocket_listener.py +0 -0
  25. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/config.py +0 -0
  26. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/errors.py +0 -0
  27. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/py.typed +0 -0
  28. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
  29. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
  30. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
  31. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
  32. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
  33. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
  34. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
  35. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
  36. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
  37. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
  38. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
  39. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
  40. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
  41. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
  42. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
  43. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
  44. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
  45. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
  46. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
  47. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
  48. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/smoke_test.py +0 -0
  49. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/__init__.py +0 -0
  50. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/backup.py +0 -0
  51. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/best_practice_checker.py +0 -0
  52. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/device_control.py +0 -0
  53. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/enhanced.py +0 -0
  54. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/registry.py +0 -0
  55. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/smart_search.py +0 -0
  56. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/tools_addons.py +0 -0
  57. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/tools_areas.py +0 -0
  58. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/tools_blueprints.py +0 -0
  59. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/tools_bug_report.py +0 -0
  60. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/tools_calendar.py +0 -0
  61. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/tools_camera.py +0 -0
  62. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/tools_categories.py +0 -0
  63. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/tools_config_automations.py +0 -0
  64. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
  65. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
  66. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/tools_entities.py +0 -0
  67. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/tools_filesystem.py +0 -0
  68. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/tools_groups.py +0 -0
  69. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/tools_hacs.py +0 -0
  70. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/tools_history.py +0 -0
  71. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/tools_labels.py +0 -0
  72. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/tools_mcp_component.py +0 -0
  73. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/tools_registry.py +0 -0
  74. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/tools_resources.py +0 -0
  75. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/tools_search.py +0 -0
  76. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/tools_service.py +0 -0
  77. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/tools_services.py +0 -0
  78. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/tools_system.py +0 -0
  79. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/tools_todo.py +0 -0
  80. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/tools_traces.py +0 -0
  81. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/tools_updates.py +0 -0
  82. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/tools_utility.py +0 -0
  83. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
  84. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/tools_zones.py +0 -0
  85. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/util_helpers.py +0 -0
  86. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/transforms/__init__.py +0 -0
  87. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/transforms/categorized_search.py +0 -0
  88. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/utils/__init__.py +0 -0
  89. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/utils/config_hash.py +0 -0
  90. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/utils/domain_handlers.py +0 -0
  91. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/utils/fuzzy_search.py +0 -0
  92. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/utils/operation_manager.py +0 -0
  93. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/utils/python_sandbox.py +0 -0
  94. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/utils/usage_logger.py +0 -0
  95. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
  96. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
  97. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
  98. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
  99. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
  100. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/tests/__init__.py +0 -0
  101. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/tests/test_constants.py +0 -0
  102. {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/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.3.0.dev382
3
+ Version: 7.3.0.dev384
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-86-blue" alt="95+ Tools">
40
+ <img src="https://img.shields.io/badge/tools-85-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>
@@ -180,7 +180,7 @@ Spend less time configuring, more time enjoying your smart home.
180
180
  <details>
181
181
  <!-- TOOLS_TABLE_START -->
182
182
 
183
- <summary><b>Complete Tool List (86 tools)</b></summary>
183
+ <summary><b>Complete Tool List (85 tools)</b></summary>
184
184
 
185
185
  | Category | Tools |
186
186
  |----------|-------|
@@ -196,7 +196,7 @@ Spend less time configuring, more time enjoying your smart home.
196
196
  | **Files** | `ha_delete_file`, `ha_list_files`, `ha_read_file`, `ha_write_file` |
197
197
  | **Groups** | `ha_config_list_groups`, `ha_config_remove_group`, `ha_config_set_group` |
198
198
  | **HACS** | `ha_hacs_add_repository`, `ha_hacs_download`, `ha_hacs_repository_info`, `ha_hacs_search` |
199
- | **Helper Entities** | `ha_config_list_helpers`, `ha_config_remove_helper`, `ha_config_set_helper`, `ha_get_helper_schema`, `ha_set_config_entry_helper` |
199
+ | **Helper Entities** | `ha_config_list_helpers`, `ha_config_remove_helper`, `ha_config_set_helper`, `ha_get_helper_schema` |
200
200
  | **History & Statistics** | `ha_get_automation_traces`, `ha_get_history`, `ha_get_logs` |
201
201
  | **Integrations** | `ha_delete_config_entry`, `ha_get_integration`, `ha_set_integration_enabled` |
202
202
  | **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-86-blue" alt="95+ Tools">
11
+ <img src="https://img.shields.io/badge/tools-85-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 (85 tools)</b></summary>
155
155
 
156
156
  | Category | Tools |
157
157
  |----------|-------|
@@ -167,7 +167,7 @@ Spend less time configuring, more time enjoying your smart home.
167
167
  | **Files** | `ha_delete_file`, `ha_list_files`, `ha_read_file`, `ha_write_file` |
168
168
  | **Groups** | `ha_config_list_groups`, `ha_config_remove_group`, `ha_config_set_group` |
169
169
  | **HACS** | `ha_hacs_add_repository`, `ha_hacs_download`, `ha_hacs_repository_info`, `ha_hacs_search` |
170
- | **Helper Entities** | `ha_config_list_helpers`, `ha_config_remove_helper`, `ha_config_set_helper`, `ha_get_helper_schema`, `ha_set_config_entry_helper` |
170
+ | **Helper Entities** | `ha_config_list_helpers`, `ha_config_remove_helper`, `ha_config_set_helper`, `ha_get_helper_schema` |
171
171
  | **History & Statistics** | `ha_get_automation_traces`, `ha_get_history`, `ha_get_logs` |
172
172
  | **Integrations** | `ha_delete_config_entry`, `ha_get_integration`, `ha_set_integration_enabled` |
173
173
  | **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.3.0.dev382"
7
+ version = "7.3.0.dev384"
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"
@@ -62,14 +62,17 @@ class OAuthProxyClient:
62
62
  """Get the OAuth client for the current request context."""
63
63
  from fastmcp.server.dependencies import get_access_token
64
64
 
65
- from ha_mcp.client.rest_client import HomeAssistantClient
65
+ from ha_mcp.client.rest_client import (
66
+ HomeAssistantAuthError,
67
+ HomeAssistantClient,
68
+ )
66
69
 
67
70
  # Get the access token from the current request context
68
71
  token = get_access_token()
69
72
 
70
73
  if not token:
71
74
  logger.warning("No access token in context")
72
- raise RuntimeError("No OAuth token in request context")
75
+ raise HomeAssistantAuthError("No OAuth token in request context")
73
76
 
74
77
  # Extract HA token from claims (URL is server-side config)
75
78
  claims = token.claims
@@ -78,7 +81,7 @@ class OAuthProxyClient:
78
81
  logger.error(
79
82
  f"OAuth token missing HA credentials. Keys present: {list(claims.keys()) if claims else []}"
80
83
  )
81
- raise RuntimeError("No Home Assistant credentials in OAuth token claims")
84
+ raise HomeAssistantAuthError("No Home Assistant credentials in OAuth token claims")
82
85
 
83
86
  ha_token = claims["ha_token"]
84
87
 
@@ -43,6 +43,17 @@ class HomeAssistantAPIError(HomeAssistantError):
43
43
  self.response_data = response_data
44
44
 
45
45
 
46
+ class HomeAssistantCommandError(HomeAssistantError):
47
+ """WebSocket command returned success=False.
48
+
49
+ Raised by ``WebSocketClient.send_command`` when Home Assistant responds
50
+ with ``{type: "result", success: False}``. Used as a type marker in
51
+ ``_classify_exception``'s match dispatch; classification then falls
52
+ through to ``_classify_by_message`` for pattern matching on the
53
+ error message.
54
+ """
55
+
56
+
46
57
  class HomeAssistantClient:
47
58
  """Authenticated HTTP client for Home Assistant API."""
48
59
 
@@ -20,6 +20,7 @@ from urllib.parse import urlparse
20
20
  import websockets
21
21
 
22
22
  from ..config import get_global_settings
23
+ from .rest_client import HomeAssistantCommandError, HomeAssistantConnectionError
23
24
 
24
25
  logger = logging.getLogger(__name__)
25
26
 
@@ -218,7 +219,7 @@ class HomeAssistantWebSocketClient:
218
219
  message_type="auth_required", timeout=5
219
220
  )
220
221
  if not auth_msg:
221
- raise Exception("Did not receive auth_required message")
222
+ raise HomeAssistantConnectionError("Did not receive auth_required message")
222
223
 
223
224
  # Send authentication
224
225
  await self._send_auth()
@@ -411,7 +412,7 @@ class HomeAssistantWebSocketClient:
411
412
  Response from Home Assistant
412
413
  """
413
414
  if not self._state.is_ready:
414
- raise Exception("WebSocket not authenticated")
415
+ raise HomeAssistantConnectionError("WebSocket not authenticated")
415
416
 
416
417
  message_id = self.get_next_message_id()
417
418
  message = {"id": message_id, "type": command_type, **kwargs}
@@ -439,7 +440,7 @@ class HomeAssistantWebSocketClient:
439
440
  if isinstance(error, dict)
440
441
  else str(error)
441
442
  )
442
- raise Exception(f"Command failed: {error_msg}")
443
+ raise HomeAssistantCommandError(f"Command failed: {error_msg}")
443
444
 
444
445
  # Return success response according to HA WebSocket format
445
446
  return {
@@ -484,7 +485,7 @@ class HomeAssistantWebSocketClient:
484
485
  A (result_response, event_response) tuple.
485
486
  """
486
487
  if not self._state.is_ready:
487
- raise Exception("WebSocket not authenticated")
488
+ raise HomeAssistantConnectionError("WebSocket not authenticated")
488
489
 
489
490
  message_id = self.get_next_message_id()
490
491
  message = {"id": message_id, "type": command_type, **kwargs}
@@ -516,7 +517,7 @@ class HomeAssistantWebSocketClient:
516
517
  if isinstance(error, dict)
517
518
  else str(error)
518
519
  )
519
- raise Exception(f"Command failed: {error_msg}")
520
+ raise HomeAssistantCommandError(f"Command failed: {error_msg}")
520
521
 
521
522
  try:
522
523
  event_response = await asyncio.wait_for(
@@ -351,10 +351,14 @@ class HomeAssistantSmartMCPServer(EnhancedToolsMixin):
351
351
  "conditions actions get show detail"
352
352
  ),
353
353
  # s09: "create helper" → ha_config_set_helper should outrank remove_helper
354
+ # Covers all 27 helper types (12 simple + 15 flow-based, unified in #967).
354
355
  "ha_config_set_helper": (
355
- "create new add helper input_boolean input_number input_text "
356
- "counter timer input_datetime input_select input_button "
357
- "schedule zone group min_max"
356
+ "create update new add helper "
357
+ "input_boolean input_button input_number input_text input_datetime "
358
+ "input_select counter timer schedule zone person tag "
359
+ "template group utility_meter derivative min_max threshold "
360
+ "integration statistics trend random filter tod "
361
+ "generic_thermostat switch_as_x generic_hygrostat"
358
362
  ),
359
363
  # Boost tools that compete with ha_deep_search for common queries
360
364
  "ha_config_get_script": (
@@ -7,6 +7,7 @@ Centralized utilities that can be shared across multiple tool implementations.
7
7
  import functools
8
8
  import json
9
9
  import logging
10
+ import re
10
11
  import sys
11
12
  import time
12
13
  from typing import Any, Literal, NoReturn, overload
@@ -16,6 +17,7 @@ from fastmcp.exceptions import ToolError
16
17
  from ..client.rest_client import (
17
18
  HomeAssistantAPIError,
18
19
  HomeAssistantAuthError,
20
+ HomeAssistantCommandError,
19
21
  HomeAssistantConnectionError,
20
22
  )
21
23
  from ..client.websocket_client import HomeAssistantWebSocketClient
@@ -142,6 +144,14 @@ def _classify_exception(
142
144
  )
143
145
  case HomeAssistantAPIError():
144
146
  result = _classify_api_status(error, error_msg, context)
147
+ case HomeAssistantCommandError():
148
+ # WebSocket command-failure. The ``error.code`` on Supervisor
149
+ # calls routed through HA Core's hassio WS bridge is always
150
+ # ``unknown_error`` (see homeassistant/components/hassio/
151
+ # websocket_api.py), so discrimination must come from the
152
+ # message. Fall through to ``_classify_by_message`` which
153
+ # pattern-matches schema, auth, not-found and timeout cases.
154
+ result = None
145
155
  case TimeoutError():
146
156
  operation = context.get("operation", "request") if context else "request"
147
157
  timeout_seconds = context.get("timeout_seconds", 30) if context else 30
@@ -163,7 +173,29 @@ def _classify_by_message(
163
173
  ) -> dict[str, Any]:
164
174
  """Classify exception by error message patterns."""
165
175
  result: dict[str, Any]
166
- if "not found" in error_str or "404" in error_str:
176
+ # Schema-branch must precede the "not found" / 404 branch (most-specific-first):
177
+ # a vol.Invalid message phrased like "Command failed: key X not found" would
178
+ # otherwise misclassify as RESOURCE_NOT_FOUND. The "command failed:" prefix
179
+ # gates the branch so non-schema WS errors fall through.
180
+ if "command failed:" in error_str and (
181
+ any(
182
+ marker in error_str
183
+ for marker in (
184
+ "missing option",
185
+ "extra keys not allowed",
186
+ "unknown secret",
187
+ "unknown type",
188
+ )
189
+ )
190
+ or re.search(r"expected (?:a |str|int|bool|dict|list|float|type|one of)", error_str)
191
+ ):
192
+ # Supervisor schema validation: vol.Invalid message arriving as a
193
+ # HomeAssistantCommandError via HA Core's hassio WS bridge. The
194
+ # markers plus the "expected <type>" anchor regex cover the
195
+ # heterogeneous vol.Invalid vocabulary without relying on an
196
+ # error code (always unknown_error from the bridge).
197
+ result = create_validation_error(error_msg, context=context)
198
+ elif "not found" in error_str or "404" in error_str:
167
199
  entity_id = context.get("entity_id") if context else None
168
200
  if entity_id:
169
201
  result = create_entity_not_found_error(entity_id, details=error_msg)
@@ -173,8 +205,23 @@ def _classify_by_message(
173
205
  result = create_timeout_error("operation", 30, details=error_msg, context=context)
174
206
  elif "connection" in error_str or "connect" in error_str:
175
207
  result = create_connection_error(error_msg, context=context)
176
- elif "auth" in error_str or "token" in error_str or "401" in error_str:
208
+ elif any(
209
+ phrase in error_str
210
+ for phrase in (
211
+ "unauthorized",
212
+ "authentication",
213
+ "invalid token",
214
+ "access denied",
215
+ )
216
+ ) or "401" in error_str:
177
217
  result = create_auth_error(error_msg, context=context)
218
+ elif error_str.startswith("command failed:"):
219
+ # HomeAssistantCommandError fallback: WS ``success=False`` with a
220
+ # message that doesn't match any specific marker above. This is a
221
+ # known failure mode (the WS command itself failed), not an
222
+ # unexpected internal error — route to SERVICE_CALL_FAILED,
223
+ # mirroring the 4xx fallback in _classify_api_status.
224
+ result = create_error_response(ErrorCode.SERVICE_CALL_FAILED, error_msg, context=context)
178
225
  else:
179
226
  result = create_error_response(
180
227
  ErrorCode.INTERNAL_ERROR, "An unexpected error occurred", details=error_msg, context=context