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.
- {ha_mcp_dev-7.3.0.dev382/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.3.0.dev384}/PKG-INFO +4 -4
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/README.md +3 -3
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/pyproject.toml +1 -1
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/__main__.py +6 -3
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/client/rest_client.py +11 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/client/websocket_client.py +6 -5
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/server.py +7 -3
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/helpers.py +49 -2
- ha_mcp_dev-7.3.0.dev384/src/ha_mcp/tools/tools_config_entry_flow.py +539 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/tools_config_helpers.py +460 -59
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/tools_integrations.py +1 -1
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/tools_yaml_config.py +5 -5
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384/src/ha_mcp_dev.egg-info}/PKG-INFO +4 -4
- ha_mcp_dev-7.3.0.dev382/src/ha_mcp/tools/tools_config_entry_flow.py +0 -574
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/LICENSE +0 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/MANIFEST.in +0 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/setup.cfg +0 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/__init__.py +0 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/_pypi_marker +0 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/auth/__init__.py +0 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/auth/consent_form.py +0 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/auth/provider.py +0 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/client/__init__.py +0 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/client/websocket_listener.py +0 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/config.py +0 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/errors.py +0 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/py.typed +0 -0
- {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
- {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
- {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
- {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
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/smoke_test.py +0 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/__init__.py +0 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/backup.py +0 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/best_practice_checker.py +0 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/device_control.py +0 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/enhanced.py +0 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/registry.py +0 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/smart_search.py +0 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/tools_addons.py +0 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/tools_areas.py +0 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/tools_blueprints.py +0 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/tools_bug_report.py +0 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/tools_calendar.py +0 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/tools_camera.py +0 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/tools_categories.py +0 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/tools_config_automations.py +0 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/tools_entities.py +0 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/tools_filesystem.py +0 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/tools_groups.py +0 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/tools_hacs.py +0 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/tools_history.py +0 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/tools_labels.py +0 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/tools_mcp_component.py +0 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/tools_registry.py +0 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/tools_resources.py +0 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/tools_search.py +0 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/tools_service.py +0 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/tools_services.py +0 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/tools_system.py +0 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/tools_todo.py +0 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/tools_traces.py +0 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/tools_updates.py +0 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/tools_utility.py +0 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/tools_zones.py +0 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/tools/util_helpers.py +0 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/transforms/__init__.py +0 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/transforms/categorized_search.py +0 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/utils/__init__.py +0 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/utils/config_hash.py +0 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/utils/domain_handlers.py +0 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/utils/fuzzy_search.py +0 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/utils/operation_manager.py +0 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/utils/python_sandbox.py +0 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp/utils/usage_logger.py +0 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
- {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
- {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
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
- {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
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/tests/__init__.py +0 -0
- {ha_mcp_dev-7.3.0.dev382 → ha_mcp_dev-7.3.0.dev384}/tests/test_constants.py +0 -0
- {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.
|
|
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-
|
|
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 (
|
|
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
|
|
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-
|
|
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 (
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
356
|
-
"
|
|
357
|
-
"schedule zone
|
|
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
|
-
|
|
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
|
|
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
|