ha-mcp-dev 7.4.1.dev450__tar.gz → 7.4.1.dev451__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.4.1.dev450/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.4.1.dev451}/PKG-INFO +4 -4
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/README.md +3 -3
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/pyproject.toml +1 -1
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/tools/tools_areas.py +179 -251
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/tools/tools_code.py +2 -2
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/transforms/categorized_search.py +2 -2
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451/src/ha_mcp_dev.egg-info}/PKG-INFO +4 -4
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/LICENSE +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/MANIFEST.in +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/setup.cfg +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/__init__.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/__main__.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/_pypi_marker +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/_version.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/auth/__init__.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/auth/consent_form.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/auth/provider.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/client/__init__.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/client/rest_client.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/client/websocket_client.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/client/websocket_listener.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/config.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/errors.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/py.typed +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/server.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/settings_ui.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/smoke_test.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/tools/__init__.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/tools/backup.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/tools/best_practice_checker.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/tools/device_control.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/tools/enhanced.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/tools/helpers.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/tools/reference_validator.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/tools/registry.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/tools/smart_search.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/tools/tools_addons.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/tools/tools_blueprints.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/tools/tools_bug_report.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/tools/tools_calendar.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/tools/tools_camera.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/tools/tools_categories.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/tools/tools_config_automations.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/tools/tools_config_entry_flow.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/tools/tools_energy.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/tools/tools_entities.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/tools/tools_filesystem.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/tools/tools_groups.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/tools/tools_hacs.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/tools/tools_history.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/tools/tools_integrations.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/tools/tools_labels.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/tools/tools_mcp_component.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/tools/tools_registry.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/tools/tools_resources.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/tools/tools_search.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/tools/tools_service.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/tools/tools_services.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/tools/tools_system.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/tools/tools_todo.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/tools/tools_traces.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/tools/tools_updates.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/tools/tools_utility.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/tools/tools_zones.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/tools/util_helpers.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/transforms/__init__.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/utils/__init__.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/utils/config_hash.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/utils/data_paths.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/utils/domain_handlers.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/utils/fuzzy_search.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/utils/operation_manager.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/utils/python_sandbox.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/utils/usage_logger.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/tests/__init__.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/tests/test_constants.py +0 -0
- {ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/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.
|
|
3
|
+
Version: 7.4.1.dev451
|
|
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-
|
|
41
|
+
<img src="https://img.shields.io/badge/tools-85-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,12 +181,12 @@ 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 (
|
|
184
|
+
<summary><b>Complete Tool List (85 tools)</b></summary>
|
|
185
185
|
|
|
186
186
|
| Category | Tools |
|
|
187
187
|
|----------|-------|
|
|
188
188
|
| **Add-ons** | `ha_get_addon`, `ha_manage_addon` |
|
|
189
|
-
| **Areas & Floors** | `ha_config_list_areas`, `ha_config_list_floors`, `
|
|
189
|
+
| **Areas & Floors** | `ha_config_list_areas`, `ha_config_list_floors`, `ha_list_floors_areas`, `ha_remove_area_or_floor`, `ha_set_area_or_floor` |
|
|
190
190
|
| **Automations** | `ha_config_get_automation`, `ha_config_remove_automation`, `ha_config_set_automation` |
|
|
191
191
|
| **Blueprints** | `ha_get_blueprint`, `ha_import_blueprint` |
|
|
192
192
|
| **Calendar** | `ha_config_get_calendar_events`, `ha_config_remove_calendar_event`, `ha_config_set_calendar_event` |
|
|
@@ -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,12 +151,12 @@ 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
|
|----------|-------|
|
|
158
158
|
| **Add-ons** | `ha_get_addon`, `ha_manage_addon` |
|
|
159
|
-
| **Areas & Floors** | `ha_config_list_areas`, `ha_config_list_floors`, `
|
|
159
|
+
| **Areas & Floors** | `ha_config_list_areas`, `ha_config_list_floors`, `ha_list_floors_areas`, `ha_remove_area_or_floor`, `ha_set_area_or_floor` |
|
|
160
160
|
| **Automations** | `ha_config_get_automation`, `ha_config_remove_automation`, `ha_config_set_automation` |
|
|
161
161
|
| **Blueprints** | `ha_get_blueprint`, `ha_import_blueprint` |
|
|
162
162
|
| **Calendar** | `ha_config_get_calendar_events`, `ha_config_remove_calendar_event`, `ha_config_set_calendar_event` |
|
|
@@ -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.
|
|
7
|
+
version = "7.4.1.dev451"
|
|
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"
|
|
@@ -6,7 +6,7 @@ Home Assistant areas and floors - essential organizational features for smart ho
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import logging
|
|
9
|
-
from typing import Annotated, Any
|
|
9
|
+
from typing import Annotated, Any, Literal
|
|
10
10
|
|
|
11
11
|
from fastmcp.exceptions import ToolError
|
|
12
12
|
from fastmcp.tools import tool
|
|
@@ -168,186 +168,6 @@ class AreaTools:
|
|
|
168
168
|
"Verify WebSocket connection is active",
|
|
169
169
|
])
|
|
170
170
|
|
|
171
|
-
@tool(
|
|
172
|
-
name="ha_config_set_area",
|
|
173
|
-
tags={"Areas & Floors"},
|
|
174
|
-
annotations={"destructiveHint": True, "title": "Create or Update Area"},
|
|
175
|
-
)
|
|
176
|
-
@log_tool_usage
|
|
177
|
-
async def ha_config_set_area(
|
|
178
|
-
self,
|
|
179
|
-
name: Annotated[
|
|
180
|
-
str | None,
|
|
181
|
-
Field(
|
|
182
|
-
description="Name for the area (required for create, optional for update, e.g., 'Living Room', 'Kitchen')",
|
|
183
|
-
default=None,
|
|
184
|
-
),
|
|
185
|
-
] = None,
|
|
186
|
-
area_id: Annotated[
|
|
187
|
-
str | None,
|
|
188
|
-
Field(
|
|
189
|
-
description="Area ID to update (omit to create new area, use ha_config_list_areas to find IDs)",
|
|
190
|
-
default=None,
|
|
191
|
-
),
|
|
192
|
-
] = None,
|
|
193
|
-
floor_id: Annotated[
|
|
194
|
-
str | None,
|
|
195
|
-
Field(
|
|
196
|
-
description="Floor ID to assign this area to (use ha_config_list_floors to find IDs, empty string to remove)",
|
|
197
|
-
default=None,
|
|
198
|
-
),
|
|
199
|
-
] = None,
|
|
200
|
-
icon: Annotated[
|
|
201
|
-
str | None,
|
|
202
|
-
Field(
|
|
203
|
-
description="Material Design Icon (e.g., 'mdi:sofa', 'mdi:bed', empty string to remove)",
|
|
204
|
-
default=None,
|
|
205
|
-
),
|
|
206
|
-
] = None,
|
|
207
|
-
aliases: Annotated[
|
|
208
|
-
str | list[str] | None,
|
|
209
|
-
Field(
|
|
210
|
-
description="Alternative names for voice assistant recognition (e.g., ['lounge', 'family room'], empty list to clear)",
|
|
211
|
-
default=None,
|
|
212
|
-
),
|
|
213
|
-
] = None,
|
|
214
|
-
picture: Annotated[
|
|
215
|
-
str | None,
|
|
216
|
-
Field(
|
|
217
|
-
description="URL to a picture representing the area (empty string to remove)",
|
|
218
|
-
default=None,
|
|
219
|
-
),
|
|
220
|
-
] = None,
|
|
221
|
-
) -> dict[str, Any]:
|
|
222
|
-
"""
|
|
223
|
-
Create or update a Home Assistant area (room).
|
|
224
|
-
|
|
225
|
-
Areas organize entities by physical location for room-based control.
|
|
226
|
-
|
|
227
|
-
Create: provide name only.
|
|
228
|
-
Update: provide area_id (from ha_config_list_areas) plus any fields to change.
|
|
229
|
-
|
|
230
|
-
EXAMPLES:
|
|
231
|
-
ha_config_set_area(name="Kitchen")
|
|
232
|
-
ha_config_set_area(name="Living Room", icon="mdi:sofa")
|
|
233
|
-
ha_config_set_area(area_id="kitchen", name="Kitchen Renamed", floor_id="ground_floor")
|
|
234
|
-
"""
|
|
235
|
-
try:
|
|
236
|
-
# Parse aliases if provided as string
|
|
237
|
-
try:
|
|
238
|
-
parsed_aliases = parse_string_list_param(aliases, "aliases")
|
|
239
|
-
except ValueError as e:
|
|
240
|
-
raise_tool_error(create_error_response(
|
|
241
|
-
ErrorCode.VALIDATION_INVALID_PARAMETER,
|
|
242
|
-
f"Invalid aliases parameter: {e}",
|
|
243
|
-
))
|
|
244
|
-
|
|
245
|
-
# Determine if this is a create or update operation
|
|
246
|
-
if area_id:
|
|
247
|
-
message = self._build_area_update_message(
|
|
248
|
-
area_id, name, floor_id, icon, parsed_aliases, picture,
|
|
249
|
-
)
|
|
250
|
-
operation = "update"
|
|
251
|
-
else:
|
|
252
|
-
if not name:
|
|
253
|
-
raise_tool_error(create_error_response(
|
|
254
|
-
ErrorCode.VALIDATION_MISSING_PARAMETER,
|
|
255
|
-
"name is required when creating a new area",
|
|
256
|
-
context={"operation": "create_area"},
|
|
257
|
-
suggestions=["Provide a name for the new area"],
|
|
258
|
-
))
|
|
259
|
-
message = self._build_area_create_message(
|
|
260
|
-
name, floor_id, icon, parsed_aliases, picture,
|
|
261
|
-
)
|
|
262
|
-
operation = "create"
|
|
263
|
-
|
|
264
|
-
result = await self._client.send_websocket_message(message)
|
|
265
|
-
|
|
266
|
-
if result.get("success"):
|
|
267
|
-
area_data = result.get("result", {})
|
|
268
|
-
area_name = name or area_data.get("name", area_id)
|
|
269
|
-
return {
|
|
270
|
-
"success": True,
|
|
271
|
-
"area": area_data,
|
|
272
|
-
"area_id": area_data.get("area_id", area_id),
|
|
273
|
-
"message": f"Successfully {operation}d area: {area_name}",
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
error = result.get("error", {})
|
|
277
|
-
error_msg = error.get("message", str(error)) if isinstance(error, dict) else str(error)
|
|
278
|
-
ctx: dict[str, Any] = {"operation": operation}
|
|
279
|
-
if name:
|
|
280
|
-
ctx["name"] = name
|
|
281
|
-
if area_id:
|
|
282
|
-
ctx["area_id"] = area_id
|
|
283
|
-
raise_tool_error(create_error_response(
|
|
284
|
-
ErrorCode.SERVICE_CALL_FAILED,
|
|
285
|
-
f"Failed to {operation} area: {error_msg}",
|
|
286
|
-
context=ctx,
|
|
287
|
-
))
|
|
288
|
-
|
|
289
|
-
except ToolError:
|
|
290
|
-
raise
|
|
291
|
-
except Exception as e:
|
|
292
|
-
logger.error(f"Error {operation} area {name!r}: {e}")
|
|
293
|
-
exception_to_structured_error(e, context={"operation": operation, "name": name, "area_id": area_id}, suggestions=[
|
|
294
|
-
"Check Home Assistant connection",
|
|
295
|
-
"For create: Verify the name is unique",
|
|
296
|
-
"For update: Verify the area_id exists using ha_config_list_areas()",
|
|
297
|
-
"If assigning to a floor, verify floor_id exists",
|
|
298
|
-
])
|
|
299
|
-
|
|
300
|
-
@tool(
|
|
301
|
-
name="ha_config_remove_area",
|
|
302
|
-
tags={"Areas & Floors"},
|
|
303
|
-
annotations={"destructiveHint": True, "idempotentHint": True, "title": "Remove Area"},
|
|
304
|
-
)
|
|
305
|
-
@log_tool_usage
|
|
306
|
-
async def ha_config_remove_area(
|
|
307
|
-
self,
|
|
308
|
-
area_id: Annotated[
|
|
309
|
-
str,
|
|
310
|
-
Field(description="Area ID to delete (use ha_config_list_areas to find IDs)"),
|
|
311
|
-
],
|
|
312
|
-
) -> dict[str, Any]:
|
|
313
|
-
"""
|
|
314
|
-
Delete a Home Assistant area.
|
|
315
|
-
|
|
316
|
-
Entities and devices in the area are not deleted, just unassigned.
|
|
317
|
-
May break automations referencing this area.
|
|
318
|
-
"""
|
|
319
|
-
try:
|
|
320
|
-
message: dict[str, Any] = {
|
|
321
|
-
"type": "config/area_registry/delete",
|
|
322
|
-
"area_id": area_id,
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
result = await self._client.send_websocket_message(message)
|
|
326
|
-
|
|
327
|
-
if result.get("success"):
|
|
328
|
-
return {
|
|
329
|
-
"success": True,
|
|
330
|
-
"area_id": area_id,
|
|
331
|
-
"message": f"Successfully deleted area: {area_id}",
|
|
332
|
-
}
|
|
333
|
-
else:
|
|
334
|
-
error = result.get("error", {})
|
|
335
|
-
error_msg = error.get("message", str(error)) if isinstance(error, dict) else str(error)
|
|
336
|
-
raise_tool_error(create_error_response(
|
|
337
|
-
ErrorCode.SERVICE_CALL_FAILED,
|
|
338
|
-
f"Failed to delete area: {error_msg}",
|
|
339
|
-
context={"area_id": area_id},
|
|
340
|
-
))
|
|
341
|
-
|
|
342
|
-
except ToolError:
|
|
343
|
-
raise
|
|
344
|
-
except Exception as e:
|
|
345
|
-
logger.error(f"Error removing area {area_id!r}: {e}")
|
|
346
|
-
exception_to_structured_error(e, context={"area_id": area_id}, suggestions=[
|
|
347
|
-
"Check Home Assistant connection",
|
|
348
|
-
"Verify the area_id exists using ha_config_list_areas()",
|
|
349
|
-
])
|
|
350
|
-
|
|
351
171
|
# ============================================================
|
|
352
172
|
# FLOOR TOOLS
|
|
353
173
|
# ============================================================
|
|
@@ -533,58 +353,88 @@ class AreaTools:
|
|
|
533
353
|
],
|
|
534
354
|
)
|
|
535
355
|
|
|
356
|
+
# ============================================================
|
|
357
|
+
# COMBINED SET / REMOVE
|
|
358
|
+
# ============================================================
|
|
359
|
+
|
|
536
360
|
@tool(
|
|
537
|
-
name="
|
|
361
|
+
name="ha_set_area_or_floor",
|
|
538
362
|
tags={"Areas & Floors"},
|
|
539
|
-
annotations={"destructiveHint": True, "title": "Create or Update Floor"},
|
|
363
|
+
annotations={"destructiveHint": True, "title": "Create or Update Area or Floor"},
|
|
540
364
|
)
|
|
541
365
|
@log_tool_usage
|
|
542
|
-
async def
|
|
366
|
+
async def ha_set_area_or_floor(
|
|
543
367
|
self,
|
|
368
|
+
kind: Annotated[
|
|
369
|
+
Literal["area", "floor"],
|
|
370
|
+
Field(
|
|
371
|
+
description="Which registry to operate on: 'area' for rooms, 'floor' for building levels",
|
|
372
|
+
),
|
|
373
|
+
],
|
|
544
374
|
name: Annotated[
|
|
545
375
|
str | None,
|
|
546
376
|
Field(
|
|
547
|
-
description="Name
|
|
377
|
+
description="Name (required when creating; optional when updating, e.g., 'Living Room', 'Ground Floor')",
|
|
378
|
+
default=None,
|
|
379
|
+
),
|
|
380
|
+
] = None,
|
|
381
|
+
id: Annotated[ # noqa: A002
|
|
382
|
+
str | None,
|
|
383
|
+
Field(
|
|
384
|
+
description="Existing area_id or floor_id to update (omit to create a new entry; use ha_list_floors_areas to find IDs)",
|
|
548
385
|
default=None,
|
|
549
386
|
),
|
|
550
387
|
] = None,
|
|
551
388
|
floor_id: Annotated[
|
|
552
389
|
str | None,
|
|
553
390
|
Field(
|
|
554
|
-
description="Floor
|
|
391
|
+
description="Floor assignment when kind='area' (use empty string to clear). Only valid when kind='area'.",
|
|
555
392
|
default=None,
|
|
556
393
|
),
|
|
557
394
|
] = None,
|
|
558
395
|
level: Annotated[
|
|
559
396
|
int | None,
|
|
560
397
|
Field(
|
|
561
|
-
description="Numeric level
|
|
398
|
+
description="Numeric level when kind='floor' (0=ground, 1=first, -1=basement). Only valid when kind='floor'.",
|
|
562
399
|
default=None,
|
|
563
400
|
),
|
|
564
401
|
] = None,
|
|
565
402
|
icon: Annotated[
|
|
566
403
|
str | None,
|
|
567
404
|
Field(
|
|
568
|
-
description="Material Design Icon (e.g., 'mdi:
|
|
405
|
+
description="Material Design Icon (e.g., 'mdi:sofa', 'mdi:home-floor-1', empty string to remove)",
|
|
569
406
|
default=None,
|
|
570
407
|
),
|
|
571
408
|
] = None,
|
|
572
409
|
aliases: Annotated[
|
|
573
410
|
str | list[str] | None,
|
|
574
411
|
Field(
|
|
575
|
-
description="Alternative names for voice assistant recognition (e.g., ['
|
|
412
|
+
description="Alternative names for voice assistant recognition (e.g., ['lounge'], empty list to clear)",
|
|
413
|
+
default=None,
|
|
414
|
+
),
|
|
415
|
+
] = None,
|
|
416
|
+
picture: Annotated[
|
|
417
|
+
str | None,
|
|
418
|
+
Field(
|
|
419
|
+
description="Picture URL when kind='area' (empty string to remove). Only valid when kind='area'.",
|
|
576
420
|
default=None,
|
|
577
421
|
),
|
|
578
422
|
] = None,
|
|
579
423
|
) -> dict[str, Any]:
|
|
580
|
-
"""
|
|
581
|
-
|
|
424
|
+
"""Create or update a Home Assistant area or floor.
|
|
425
|
+
|
|
426
|
+
Pass kind='area' (with optional floor_id, picture) or kind='floor' (with optional level).
|
|
427
|
+
Provide name only to create a new entry; provide id to update an existing one.
|
|
428
|
+
Cross-kind parameters (e.g., picture under kind='floor') are rejected with VALIDATION_INVALID_PARAMETER.
|
|
582
429
|
|
|
583
|
-
|
|
584
|
-
|
|
430
|
+
EXAMPLES:
|
|
431
|
+
ha_set_area_or_floor(kind="area", name="Kitchen")
|
|
432
|
+
ha_set_area_or_floor(kind="area", id="kitchen", floor_id="ground_floor")
|
|
433
|
+
ha_set_area_or_floor(kind="floor", name="Basement", level=-1)
|
|
434
|
+
ha_set_area_or_floor(kind="floor", id="ground_floor", level=0)
|
|
585
435
|
"""
|
|
436
|
+
operation = "create"
|
|
586
437
|
try:
|
|
587
|
-
# Parse aliases if provided as string
|
|
588
438
|
try:
|
|
589
439
|
parsed_aliases = parse_string_list_param(aliases, "aliases")
|
|
590
440
|
except ValueError as e:
|
|
@@ -593,83 +443,156 @@ class AreaTools:
|
|
|
593
443
|
f"Invalid aliases parameter: {e}",
|
|
594
444
|
))
|
|
595
445
|
|
|
596
|
-
#
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
if not
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
446
|
+
# Reject cross-kind params loudly so silent intent loss can't happen
|
|
447
|
+
# (e.g., kind='floor' with picture='...' previously dropped the picture
|
|
448
|
+
# without a diagnostic).
|
|
449
|
+
cross_kind_params: list[str] = []
|
|
450
|
+
if kind == "area" and level is not None:
|
|
451
|
+
cross_kind_params.append("level")
|
|
452
|
+
elif kind == "floor":
|
|
453
|
+
if floor_id is not None:
|
|
454
|
+
cross_kind_params.append("floor_id")
|
|
455
|
+
if picture is not None:
|
|
456
|
+
cross_kind_params.append("picture")
|
|
457
|
+
if cross_kind_params:
|
|
458
|
+
raise_tool_error(create_error_response(
|
|
459
|
+
ErrorCode.VALIDATION_INVALID_PARAMETER,
|
|
460
|
+
f"Parameter(s) {cross_kind_params} are not valid for kind={kind!r}",
|
|
461
|
+
context={"kind": kind, "invalid_parameters": cross_kind_params},
|
|
462
|
+
suggestions=[
|
|
463
|
+
"For kind='area' use: name, id, floor_id, icon, aliases, picture",
|
|
464
|
+
"For kind='floor' use: name, id, level, icon, aliases",
|
|
465
|
+
],
|
|
466
|
+
))
|
|
467
|
+
|
|
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},
|
|
476
|
+
suggestions=[
|
|
477
|
+
"Omit id entirely to create a new entry",
|
|
478
|
+
"Pass a real area_id/floor_id to update an existing entry",
|
|
479
|
+
],
|
|
480
|
+
))
|
|
481
|
+
|
|
482
|
+
if kind == "area":
|
|
483
|
+
if id:
|
|
484
|
+
message = self._build_area_update_message(
|
|
485
|
+
id, name, floor_id, icon, parsed_aliases, picture,
|
|
486
|
+
)
|
|
487
|
+
operation = "update"
|
|
488
|
+
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
|
+
))
|
|
496
|
+
message = self._build_area_create_message(
|
|
497
|
+
name, floor_id, icon, parsed_aliases, picture,
|
|
498
|
+
)
|
|
499
|
+
operation = "create"
|
|
500
|
+
result_key = "area"
|
|
501
|
+
id_key = "area_id"
|
|
502
|
+
else: # kind == "floor"
|
|
503
|
+
if id:
|
|
504
|
+
message = self._build_floor_update_message(
|
|
505
|
+
id, name, level, icon, parsed_aliases,
|
|
506
|
+
)
|
|
507
|
+
operation = "update"
|
|
508
|
+
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
|
+
))
|
|
516
|
+
message = self._build_floor_create_message(
|
|
517
|
+
name, level, icon, parsed_aliases,
|
|
518
|
+
)
|
|
519
|
+
operation = "create"
|
|
520
|
+
result_key = "floor"
|
|
521
|
+
id_key = "floor_id"
|
|
614
522
|
|
|
615
523
|
result = await self._client.send_websocket_message(message)
|
|
616
524
|
|
|
617
525
|
if result.get("success"):
|
|
618
|
-
|
|
619
|
-
|
|
526
|
+
data = result.get("result", {})
|
|
527
|
+
returned_id = data.get(id_key, id)
|
|
528
|
+
display_name = name or data.get("name", returned_id)
|
|
620
529
|
return {
|
|
621
530
|
"success": True,
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
"
|
|
531
|
+
result_key: data,
|
|
532
|
+
id_key: returned_id,
|
|
533
|
+
"kind": kind,
|
|
534
|
+
"message": f"Successfully {operation}d {kind}: {display_name}",
|
|
625
535
|
}
|
|
626
536
|
|
|
627
537
|
error = result.get("error", {})
|
|
628
538
|
error_msg = error.get("message", str(error)) if isinstance(error, dict) else str(error)
|
|
629
|
-
ctx: dict[str, Any] = {"operation": operation}
|
|
539
|
+
ctx: dict[str, Any] = {"operation": operation, "kind": kind}
|
|
630
540
|
if name:
|
|
631
541
|
ctx["name"] = name
|
|
632
|
-
if
|
|
633
|
-
ctx[
|
|
542
|
+
if id:
|
|
543
|
+
ctx[id_key] = id
|
|
634
544
|
raise_tool_error(create_error_response(
|
|
635
545
|
ErrorCode.SERVICE_CALL_FAILED,
|
|
636
|
-
f"Failed to {operation}
|
|
546
|
+
f"Failed to {operation} {kind}: {error_msg}",
|
|
637
547
|
context=ctx,
|
|
638
548
|
))
|
|
639
549
|
|
|
640
550
|
except ToolError:
|
|
641
551
|
raise
|
|
642
552
|
except Exception as e:
|
|
643
|
-
logger.error(f"Error {operation}
|
|
644
|
-
|
|
553
|
+
logger.error(f"Error {operation} {kind} {name!r}: {e}")
|
|
554
|
+
suggestions = [
|
|
645
555
|
"Check Home Assistant connection",
|
|
646
556
|
"For create: Verify the name is unique",
|
|
647
|
-
"For update: Verify the
|
|
648
|
-
]
|
|
557
|
+
f"For update: Verify the {kind} id exists using ha_list_floors_areas()",
|
|
558
|
+
]
|
|
559
|
+
if kind == "area":
|
|
560
|
+
suggestions.append("If assigning to a floor, verify floor_id exists")
|
|
561
|
+
exception_to_structured_error(
|
|
562
|
+
e,
|
|
563
|
+
context={"operation": operation, "kind": kind, "name": name, "id": id},
|
|
564
|
+
suggestions=suggestions,
|
|
565
|
+
)
|
|
649
566
|
|
|
650
567
|
@tool(
|
|
651
|
-
name="
|
|
568
|
+
name="ha_remove_area_or_floor",
|
|
652
569
|
tags={"Areas & Floors"},
|
|
653
|
-
annotations={"destructiveHint": True, "idempotentHint": True, "title": "Remove Floor"},
|
|
570
|
+
annotations={"destructiveHint": True, "idempotentHint": True, "title": "Remove Area or Floor"},
|
|
654
571
|
)
|
|
655
572
|
@log_tool_usage
|
|
656
|
-
async def
|
|
573
|
+
async def ha_remove_area_or_floor(
|
|
657
574
|
self,
|
|
658
|
-
|
|
575
|
+
kind: Annotated[
|
|
576
|
+
Literal["area", "floor"],
|
|
577
|
+
Field(description="Which registry to delete from: 'area' or 'floor'"),
|
|
578
|
+
],
|
|
579
|
+
id: Annotated[ # noqa: A002
|
|
659
580
|
str,
|
|
660
|
-
Field(description="
|
|
581
|
+
Field(description="Area ID or floor ID to delete (use ha_list_floors_areas to find IDs)"),
|
|
661
582
|
],
|
|
662
583
|
) -> dict[str, Any]:
|
|
663
|
-
"""
|
|
664
|
-
Delete a Home Assistant floor.
|
|
584
|
+
"""Remove a Home Assistant area or floor.
|
|
665
585
|
|
|
666
|
-
|
|
667
|
-
|
|
586
|
+
Removing an area unassigns its entities and devices (the entities and
|
|
587
|
+
devices themselves are not removed). Removing a floor unassigns its
|
|
588
|
+
areas. May break automations referencing the removed area/floor.
|
|
668
589
|
"""
|
|
590
|
+
registry = "area_registry" if kind == "area" else "floor_registry"
|
|
591
|
+
id_key = "area_id" if kind == "area" else "floor_id"
|
|
669
592
|
try:
|
|
670
593
|
message: dict[str, Any] = {
|
|
671
|
-
"type": "config/
|
|
672
|
-
|
|
594
|
+
"type": f"config/{registry}/delete",
|
|
595
|
+
id_key: id,
|
|
673
596
|
}
|
|
674
597
|
|
|
675
598
|
result = await self._client.send_websocket_message(message)
|
|
@@ -677,26 +600,31 @@ class AreaTools:
|
|
|
677
600
|
if result.get("success"):
|
|
678
601
|
return {
|
|
679
602
|
"success": True,
|
|
680
|
-
|
|
681
|
-
"
|
|
603
|
+
id_key: id,
|
|
604
|
+
"kind": kind,
|
|
605
|
+
"message": f"Successfully removed {kind}: {id}",
|
|
682
606
|
}
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
607
|
+
|
|
608
|
+
error = result.get("error", {})
|
|
609
|
+
error_msg = error.get("message", str(error)) if isinstance(error, dict) else str(error)
|
|
610
|
+
raise_tool_error(create_error_response(
|
|
611
|
+
ErrorCode.SERVICE_CALL_FAILED,
|
|
612
|
+
f"Failed to remove {kind}: {error_msg}",
|
|
613
|
+
context={"kind": kind, id_key: id},
|
|
614
|
+
))
|
|
691
615
|
|
|
692
616
|
except ToolError:
|
|
693
617
|
raise
|
|
694
618
|
except Exception as e:
|
|
695
|
-
logger.error(f"Error removing
|
|
696
|
-
exception_to_structured_error(
|
|
697
|
-
|
|
698
|
-
"
|
|
699
|
-
|
|
619
|
+
logger.error(f"Error removing {kind} {id!r}: {e}")
|
|
620
|
+
exception_to_structured_error(
|
|
621
|
+
e,
|
|
622
|
+
context={"kind": kind, id_key: id},
|
|
623
|
+
suggestions=[
|
|
624
|
+
"Check Home Assistant connection",
|
|
625
|
+
f"Verify the {kind} id exists using ha_list_floors_areas()",
|
|
626
|
+
],
|
|
627
|
+
)
|
|
700
628
|
|
|
701
629
|
|
|
702
630
|
def register_area_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
|
|
@@ -182,7 +182,7 @@ _BLOCKED_WS_COMMANDS: frozenset[str] = frozenset({
|
|
|
182
182
|
"config/entity_registry/remove",
|
|
183
183
|
# Floor / label / category registries follow the same rationale as
|
|
184
184
|
# area / device / entity above: each has a wrapping MCP tool
|
|
185
|
-
# (``
|
|
185
|
+
# (``ha_set_area_or_floor``, ``ha_config_set_label``,
|
|
186
186
|
# ``ha_config_set_category``) that performs invariant checks the
|
|
187
187
|
# raw WS command skips.
|
|
188
188
|
"config/floor_registry/create",
|
|
@@ -812,7 +812,7 @@ async def _run_sandboxed_code(
|
|
|
812
812
|
f"WebSocket command {msg_type!r} is blocked from the "
|
|
813
813
|
"sandbox. Use the corresponding wrapping tool via "
|
|
814
814
|
"call_tool (e.g. ha_config_set_dashboard, "
|
|
815
|
-
"
|
|
815
|
+
"ha_set_area_or_floor, ha_update_device, ha_set_entity) "
|
|
816
816
|
"so validation runs."
|
|
817
817
|
)
|
|
818
818
|
}
|
{ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/transforms/categorized_search.py
RENAMED
|
@@ -119,14 +119,14 @@ def _build_proxy_descriptions(search_tool_name: str) -> dict[str, str]:
|
|
|
119
119
|
f"Creates or updates data. Use for any tool that modifies "
|
|
120
120
|
f"state but does not delete/remove resources.\n"
|
|
121
121
|
f"{_PROXY_PARAMS_SUFFIX}\n"
|
|
122
|
-
f'EXAMPLE: ha_call_write_tool(name="
|
|
122
|
+
f'EXAMPLE: ha_call_write_tool(name="ha_set_area_or_floor", arguments={{"kind": "area", "name": "Kitchen"}})'
|
|
123
123
|
),
|
|
124
124
|
"delete": (
|
|
125
125
|
f"Execute a delete/remove tool discovered via {search_tool_name}. "
|
|
126
126
|
f"Permanently removes data. Use for tools that delete or "
|
|
127
127
|
f"remove resources (areas, automations, devices, etc.).\n"
|
|
128
128
|
f"{_PROXY_PARAMS_SUFFIX}\n"
|
|
129
|
-
f'EXAMPLE: ha_call_delete_tool(name="
|
|
129
|
+
f'EXAMPLE: ha_call_delete_tool(name="ha_remove_area_or_floor", arguments={{"kind": "area", "id": "old_area"}})'
|
|
130
130
|
),
|
|
131
131
|
}
|
|
132
132
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ha-mcp-dev
|
|
3
|
-
Version: 7.4.1.
|
|
3
|
+
Version: 7.4.1.dev451
|
|
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-
|
|
41
|
+
<img src="https://img.shields.io/badge/tools-85-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,12 +181,12 @@ 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 (
|
|
184
|
+
<summary><b>Complete Tool List (85 tools)</b></summary>
|
|
185
185
|
|
|
186
186
|
| Category | Tools |
|
|
187
187
|
|----------|-------|
|
|
188
188
|
| **Add-ons** | `ha_get_addon`, `ha_manage_addon` |
|
|
189
|
-
| **Areas & Floors** | `ha_config_list_areas`, `ha_config_list_floors`, `
|
|
189
|
+
| **Areas & Floors** | `ha_config_list_areas`, `ha_config_list_floors`, `ha_list_floors_areas`, `ha_remove_area_or_floor`, `ha_set_area_or_floor` |
|
|
190
190
|
| **Automations** | `ha_config_get_automation`, `ha_config_remove_automation`, `ha_config_set_automation` |
|
|
191
191
|
| **Blueprints** | `ha_get_blueprint`, `ha_import_blueprint` |
|
|
192
192
|
| **Calendar** | `ha_config_get_calendar_events`, `ha_config_remove_calendar_event`, `ha_config_set_calendar_event` |
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/resources/skills-vendor/AGENTS.md
RENAMED
|
File without changes
|
{ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/resources/skills-vendor/CLAUDE.md
RENAMED
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/resources/skills-vendor/LICENSE
RENAMED
|
File without changes
|
{ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/resources/skills-vendor/README.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/tools/best_practice_checker.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/tools/tools_config_automations.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/tools/tools_config_dashboards.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/tools/tools_config_entry_flow.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/tools/tools_config_helpers.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/tools/tools_config_scripts.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/tools/tools_voice_assistant.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp/utils/kill_signal_diagnostics.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp_dev.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{ha_mcp_dev-7.4.1.dev450 → ha_mcp_dev-7.4.1.dev451}/src/ha_mcp_dev.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|