ha-mcp-dev 7.5.0.dev592__tar.gz → 7.5.0.dev594__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.dev592/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.5.0.dev594}/PKG-INFO +3 -3
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/README.md +2 -2
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/pyproject.toml +1 -1
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/policy/value_sources.py +0 -1
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/backup.py +217 -48
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_code.py +115 -115
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_registry.py +9 -9
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594/src/ha_mcp_dev.egg-info}/PKG-INFO +3 -3
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/LICENSE +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/MANIFEST.in +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/setup.cfg +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/__init__.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/__main__.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/_pypi_marker +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/_version.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/auth/__init__.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/auth/consent_form.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/auth/provider.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/backup_manager.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/client/__init__.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/client/rest_client.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/client/supervisor_client.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/client/websocket_client.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/client/websocket_listener.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/config.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/errors.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/policy/__init__.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/policy/approval_queue.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/policy/evaluator.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/policy/handlers.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/policy/middleware.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/policy/model.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/policy/persistence.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/py.typed +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/yaml-only-integrations.md +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/server.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/settings_ui.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/smoke_test.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/__init__.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/auto_backup.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/best_practice_checker.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/device_control.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/enhanced.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/helpers.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/reference_validator.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/registry.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/smart_search.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_addons.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_areas.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_blueprints.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_bug_report.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_calendar.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_camera.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_categories.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_config_automations.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_config_entry_flow.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_energy.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_entities.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_filesystem.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_groups.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_hacs.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_history.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_integrations.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_labels.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_mcp_component.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_resources.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_search.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_service.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_services.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_system.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_todo.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_traces.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_updates.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_utility.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_zones.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/util_helpers.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/transforms/__init__.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/transforms/categorized_search.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/utils/__init__.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/utils/config_hash.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/utils/data_paths.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/utils/domain_handlers.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/utils/fuzzy_search.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/utils/operation_manager.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/utils/python_sandbox.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/utils/usage_logger.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/tests/__init__.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/tests/test_constants.py +0 -0
- {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev594}/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.dev594
|
|
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
|
|
@@ -193,7 +193,7 @@ Spend less time configuring, more time enjoying your smart home.
|
|
|
193
193
|
| **Calendar** | `ha_config_get_calendar_events`, `ha_config_remove_calendar_event`, `ha_config_set_calendar_event` |
|
|
194
194
|
| **Camera** | `ha_get_camera_image` |
|
|
195
195
|
| **Dashboards** | `ha_config_delete_dashboard_resource`, `ha_config_delete_dashboard`, `ha_config_get_dashboard`, `ha_config_list_dashboard_resources`, `ha_config_set_dashboard_resource`, `ha_config_set_dashboard` |
|
|
196
|
-
| **Device Registry** | `ha_get_device`, `ha_remove_device`, `
|
|
196
|
+
| **Device Registry** | `ha_get_device`, `ha_remove_device`, `ha_set_device` |
|
|
197
197
|
| **Energy** | `ha_manage_energy_prefs` |
|
|
198
198
|
| **Entity Registry** | `ha_get_entity_exposure`, `ha_get_entity`, `ha_remove_entity`, `ha_set_entity` |
|
|
199
199
|
| **Files** | `ha_delete_file` *(beta)*, `ha_list_files` *(beta)*, `ha_read_file` *(beta)*, `ha_write_file` *(beta)* |
|
|
@@ -351,6 +351,7 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
|
|
|
351
351
|
- **[@julienld](https://github.com/julienld)** — Project creator.
|
|
352
352
|
- **[@sergeykad](https://github.com/sergeykad)** — Core maintainer.
|
|
353
353
|
- **[@kingpanther13](https://github.com/kingpanther13)** — Core maintainer.
|
|
354
|
+
- **[@Patch76](https://github.com/Patch76)** — Core maintainer.
|
|
354
355
|
|
|
355
356
|
### Contributors
|
|
356
357
|
|
|
@@ -375,7 +376,6 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
|
|
|
375
376
|
- **[@restriction](https://github.com/restriction)** — Responsible disclosure: python_transform sandbox missing call target validation.
|
|
376
377
|
- **[@lcrostarosa](https://github.com/lcrostarosa)** — Diagnostic and health monitoring tools concept (#675), inspiring system/error logs, repairs, and ZHA radio metrics integration.
|
|
377
378
|
- **[@roysha1](https://github.com/roysha1)** — Copilot CLI support in the installation wizard; replaced placeholder logo SVGs with real brand icons on the documentation site.
|
|
378
|
-
- **[@Patch76](https://github.com/Patch76)** — `ha_remove_entity` tool, history/statistics pagination and validation, docs sync automation, docstring guidelines, dashboard tool consolidation.
|
|
379
379
|
- **[@teancom](https://github.com/teancom)** — Fix add-on stats endpoint (`/addons/{slug}/stats`).
|
|
380
380
|
- **[@TomasDJo](https://github.com/TomasDJo)** — Category support for automations, scripts, and scenes.
|
|
381
381
|
- **[@bzelch](https://github.com/bzelch)** — `python_transform` support for automations and scripts.
|
|
@@ -163,7 +163,7 @@ Spend less time configuring, more time enjoying your smart home.
|
|
|
163
163
|
| **Calendar** | `ha_config_get_calendar_events`, `ha_config_remove_calendar_event`, `ha_config_set_calendar_event` |
|
|
164
164
|
| **Camera** | `ha_get_camera_image` |
|
|
165
165
|
| **Dashboards** | `ha_config_delete_dashboard_resource`, `ha_config_delete_dashboard`, `ha_config_get_dashboard`, `ha_config_list_dashboard_resources`, `ha_config_set_dashboard_resource`, `ha_config_set_dashboard` |
|
|
166
|
-
| **Device Registry** | `ha_get_device`, `ha_remove_device`, `
|
|
166
|
+
| **Device Registry** | `ha_get_device`, `ha_remove_device`, `ha_set_device` |
|
|
167
167
|
| **Energy** | `ha_manage_energy_prefs` |
|
|
168
168
|
| **Entity Registry** | `ha_get_entity_exposure`, `ha_get_entity`, `ha_remove_entity`, `ha_set_entity` |
|
|
169
169
|
| **Files** | `ha_delete_file` *(beta)*, `ha_list_files` *(beta)*, `ha_read_file` *(beta)*, `ha_write_file` *(beta)* |
|
|
@@ -321,6 +321,7 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
|
|
|
321
321
|
- **[@julienld](https://github.com/julienld)** — Project creator.
|
|
322
322
|
- **[@sergeykad](https://github.com/sergeykad)** — Core maintainer.
|
|
323
323
|
- **[@kingpanther13](https://github.com/kingpanther13)** — Core maintainer.
|
|
324
|
+
- **[@Patch76](https://github.com/Patch76)** — Core maintainer.
|
|
324
325
|
|
|
325
326
|
### Contributors
|
|
326
327
|
|
|
@@ -345,7 +346,6 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
|
|
|
345
346
|
- **[@restriction](https://github.com/restriction)** — Responsible disclosure: python_transform sandbox missing call target validation.
|
|
346
347
|
- **[@lcrostarosa](https://github.com/lcrostarosa)** — Diagnostic and health monitoring tools concept (#675), inspiring system/error logs, repairs, and ZHA radio metrics integration.
|
|
347
348
|
- **[@roysha1](https://github.com/roysha1)** — Copilot CLI support in the installation wizard; replaced placeholder logo SVGs with real brand icons on the documentation site.
|
|
348
|
-
- **[@Patch76](https://github.com/Patch76)** — `ha_remove_entity` tool, history/statistics pagination and validation, docs sync automation, docstring guidelines, dashboard tool consolidation.
|
|
349
349
|
- **[@teancom](https://github.com/teancom)** — Fix add-on stats endpoint (`/addons/{slug}/stats`).
|
|
350
350
|
- **[@TomasDJo](https://github.com/TomasDJo)** — Category support for automations, scripts, and scenes.
|
|
351
351
|
- **[@bzelch](https://github.com/bzelch)** — `python_transform` support for automations and scripts.
|
|
@@ -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.dev594"
|
|
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"
|
|
@@ -39,7 +39,6 @@ VALUE_SOURCE_REGISTRY: dict[tuple[str, str], str] = {
|
|
|
39
39
|
("ha_get_entity", "args.entity_id"): "ha_entities",
|
|
40
40
|
("ha_get_history", "args.entity_ids"): "ha_entities",
|
|
41
41
|
("ha_remove_entity", "args.entity_id"): "ha_entities",
|
|
42
|
-
("ha_update_device", "args.entity_id"): "ha_entities",
|
|
43
42
|
("ha_get_entity_exposure", "args.entity_id"): "ha_entities",
|
|
44
43
|
("ha_set_integration_enabled", "args.entity_id"): "ha_entities",
|
|
45
44
|
}
|
|
@@ -18,14 +18,14 @@ HA restore path. Each call must explicitly pick its scope.
|
|
|
18
18
|
|
|
19
19
|
import asyncio
|
|
20
20
|
import logging
|
|
21
|
-
from datetime import datetime
|
|
21
|
+
from datetime import UTC, datetime, timedelta
|
|
22
22
|
from typing import TYPE_CHECKING, Annotated, Any, Literal, cast
|
|
23
23
|
|
|
24
24
|
from fastmcp.exceptions import ToolError
|
|
25
25
|
from pydantic import Field
|
|
26
26
|
|
|
27
27
|
from ..backup_manager import get_backup_manager
|
|
28
|
-
from ..client.rest_client import HomeAssistantClient
|
|
28
|
+
from ..client.rest_client import HomeAssistantClient, HomeAssistantError
|
|
29
29
|
from ..client.websocket_client import HomeAssistantWebSocketClient
|
|
30
30
|
from ..config import get_global_settings
|
|
31
31
|
from ..errors import ErrorCode, create_error_response
|
|
@@ -41,8 +41,15 @@ if TYPE_CHECKING:
|
|
|
41
41
|
|
|
42
42
|
logger = logging.getLogger(__name__)
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
# Default poll window for full HA backups. The 120s prior default underfit
|
|
45
|
+
# slow HA instances (#1433: poll loop exited while HA was still in
|
|
46
|
+
# state="create_backup", the wrapper treated that as failure, retried, and
|
|
47
|
+
# produced duplicate backups). 300s covers the long tail without making the
|
|
48
|
+
# happy-path wait noticeable.
|
|
49
|
+
_BACKUP_MAX_WAIT_S = 300
|
|
45
50
|
_BACKUP_POLL_INTERVAL_S = 2
|
|
51
|
+
# Clock-skew tolerance when filtering backup entries by date vs job-start.
|
|
52
|
+
_BACKUP_DATE_FILTER_TOLERANCE_S = 5
|
|
46
53
|
|
|
47
54
|
|
|
48
55
|
def _get_backup_hint_text() -> str:
|
|
@@ -171,6 +178,99 @@ async def _get_backup_password(
|
|
|
171
178
|
return cast(str, default_password)
|
|
172
179
|
|
|
173
180
|
|
|
181
|
+
def _parse_backup_date(raw: Any) -> datetime | None:
|
|
182
|
+
"""Parse an HA backup `date` (ISO-8601, may use `Z` suffix) to a tz-aware
|
|
183
|
+
datetime, returning None on missing/malformed input. Naive timestamps are
|
|
184
|
+
treated as UTC."""
|
|
185
|
+
if not isinstance(raw, str) or not raw:
|
|
186
|
+
return None
|
|
187
|
+
try:
|
|
188
|
+
dt = datetime.fromisoformat(raw)
|
|
189
|
+
except ValueError:
|
|
190
|
+
return None
|
|
191
|
+
if dt.tzinfo is None:
|
|
192
|
+
dt = dt.replace(tzinfo=UTC)
|
|
193
|
+
return dt
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def _build_success_response_if_found(
|
|
197
|
+
info_result: dict[str, Any],
|
|
198
|
+
*,
|
|
199
|
+
name: str,
|
|
200
|
+
backup_job_id: str,
|
|
201
|
+
agent_id: str,
|
|
202
|
+
duration_seconds: int,
|
|
203
|
+
job_start_ts: datetime,
|
|
204
|
+
) -> dict[str, Any] | None:
|
|
205
|
+
"""Return the canonical success-response dict, or None.
|
|
206
|
+
|
|
207
|
+
Single source of truth for "did this backup actually complete cleanly?".
|
|
208
|
+
Called from both the in-loop branch and the post-timeout final check.
|
|
209
|
+
|
|
210
|
+
Returns None unless ALL of:
|
|
211
|
+
|
|
212
|
+
* `result.state == "idle"` AND `result.last_action_event.state == "completed"`
|
|
213
|
+
— list-membership alone is insufficient because HA registers the entry in
|
|
214
|
+
`backups` before compression/encryption finish; claiming success on a
|
|
215
|
+
half-written entry would let callers immediately attempt restore on a
|
|
216
|
+
partial file. The state-gate enforces "fully finalized".
|
|
217
|
+
* The match resolves to *this* job, not a stale prior-run entry with the
|
|
218
|
+
same name (HA does not enforce unique backup names, and the post-timeout
|
|
219
|
+
window is exactly when collisions are likely after a retry). Filters to
|
|
220
|
+
entries with `name == name` AND `date >= job_start_ts - tolerance`,
|
|
221
|
+
then within that fresh set prefers a `last_action_event.backup_id`
|
|
222
|
+
match if HA exposes one, otherwise picks the newest by date.
|
|
223
|
+
"""
|
|
224
|
+
result_block = info_result.get("result") or {}
|
|
225
|
+
state = result_block.get("state")
|
|
226
|
+
last_event = result_block.get("last_action_event") or {}
|
|
227
|
+
if state != "idle" or last_event.get("state") != "completed":
|
|
228
|
+
return None
|
|
229
|
+
|
|
230
|
+
backups = result_block.get("backups") or []
|
|
231
|
+
|
|
232
|
+
# Freshness gate applies uniformly — both the `last_action_event.backup_id`
|
|
233
|
+
# path and the name-only fallback are constrained to entries dated
|
|
234
|
+
# at-or-after the job start. Without this, a concurrent backup (e.g. UI-
|
|
235
|
+
# triggered) updating `last_action_event` between our last in-loop poll
|
|
236
|
+
# and the post-timeout lookup could let us match its entry as if it were
|
|
237
|
+
# ours.
|
|
238
|
+
tolerance = timedelta(seconds=_BACKUP_DATE_FILTER_TOLERANCE_S)
|
|
239
|
+
cutoff = job_start_ts - tolerance
|
|
240
|
+
fresh = [
|
|
241
|
+
b
|
|
242
|
+
for b in backups
|
|
243
|
+
if b.get("name") == name
|
|
244
|
+
and (entry_date := _parse_backup_date(b.get("date"))) is not None
|
|
245
|
+
and entry_date >= cutoff
|
|
246
|
+
]
|
|
247
|
+
if not fresh:
|
|
248
|
+
return None
|
|
249
|
+
|
|
250
|
+
target_id = last_event.get("backup_id")
|
|
251
|
+
authoritative = (
|
|
252
|
+
next((b for b in fresh if b.get("backup_id") == target_id), None)
|
|
253
|
+
if target_id
|
|
254
|
+
else None
|
|
255
|
+
)
|
|
256
|
+
match = authoritative or max(
|
|
257
|
+
fresh,
|
|
258
|
+
key=lambda b: _parse_backup_date(b.get("date")) or job_start_ts,
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
return {
|
|
262
|
+
"success": True,
|
|
263
|
+
"backup_id": match.get("backup_id"),
|
|
264
|
+
"backup_job_id": backup_job_id,
|
|
265
|
+
"name": name,
|
|
266
|
+
"date": match.get("date"),
|
|
267
|
+
"size_bytes": (match.get("agents") or {}).get(agent_id, {}).get("size"),
|
|
268
|
+
"status": "Backup completed successfully",
|
|
269
|
+
"duration_seconds": duration_seconds,
|
|
270
|
+
"note": "Backup uses your Home Assistant's default backup password",
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
|
|
174
274
|
async def _poll_backup_completion(
|
|
175
275
|
ws_client: HomeAssistantWebSocketClient,
|
|
176
276
|
name: str,
|
|
@@ -185,8 +285,18 @@ async def _poll_backup_completion(
|
|
|
185
285
|
``hassio.local`` on Supervised, ``backup.local`` on Core); used to look
|
|
186
286
|
up the per-agent size in the backup-info payload.
|
|
187
287
|
|
|
188
|
-
|
|
288
|
+
On timeout, performs one final ``backup/info`` lookup before raising. If
|
|
289
|
+
that lookup confirms `state=idle` + `event_state=completed` AND finds a
|
|
290
|
+
backup belonging to *this* job (by ``last_action_event.backup_id`` or
|
|
291
|
+
fresh date-window name-match), returns the success response with a
|
|
292
|
+
``warnings`` entry noting the late detection (#1433). Otherwise raises
|
|
293
|
+
``TIMEOUT_OPERATION`` — and if the entry exists but state still indicates
|
|
294
|
+
creation in progress, surfaces ``likely_in_progress=true`` in the error
|
|
295
|
+
context so callers can back off retries instead of compounding duplicates.
|
|
296
|
+
|
|
297
|
+
Raises ToolError on backup failure or final timeout with no completion.
|
|
189
298
|
"""
|
|
299
|
+
job_start_ts = datetime.now(UTC)
|
|
190
300
|
waited = 0
|
|
191
301
|
|
|
192
302
|
while waited < max_wait_seconds:
|
|
@@ -194,61 +304,120 @@ async def _poll_backup_completion(
|
|
|
194
304
|
waited += poll_interval
|
|
195
305
|
|
|
196
306
|
info_result = await ws_client.send_command("backup/info")
|
|
197
|
-
if info_result.get("success"):
|
|
198
|
-
state = info_result.get("result", {}).get("state")
|
|
199
|
-
last_event = info_result.get("result", {}).get("last_action_event", {})
|
|
200
|
-
event_state = last_event.get("state")
|
|
201
|
-
|
|
307
|
+
if not info_result.get("success"):
|
|
202
308
|
logger.debug(
|
|
203
|
-
f"
|
|
309
|
+
f"backup/info returned success=False at waited={waited}s; retrying"
|
|
310
|
+
)
|
|
311
|
+
continue
|
|
312
|
+
|
|
313
|
+
result_block = info_result.get("result") or {}
|
|
314
|
+
last_event = result_block.get("last_action_event") or {}
|
|
315
|
+
event_state = last_event.get("state")
|
|
316
|
+
logger.debug(
|
|
317
|
+
f"Backup state: {result_block.get('state')}, "
|
|
318
|
+
f"event_state: {event_state}, waited: {waited}s"
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
if event_state == "failed":
|
|
322
|
+
raise_tool_error(
|
|
323
|
+
create_error_response(
|
|
324
|
+
ErrorCode.SERVICE_CALL_FAILED,
|
|
325
|
+
"Backup creation failed",
|
|
326
|
+
context={"backup_job_id": backup_job_id},
|
|
327
|
+
)
|
|
204
328
|
)
|
|
205
329
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
330
|
+
response = _build_success_response_if_found(
|
|
331
|
+
info_result,
|
|
332
|
+
name=name,
|
|
333
|
+
backup_job_id=backup_job_id,
|
|
334
|
+
agent_id=agent_id,
|
|
335
|
+
duration_seconds=waited,
|
|
336
|
+
job_start_ts=job_start_ts,
|
|
337
|
+
)
|
|
338
|
+
if response is not None:
|
|
339
|
+
logger.info(f"Backup completed successfully: {response['backup_id']}")
|
|
340
|
+
return response
|
|
341
|
+
# state=idle+completed observed but entry not yet (or not freshly) in
|
|
342
|
+
# the list — same bug class as #1433 one tick earlier; continue polling.
|
|
343
|
+
|
|
344
|
+
logger.info(
|
|
345
|
+
f"Backup did not complete within {max_wait_seconds}s; "
|
|
346
|
+
"performing final backup-list lookup before raising timeout"
|
|
347
|
+
)
|
|
348
|
+
# send_command raises on HA-side failure rather than returning
|
|
349
|
+
# success=false. Treat any `HomeAssistantError` (incl. AuthError,
|
|
350
|
+
# ConnectionError, CommandError) during this best-effort verification
|
|
351
|
+
# as "couldn't verify"; surface the failure in the error context via
|
|
352
|
+
# `verification_error` so the auth-case stops being invisible behind a
|
|
353
|
+
# misleading TIMEOUT_OPERATION. Programming errors (AttributeError,
|
|
354
|
+
# TypeError, KeyError) intentionally propagate.
|
|
355
|
+
verification_error: str | None = None
|
|
356
|
+
likely_in_progress = False
|
|
357
|
+
final_info: dict[str, Any] | None = None
|
|
358
|
+
try:
|
|
359
|
+
final_info = await ws_client.send_command("backup/info")
|
|
360
|
+
except HomeAssistantError as e:
|
|
361
|
+
verification_error = repr(e)
|
|
362
|
+
logger.warning(
|
|
363
|
+
f"Post-timeout backup/info lookup failed ({e!r}); "
|
|
364
|
+
"falling through to TIMEOUT_OPERATION"
|
|
365
|
+
)
|
|
236
366
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
367
|
+
if final_info is not None and final_info.get("success"):
|
|
368
|
+
response = _build_success_response_if_found(
|
|
369
|
+
final_info,
|
|
370
|
+
name=name,
|
|
371
|
+
backup_job_id=backup_job_id,
|
|
372
|
+
agent_id=agent_id,
|
|
373
|
+
duration_seconds=max_wait_seconds,
|
|
374
|
+
job_start_ts=job_start_ts,
|
|
375
|
+
)
|
|
376
|
+
if response is not None:
|
|
377
|
+
response["warnings"] = [
|
|
378
|
+
f"Backup completion observed only after the {max_wait_seconds}s "
|
|
379
|
+
"poll window — the operation succeeded but took longer than "
|
|
380
|
+
"expected. Increase max_wait_seconds if this recurs.",
|
|
381
|
+
]
|
|
382
|
+
logger.info(
|
|
383
|
+
f"Backup found in post-timeout list lookup: {response['backup_id']}"
|
|
384
|
+
)
|
|
385
|
+
return response
|
|
386
|
+
# Helper returned None. Three cases distinguishable from the raw
|
|
387
|
+
# final_info: (a) backup failed in the gap between last in-loop poll
|
|
388
|
+
# and the final lookup → raise SERVICE_CALL_FAILED, the failure mode
|
|
389
|
+
# is known and unambiguous; (b) state still indicates creation in
|
|
390
|
+
# progress → surface `likely_in_progress` so callers back off retries
|
|
391
|
+
# rather than compounding duplicates; (c) state idle+completed but no
|
|
392
|
+
# fresh matching entry → genuine TIMEOUT_OPERATION, nothing extra to
|
|
393
|
+
# add.
|
|
394
|
+
result_block = final_info.get("result") or {}
|
|
395
|
+
last_event = result_block.get("last_action_event") or {}
|
|
396
|
+
state = result_block.get("state")
|
|
397
|
+
event_state = last_event.get("state")
|
|
398
|
+
if event_state == "failed":
|
|
399
|
+
raise_tool_error(
|
|
400
|
+
create_error_response(
|
|
401
|
+
ErrorCode.SERVICE_CALL_FAILED,
|
|
402
|
+
"Backup creation failed (observed at post-timeout lookup)",
|
|
403
|
+
context={"backup_job_id": backup_job_id, "name": name},
|
|
244
404
|
)
|
|
405
|
+
)
|
|
406
|
+
if state != "idle":
|
|
407
|
+
likely_in_progress = True
|
|
245
408
|
|
|
246
409
|
logger.warning(f"Backup did not complete within {max_wait_seconds} seconds")
|
|
410
|
+
error_context: dict[str, Any] = {"backup_job_id": backup_job_id, "name": name}
|
|
411
|
+
if verification_error is not None:
|
|
412
|
+
error_context["verification_error"] = verification_error
|
|
413
|
+
if likely_in_progress:
|
|
414
|
+
error_context["likely_in_progress"] = True
|
|
415
|
+
|
|
247
416
|
raise_tool_error(
|
|
248
417
|
create_error_response(
|
|
249
418
|
ErrorCode.TIMEOUT_OPERATION,
|
|
250
419
|
f"Backup creation timed out after {max_wait_seconds} seconds",
|
|
251
|
-
context=
|
|
420
|
+
context=error_context,
|
|
252
421
|
suggestions=[
|
|
253
422
|
"Backup may still be in progress. Check Home Assistant backup status."
|
|
254
423
|
],
|