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