ha-mcp-dev 7.9.0.dev759__tar.gz → 7.9.0.dev760__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.9.0.dev759/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.9.0.dev760}/PKG-INFO +1 -1
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/pyproject.toml +1 -1
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/read_only.py +18 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/best_practice_checker.py +73 -2
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/tools_config_automations.py +2 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/tools_traces.py +7 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/tools_updates.py +254 -56
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/update_check.py +2 -2
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/utils/domain_handlers.py +10 -1
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/LICENSE +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/MANIFEST.in +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/README.md +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/setup.cfg +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/__init__.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/__main__.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/_pypi_marker +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/_version.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/auth/__init__.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/auth/consent_form.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/auth/provider.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/backup_manager.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/client/__init__.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/client/rest_client.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/client/supervisor_client.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/client/websocket_client.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/client/websocket_listener.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/config.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/dashboard_screenshot/__init__.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/dashboard_screenshot/capture.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/dashboard_screenshot/provision.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/errors.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/policy/__init__.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/policy/approval_queue.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/policy/evaluator.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/policy/handlers.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/policy/middleware.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/policy/model.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/policy/persistence.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/policy/value_sources.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/py.typed +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/appdaemon.md +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/scenes.md +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/yaml-only-integrations.md +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/server.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/settings_ui/__init__.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/settings_ui/settings.css +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/settings_ui/settings.html +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/settings_ui/settings.js +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/smoke_test.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/__init__.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/auto_backup.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/backup.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/config_entry_flow.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/device_control.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/enhanced.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/helpers.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/radio/__init__.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/radio/base.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/radio/matter.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/radio/thread.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/radio/zigbee.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/radio/zwave.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/reference_validator.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/registry.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/smart_search/__init__.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/smart_search/_base.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/smart_search/_config.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/smart_search/_deep.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/smart_search/_entities.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/smart_search/_fetch.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/smart_search/_overview.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/smart_search/_scenes.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/smart_search/_scoring.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/tool_search_hint_middleware.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/tools_addons.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/tools_areas.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/tools_blueprints.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/tools_bug_report.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/tools_calendar.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/tools_camera.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/tools_categories.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/tools_code.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/tools_dashboard_screenshot.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/tools_energy.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/tools_entities.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/tools_filesystem.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/tools_groups.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/tools_hacs.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/tools_history.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/tools_integrations.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/tools_labels.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/tools_mcp_component.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/tools_radio.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/tools_registry.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/tools_resources.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/tools_search.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/tools_service.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/tools_services.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/tools_system.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/tools_themes.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/tools_todo.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/tools_utility.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/tools_zones.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/util_helpers.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/validation_middleware.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/transforms/__init__.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/transforms/categorized_search.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/utils/__init__.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/utils/config_hash.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/utils/data_paths.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/utils/fuzzy_search.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/utils/operation_manager.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/utils/python_sandbox.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/utils/skill_loader.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/utils/usage_logger.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/tests/__init__.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/tests/test_constants.py +0 -0
- {ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/tests/test_env_manager.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ha-mcp-dev"
|
|
7
|
-
version = "7.9.0.
|
|
7
|
+
version = "7.9.0.dev760"
|
|
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"
|
|
@@ -131,6 +131,16 @@ def _custom_tool_write(args: dict[str, Any]) -> str | None:
|
|
|
131
131
|
return "sandbox code execution"
|
|
132
132
|
|
|
133
133
|
|
|
134
|
+
def _updates_write(args: dict[str, Any]) -> str | None:
|
|
135
|
+
# ``action`` defaults to "list" at the schema layer, so a missing action
|
|
136
|
+
# executes as a read (the middleware sees RAW pre-validation arguments —
|
|
137
|
+
# an absent key here means the tool itself will run the list branch).
|
|
138
|
+
action = args.get("action")
|
|
139
|
+
if action in (None, "list", "get"):
|
|
140
|
+
return None
|
|
141
|
+
return f"action={action!r}"
|
|
142
|
+
|
|
143
|
+
|
|
134
144
|
def _radio_write(args: dict[str, Any]) -> str | None:
|
|
135
145
|
action = args.get("action")
|
|
136
146
|
# Reads (allowed): per-node diagnostics, the integration/network summary,
|
|
@@ -200,6 +210,14 @@ READ_ONLY_EXEMPT_TOOLS: dict[str, ReadOnlyExemption] = {
|
|
|
200
210
|
"cluster-attribute read ('cluster_read'), and the Thread dataset "
|
|
201
211
|
"listing ('list_datasets')",
|
|
202
212
|
),
|
|
213
|
+
# Update listing/details exist only here — ha_get_updates was merged
|
|
214
|
+
# into this tool (issue #1726), so hiding it would remove the read
|
|
215
|
+
# surface entirely.
|
|
216
|
+
"ha_manage_updates": ReadOnlyExemption(
|
|
217
|
+
_updates_write,
|
|
218
|
+
"listing pending updates (action='list', the default) and reading "
|
|
219
|
+
"update details/release notes (action='get')",
|
|
220
|
+
),
|
|
203
221
|
}
|
|
204
222
|
|
|
205
223
|
|
{ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/best_practice_checker.py
RENAMED
|
@@ -162,6 +162,28 @@ _TARGET_FIELDS = ("entity_id", "device_id", "area_id", "floor_id", "label_id")
|
|
|
162
162
|
# ``service:`` (legacy) and ``action:`` (modern, 2024+) for the same field.
|
|
163
163
|
_SERVICE_KEYS = ("service", "action")
|
|
164
164
|
|
|
165
|
+
# Purpose-specific trigger/condition keys renamed in HA 2026.7 — the old keys
|
|
166
|
+
# no longer load (home-assistant/core#174463).
|
|
167
|
+
_RENAMED_TRIGGER_KEYS = {
|
|
168
|
+
"battery.low": "battery.became_low",
|
|
169
|
+
"battery.not_low": "battery.no_longer_low",
|
|
170
|
+
"lawn_mower.docked": "lawn_mower.returned_to_dock",
|
|
171
|
+
"schedule.turned_on": "schedule.block_started",
|
|
172
|
+
"schedule.turned_off": "schedule.block_ended",
|
|
173
|
+
"timer.time_remaining": "timer.remaining_time_reached",
|
|
174
|
+
"update.update_became_available": "update.became_available",
|
|
175
|
+
"vacuum.docked": "vacuum.returned_to_dock",
|
|
176
|
+
}
|
|
177
|
+
_RENAMED_CONDITION_KEYS = {
|
|
178
|
+
"climate.target_temperature": "climate.is_target_temperature",
|
|
179
|
+
"climate.target_humidity": "climate.is_target_humidity",
|
|
180
|
+
}
|
|
181
|
+
# Trigger ``options.behavior`` values renamed in HA 2026.7: any→each,
|
|
182
|
+
# last→all (home-assistant/core#173259). The old values still load but raise
|
|
183
|
+
# an HA repair issue and face removal. Condition ``options.behavior`` keeps
|
|
184
|
+
# ``any``/``all`` — conditions are never flagged.
|
|
185
|
+
_DEPRECATED_TRIGGER_BEHAVIOR = {"any": "each", "last": "all"}
|
|
186
|
+
|
|
165
187
|
|
|
166
188
|
# ---------------------------------------------------------------------------
|
|
167
189
|
# Public API
|
|
@@ -318,6 +340,23 @@ def _check_condition_templates(
|
|
|
318
340
|
# Shorthand template condition
|
|
319
341
|
_check_template_string(cond, warnings, skill_prefix, "condition")
|
|
320
342
|
elif isinstance(cond, dict):
|
|
343
|
+
# 2026.7 renamed purpose-specific condition keys — old keys no
|
|
344
|
+
# longer load.
|
|
345
|
+
condition_key = cond.get("condition")
|
|
346
|
+
renamed_condition = (
|
|
347
|
+
_RENAMED_CONDITION_KEYS.get(condition_key)
|
|
348
|
+
if isinstance(condition_key, str)
|
|
349
|
+
else None
|
|
350
|
+
)
|
|
351
|
+
if renamed_condition:
|
|
352
|
+
_emit(
|
|
353
|
+
warnings,
|
|
354
|
+
f"Condition key `{condition_key}` was renamed to "
|
|
355
|
+
f"`{renamed_condition}` in HA 2026.7 and the old key no "
|
|
356
|
+
f"longer loads — use `condition: {renamed_condition}`.",
|
|
357
|
+
skill_prefix,
|
|
358
|
+
"automation-patterns.md#native-conditions",
|
|
359
|
+
)
|
|
321
360
|
if cond.get("condition") == "template":
|
|
322
361
|
vt = cond.get("value_template", "")
|
|
323
362
|
if isinstance(vt, str):
|
|
@@ -655,12 +694,44 @@ def _check_triggers(
|
|
|
655
694
|
|
|
656
695
|
platform = trigger.get("platform", trigger.get("trigger", ""))
|
|
657
696
|
|
|
697
|
+
# 2026.7 renamed purpose-specific trigger keys — old keys no longer load.
|
|
698
|
+
renamed_trigger = (
|
|
699
|
+
_RENAMED_TRIGGER_KEYS.get(platform) if isinstance(platform, str) else None
|
|
700
|
+
)
|
|
701
|
+
if renamed_trigger:
|
|
702
|
+
_emit(
|
|
703
|
+
warnings,
|
|
704
|
+
f"Trigger key `{platform}` was renamed to `{renamed_trigger}` "
|
|
705
|
+
"in HA 2026.7 and the old key no longer loads — use "
|
|
706
|
+
f"`trigger: {renamed_trigger}`.",
|
|
707
|
+
skill_prefix,
|
|
708
|
+
"automation-patterns.md#trigger-types",
|
|
709
|
+
)
|
|
710
|
+
|
|
711
|
+
# 2026.7 renamed trigger `options.behavior` values (any→each, last→all).
|
|
712
|
+
options = trigger.get("options")
|
|
713
|
+
if isinstance(options, dict):
|
|
714
|
+
behavior = options.get("behavior")
|
|
715
|
+
if isinstance(behavior, str) and behavior in _DEPRECATED_TRIGGER_BEHAVIOR:
|
|
716
|
+
_emit(
|
|
717
|
+
warnings,
|
|
718
|
+
f"Trigger `options.behavior: {behavior}` was renamed to "
|
|
719
|
+
f"`{_DEPRECATED_TRIGGER_BEHAVIOR[behavior]}` in HA 2026.7 — "
|
|
720
|
+
"the old value still loads but raises a repair issue and "
|
|
721
|
+
"will be removed. Valid trigger values: `each`, `first`, "
|
|
722
|
+
"`all` (conditions keep `any`/`all`).",
|
|
723
|
+
skill_prefix,
|
|
724
|
+
"automation-patterns.md#trigger-types",
|
|
725
|
+
)
|
|
726
|
+
|
|
658
727
|
# Device trigger → prefer entity_id-based triggers
|
|
659
728
|
if platform == "device":
|
|
660
729
|
_emit(
|
|
661
730
|
warnings,
|
|
662
|
-
"Trigger uses `device` platform with `device_id` — prefer "
|
|
663
|
-
"
|
|
731
|
+
"Trigger uses `device` platform with `device_id` — prefer a "
|
|
732
|
+
"purpose-specific trigger (`<domain>.<name>` with a `target:` "
|
|
733
|
+
"of entities/areas/floors/labels, HA 2026.7+) or a `state`/"
|
|
734
|
+
"`event` trigger with `entity_id` when possible "
|
|
664
735
|
"(device_id breaks on re-add).",
|
|
665
736
|
skill_prefix,
|
|
666
737
|
"device-control.md#entity-id-vs-device-id",
|
{ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/tools_config_automations.py
RENAMED
|
@@ -440,6 +440,8 @@ class AutomationConfigTools:
|
|
|
440
440
|
Field(
|
|
441
441
|
description="Complete automation configuration with required fields: 'alias', 'triggers', 'actions'. "
|
|
442
442
|
"Optional: 'description', 'conditions', 'mode', 'max', 'initial_state', 'variables'. "
|
|
443
|
+
"Purpose-specific triggers/conditions (HA 2026.7+ default: 'trigger': '<domain>.<name>' "
|
|
444
|
+
"with 'target'/'options') are valid config. "
|
|
443
445
|
"Mutually exclusive with python_transform.",
|
|
444
446
|
default=None,
|
|
445
447
|
),
|
|
@@ -712,6 +712,8 @@ def _populate_trigger_info(
|
|
|
712
712
|
)
|
|
713
713
|
if "entity_id" in trigger_vars:
|
|
714
714
|
result["trigger"]["entity_id"] = trigger_vars["entity_id"]
|
|
715
|
+
if "error" in trigger_step:
|
|
716
|
+
result["trigger"]["error"] = trigger_step["error"]
|
|
715
717
|
|
|
716
718
|
if "trigger" not in result and "trigger" in trace:
|
|
717
719
|
result["trigger"] = {"description": trace["trigger"]}
|
|
@@ -731,6 +733,11 @@ def _populate_condition_results(
|
|
|
731
733
|
}
|
|
732
734
|
if "timestamp" in cond:
|
|
733
735
|
cond_result["timestamp"] = cond["timestamp"]
|
|
736
|
+
# HA 2026.7+ always records template errors on the failing step
|
|
737
|
+
# (core #172917) — dropping this field would hide the reason a
|
|
738
|
+
# condition evaluated to None.
|
|
739
|
+
if "error" in cond:
|
|
740
|
+
cond_result["error"] = cond["error"]
|
|
734
741
|
condition_results.append(cond_result)
|
|
735
742
|
result["condition_results"] = condition_results
|
|
736
743
|
|
|
@@ -15,6 +15,10 @@ from fastmcp.exceptions import ToolError
|
|
|
15
15
|
from fastmcp.tools import tool
|
|
16
16
|
from pydantic import Field
|
|
17
17
|
|
|
18
|
+
from ..client.rest_client import (
|
|
19
|
+
HomeAssistantAuthError,
|
|
20
|
+
HomeAssistantConnectionError,
|
|
21
|
+
)
|
|
18
22
|
from ..errors import ErrorCode, create_error_response
|
|
19
23
|
from .helpers import (
|
|
20
24
|
exception_to_structured_error,
|
|
@@ -22,9 +26,16 @@ from .helpers import (
|
|
|
22
26
|
raise_tool_error,
|
|
23
27
|
register_tool_methods,
|
|
24
28
|
)
|
|
29
|
+
from .util_helpers import JSON_STRING_COERCION
|
|
25
30
|
|
|
26
31
|
logger = logging.getLogger(__name__)
|
|
27
32
|
|
|
33
|
+
# Batch-install category split, mirroring the HA 2026.7 Updates page: the
|
|
34
|
+
# "Update all" button never covers core/OS/supervisor — those are applied
|
|
35
|
+
# individually, on purpose.
|
|
36
|
+
_INSTALL_ALL_CATEGORIES = ("addons", "hacs", "devices", "other")
|
|
37
|
+
_PROTECTED_UPDATE_CATEGORIES = ("core", "os", "supervisor")
|
|
38
|
+
|
|
28
39
|
_GITHUB_CORE_RELEASE_URL = (
|
|
29
40
|
"https://api.github.com/repos/home-assistant/core/releases/tags/{version}"
|
|
30
41
|
)
|
|
@@ -654,112 +665,299 @@ class UpdateTools:
|
|
|
654
665
|
|
|
655
666
|
return None, None
|
|
656
667
|
|
|
668
|
+
async def _resolve_update_targets(
|
|
669
|
+
self,
|
|
670
|
+
action: str,
|
|
671
|
+
entity_ids: list[str] | str | None,
|
|
672
|
+
categories: list[str] | str | None,
|
|
673
|
+
) -> list[str]:
|
|
674
|
+
"""Validate parameters and resolve the update entity_ids to act on."""
|
|
675
|
+
ids = [entity_ids] if isinstance(entity_ids, str) else list(entity_ids or [])
|
|
676
|
+
cats = [categories] if isinstance(categories, str) else list(categories or [])
|
|
677
|
+
|
|
678
|
+
if action != "install" and (not ids or cats):
|
|
679
|
+
raise_tool_error(
|
|
680
|
+
create_error_response(
|
|
681
|
+
ErrorCode.VALIDATION_INVALID_PARAMETER,
|
|
682
|
+
f"'{action}' requires entity_ids; categories is install-only.",
|
|
683
|
+
)
|
|
684
|
+
)
|
|
685
|
+
if action == "install" and bool(ids) == bool(cats):
|
|
686
|
+
raise_tool_error(
|
|
687
|
+
create_error_response(
|
|
688
|
+
ErrorCode.VALIDATION_INVALID_PARAMETER,
|
|
689
|
+
"'install' requires exactly one of entity_ids or categories.",
|
|
690
|
+
)
|
|
691
|
+
)
|
|
692
|
+
|
|
693
|
+
invalid = [e for e in ids if not e.startswith("update.")]
|
|
694
|
+
if invalid:
|
|
695
|
+
raise_tool_error(
|
|
696
|
+
create_error_response(
|
|
697
|
+
ErrorCode.VALIDATION_INVALID_PARAMETER,
|
|
698
|
+
"entity_ids must be 'update.' entities.",
|
|
699
|
+
context={"invalid_entity_ids": invalid},
|
|
700
|
+
)
|
|
701
|
+
)
|
|
702
|
+
|
|
703
|
+
if not cats:
|
|
704
|
+
return list(dict.fromkeys(ids))
|
|
705
|
+
|
|
706
|
+
protected = sorted(set(cats) & set(_PROTECTED_UPDATE_CATEGORIES))
|
|
707
|
+
unknown = sorted(
|
|
708
|
+
set(cats) - set(_INSTALL_ALL_CATEGORIES) - set(_PROTECTED_UPDATE_CATEGORIES)
|
|
709
|
+
)
|
|
710
|
+
if protected or unknown:
|
|
711
|
+
problems = []
|
|
712
|
+
if protected:
|
|
713
|
+
problems.append(
|
|
714
|
+
f"categories {protected} are excluded from batch install by "
|
|
715
|
+
"design (mirrors the HA Updates page, which never bundles "
|
|
716
|
+
"core/OS/supervisor into 'Update all') — target those "
|
|
717
|
+
"individually via entity_ids"
|
|
718
|
+
)
|
|
719
|
+
if unknown:
|
|
720
|
+
problems.append(f"unknown categories {unknown}")
|
|
721
|
+
raise_tool_error(
|
|
722
|
+
create_error_response(
|
|
723
|
+
ErrorCode.VALIDATION_INVALID_PARAMETER,
|
|
724
|
+
"; ".join(problems),
|
|
725
|
+
context={"allowed_categories": list(_INSTALL_ALL_CATEGORIES)},
|
|
726
|
+
)
|
|
727
|
+
)
|
|
728
|
+
|
|
729
|
+
listing = await self._list_updates(include_skipped=False)
|
|
730
|
+
listed_categories = listing.get("categories", {})
|
|
731
|
+
return [
|
|
732
|
+
update["entity_id"]
|
|
733
|
+
for cat in dict.fromkeys(cats)
|
|
734
|
+
for update in listed_categories.get(cat, [])
|
|
735
|
+
if update.get("can_install")
|
|
736
|
+
]
|
|
737
|
+
|
|
657
738
|
@tool(
|
|
658
|
-
name="
|
|
739
|
+
name="ha_manage_updates",
|
|
659
740
|
tags={"System"},
|
|
660
741
|
annotations={
|
|
661
|
-
"
|
|
742
|
+
"destructiveHint": True,
|
|
662
743
|
"openWorldHint": True,
|
|
663
|
-
"
|
|
664
|
-
"title": "Get Updates",
|
|
744
|
+
"title": "Manage Updates",
|
|
665
745
|
},
|
|
666
746
|
)
|
|
667
747
|
@log_tool_usage
|
|
668
|
-
async def
|
|
748
|
+
async def ha_manage_updates(
|
|
669
749
|
self,
|
|
670
|
-
|
|
671
|
-
str
|
|
750
|
+
action: Annotated[
|
|
751
|
+
str,
|
|
752
|
+
Field(
|
|
753
|
+
description="'list' (all pending updates, default), 'get' "
|
|
754
|
+
"(details/release notes for one update), 'install' (apply "
|
|
755
|
+
"pending updates), 'skip' (hide the offered version), or "
|
|
756
|
+
"'clear_skipped' (re-offer a skipped version).",
|
|
757
|
+
default="list",
|
|
758
|
+
),
|
|
759
|
+
] = "list",
|
|
760
|
+
entity_ids: Annotated[
|
|
761
|
+
list[str] | str | None,
|
|
762
|
+
JSON_STRING_COERCION,
|
|
672
763
|
Field(
|
|
673
|
-
description="Update
|
|
674
|
-
"
|
|
764
|
+
description="Update entity_id(s) to act on. 'get' takes exactly "
|
|
765
|
+
"one; skip/clear_skipped require at least one; for install, "
|
|
766
|
+
"mutually exclusive with categories.",
|
|
767
|
+
default=None,
|
|
768
|
+
),
|
|
769
|
+
] = None,
|
|
770
|
+
categories: Annotated[
|
|
771
|
+
list[str] | str | None,
|
|
772
|
+
JSON_STRING_COERCION,
|
|
773
|
+
Field(
|
|
774
|
+
description="For install: apply every pending update in these "
|
|
775
|
+
"categories ('addons', 'hacs', 'devices', 'other'). Mirrors the "
|
|
776
|
+
"HA 2026.7 'Update all' button: core/os/supervisor are excluded "
|
|
777
|
+
"by design (target those individually via entity_ids) and "
|
|
778
|
+
"skipped updates are never included.",
|
|
675
779
|
default=None,
|
|
676
780
|
),
|
|
677
781
|
] = None,
|
|
678
782
|
include_skipped: Annotated[
|
|
679
783
|
bool,
|
|
680
784
|
Field(
|
|
681
|
-
description="
|
|
785
|
+
description="For list: include updates that have been skipped "
|
|
786
|
+
"(default: False).",
|
|
682
787
|
default=False,
|
|
683
788
|
),
|
|
684
789
|
] = False,
|
|
685
790
|
include_release_notes: Annotated[
|
|
686
791
|
bool,
|
|
687
792
|
Field(
|
|
688
|
-
description="
|
|
689
|
-
"and breaking changes for all versions between
|
|
690
|
-
"
|
|
793
|
+
description="For get on a Core update entity: fetch multi-version "
|
|
794
|
+
"release notes and breaking changes for all versions between "
|
|
795
|
+
"installed and latest (default: False). Adds breaking_changes, "
|
|
796
|
+
"multi_version_release_notes, and installed_integrations to the "
|
|
797
|
+
"response.",
|
|
798
|
+
default=False,
|
|
799
|
+
),
|
|
800
|
+
] = False,
|
|
801
|
+
backup: Annotated[
|
|
802
|
+
bool,
|
|
803
|
+
Field(
|
|
804
|
+
description="For install: create a backup before installing where "
|
|
805
|
+
"the update entity supports it (apps/add-ons). Default: False.",
|
|
691
806
|
default=False,
|
|
692
807
|
),
|
|
693
808
|
] = False,
|
|
694
809
|
) -> dict[str, Any]:
|
|
695
|
-
"""
|
|
696
|
-
Get update information -- list all updates or get details for a specific one.
|
|
697
|
-
|
|
698
|
-
Without an entity_id: Lists all available updates across the system including
|
|
699
|
-
Home Assistant Core, add-ons, device firmware, HACS, and OS updates.
|
|
810
|
+
"""Manage Home Assistant updates -- list, read details, batch install, skip, or un-skip.
|
|
700
811
|
|
|
701
|
-
|
|
702
|
-
|
|
812
|
+
Covers Core, OS, supervisor, apps (add-ons), device firmware, and HACS
|
|
813
|
+
update entities. In Read Only Mode the read actions ('list', 'get') stay
|
|
814
|
+
available; write actions are blocked.
|
|
703
815
|
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
816
|
+
Installs run asynchronously in Home Assistant and can take minutes:
|
|
817
|
+
'install' returns once the service calls are accepted, with per-entity
|
|
818
|
+
results. Poll action='list' to watch in_progress until installed_version
|
|
819
|
+
reaches latest_version.
|
|
707
820
|
|
|
708
821
|
EXAMPLES:
|
|
709
|
-
- List all updates:
|
|
710
|
-
-
|
|
711
|
-
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
release (from PyPI for pip/Docker, the Supervisor add-on store for the
|
|
721
|
-
add-on). Present on all install types; omitted only for the unknown
|
|
722
|
-
version and when HA_MCP_DISABLE_UPDATE_CHECK is set.
|
|
723
|
-
|
|
724
|
-
RETURNS (when getting specific update):
|
|
725
|
-
- Update details including installed/latest versions
|
|
726
|
-
- Release notes (fetched from WebSocket API or GitHub)
|
|
727
|
-
- Category and installation status
|
|
728
|
-
|
|
729
|
-
RETURNS (with include_release_notes=True, Core only):
|
|
730
|
-
- breaking_changes.entries[]: Each has integration, description, version
|
|
731
|
-
- multi_version_release_notes[]: Full text per version {version, content, source_url}
|
|
732
|
-
- installed_integrations: Your integration domains for cross-referencing
|
|
822
|
+
- List all updates: ha_manage_updates()
|
|
823
|
+
- Pre-update analysis: ha_manage_updates(action="get", entity_ids=["update.home_assistant_core_update"], include_release_notes=True)
|
|
824
|
+
- Update everything pending in a category: ha_manage_updates(action="install", categories=["addons", "hacs"])
|
|
825
|
+
|
|
826
|
+
RETURNS (action='list'): updates_available, updates, categories, and
|
|
827
|
+
ha_mcp_update -- this MCP server's own update status {current, latest,
|
|
828
|
+
update_available}, so a newer ha-mcp release can be flagged.
|
|
829
|
+
|
|
830
|
+
RETURNS (action='get'): update details, release notes; with
|
|
831
|
+
include_release_notes=True on Core also breaking_changes.entries[],
|
|
832
|
+
multi_version_release_notes[], and installed_integrations.
|
|
733
833
|
"""
|
|
734
834
|
try:
|
|
735
|
-
if
|
|
835
|
+
if action == "list":
|
|
736
836
|
return await self._list_updates(bool(include_skipped))
|
|
737
|
-
|
|
837
|
+
|
|
838
|
+
if action == "get":
|
|
839
|
+
ids = (
|
|
840
|
+
[entity_ids]
|
|
841
|
+
if isinstance(entity_ids, str)
|
|
842
|
+
else list(entity_ids or [])
|
|
843
|
+
)
|
|
844
|
+
if len(ids) != 1:
|
|
845
|
+
raise_tool_error(
|
|
846
|
+
create_error_response(
|
|
847
|
+
ErrorCode.VALIDATION_INVALID_PARAMETER,
|
|
848
|
+
"'get' requires exactly one entity in entity_ids.",
|
|
849
|
+
context={"entity_ids": ids},
|
|
850
|
+
)
|
|
851
|
+
)
|
|
738
852
|
return await self._get_update_details(
|
|
739
|
-
|
|
853
|
+
ids[0], bool(include_release_notes)
|
|
854
|
+
)
|
|
855
|
+
|
|
856
|
+
if action not in ("install", "skip", "clear_skipped"):
|
|
857
|
+
raise_tool_error(
|
|
858
|
+
create_error_response(
|
|
859
|
+
ErrorCode.VALIDATION_INVALID_PARAMETER,
|
|
860
|
+
f"Invalid action '{action}'. Must be 'list', 'get', "
|
|
861
|
+
"'install', 'skip', or 'clear_skipped'.",
|
|
862
|
+
context={"action": action},
|
|
863
|
+
)
|
|
864
|
+
)
|
|
865
|
+
|
|
866
|
+
targets = await self._resolve_update_targets(action, entity_ids, categories)
|
|
867
|
+
|
|
868
|
+
results: list[dict[str, Any]] = []
|
|
869
|
+
requested = len(targets)
|
|
870
|
+
|
|
871
|
+
# Explicit-id mode: verify the entities exist up front. HA service
|
|
872
|
+
# calls targeting nonexistent entities no-op silently, which would
|
|
873
|
+
# otherwise read as success.
|
|
874
|
+
if entity_ids:
|
|
875
|
+
states = await self._client.get_states()
|
|
876
|
+
known = {s.get("entity_id") for s in states if isinstance(s, dict)}
|
|
877
|
+
results.extend(
|
|
878
|
+
create_error_response(
|
|
879
|
+
ErrorCode.ENTITY_NOT_FOUND,
|
|
880
|
+
f"Update entity not found: {eid}",
|
|
881
|
+
context={"entity_id": eid},
|
|
882
|
+
suggestions=["Use ha_manage_updates() to list update entities"],
|
|
883
|
+
)
|
|
884
|
+
for eid in targets
|
|
885
|
+
if eid not in known
|
|
886
|
+
)
|
|
887
|
+
targets = [e for e in targets if e in known]
|
|
888
|
+
|
|
889
|
+
succeeded = 0
|
|
890
|
+
for eid in targets:
|
|
891
|
+
data: dict[str, Any] = {"entity_id": eid}
|
|
892
|
+
if action == "install" and backup:
|
|
893
|
+
data["backup"] = True
|
|
894
|
+
try:
|
|
895
|
+
await self._client.call_service("update", action, data)
|
|
896
|
+
results.append({"success": True, "entity_id": eid})
|
|
897
|
+
succeeded += 1
|
|
898
|
+
except (HomeAssistantConnectionError, HomeAssistantAuthError):
|
|
899
|
+
# Fatal transport/auth failure — the connection is gone,
|
|
900
|
+
# so remaining items would fail identically. Propagate to
|
|
901
|
+
# surface the root cause instead of N per-item errors.
|
|
902
|
+
raise
|
|
903
|
+
except Exception as e:
|
|
904
|
+
# Batch item failure — collect, don't raise.
|
|
905
|
+
results.append(
|
|
906
|
+
create_error_response(
|
|
907
|
+
ErrorCode.SERVICE_CALL_FAILED,
|
|
908
|
+
str(e),
|
|
909
|
+
context={"entity_id": eid},
|
|
910
|
+
)
|
|
911
|
+
)
|
|
912
|
+
|
|
913
|
+
failed = requested - succeeded
|
|
914
|
+
response: dict[str, Any] = {
|
|
915
|
+
# Aggregate convention (matches ha_bulk-style tools): True only
|
|
916
|
+
# when nothing failed — zero requested counts as success.
|
|
917
|
+
"success": failed == 0,
|
|
918
|
+
"action": action,
|
|
919
|
+
"requested": requested,
|
|
920
|
+
"succeeded": succeeded,
|
|
921
|
+
"failed": failed,
|
|
922
|
+
"results": results,
|
|
923
|
+
}
|
|
924
|
+
if not requested:
|
|
925
|
+
response["note"] = "No matching pending updates to act on."
|
|
926
|
+
elif action == "install" and succeeded:
|
|
927
|
+
response["note"] = (
|
|
928
|
+
"Installs run asynchronously in Home Assistant and can take "
|
|
929
|
+
"minutes; poll ha_manage_updates(action='list') to track "
|
|
930
|
+
"progress."
|
|
740
931
|
)
|
|
932
|
+
return response
|
|
741
933
|
|
|
742
934
|
except ToolError:
|
|
743
935
|
raise
|
|
744
936
|
except Exception as e:
|
|
745
937
|
error_msg = str(e)
|
|
746
|
-
if
|
|
938
|
+
if (
|
|
939
|
+
action == "get"
|
|
940
|
+
and entity_ids
|
|
941
|
+
and ("404" in error_msg or "not found" in error_msg.lower())
|
|
942
|
+
):
|
|
943
|
+
eid = entity_ids if isinstance(entity_ids, str) else entity_ids[0]
|
|
747
944
|
raise_tool_error(
|
|
748
945
|
create_error_response(
|
|
749
946
|
ErrorCode.ENTITY_NOT_FOUND,
|
|
750
|
-
f"Update entity not found: {
|
|
751
|
-
context={"entity_id":
|
|
947
|
+
f"Update entity not found: {eid}",
|
|
948
|
+
context={"entity_id": eid},
|
|
752
949
|
suggestions=[
|
|
753
|
-
"Use
|
|
950
|
+
"Use ha_manage_updates() without entity_ids to see "
|
|
951
|
+
"all available updates"
|
|
754
952
|
],
|
|
755
953
|
)
|
|
756
954
|
)
|
|
757
|
-
logger.error(f"Failed to
|
|
955
|
+
logger.error(f"Failed to manage updates: {e}")
|
|
758
956
|
exception_to_structured_error(
|
|
759
957
|
e,
|
|
760
958
|
suggestions=[
|
|
761
959
|
"Check Home Assistant connection",
|
|
762
|
-
"
|
|
960
|
+
"Use ha_manage_updates() to inspect available updates",
|
|
763
961
|
],
|
|
764
962
|
)
|
|
765
963
|
return (
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
An operator can sit on an old build without ever knowing. This does a
|
|
4
4
|
fail-silent check, holds the result in memory for the process, and surfaces a
|
|
5
5
|
newer release via a startup log banner and the ``ha_get_overview`` /
|
|
6
|
-
``ha_get_system_health`` / ``
|
|
6
|
+
``ha_get_system_health`` / ``ha_manage_updates`` tool fields.
|
|
7
7
|
|
|
8
8
|
The comparison reference depends on how ha-mcp is deployed, because the running
|
|
9
9
|
version only means something against the matching source:
|
|
@@ -216,7 +216,7 @@ async def get_update_field() -> UpdateField | None:
|
|
|
216
216
|
so it never blocks the event loop, and shapes the result for embedding under
|
|
217
217
|
an ``ha_mcp_update`` key. Never raises — a hiccup yields None (the tool omits
|
|
218
218
|
the field) and is logged at debug. Shared by every status tool that surfaces
|
|
219
|
-
the notice (``ha_get_overview`` / ``ha_get_system_health`` / ``
|
|
219
|
+
the notice (``ha_get_overview`` / ``ha_get_system_health`` / ``ha_manage_updates``)
|
|
220
220
|
so the shaping and event-loop offload live in one place.
|
|
221
221
|
"""
|
|
222
222
|
try:
|
|
@@ -176,7 +176,16 @@ DOMAIN_HANDLERS = {
|
|
|
176
176
|
"valid_actions": [],
|
|
177
177
|
"parameters": [],
|
|
178
178
|
"quick_actions": [],
|
|
179
|
-
|
|
179
|
+
# in_zones (all containing zones, smallest first) and tracking_type
|
|
180
|
+
# ("connection"/"location") were added in HA 2026.7; latitude/longitude
|
|
181
|
+
# may be absent when a person/tracker is located by a presence scanner.
|
|
182
|
+
"state_attributes": [
|
|
183
|
+
"state",
|
|
184
|
+
"latitude",
|
|
185
|
+
"longitude",
|
|
186
|
+
"in_zones",
|
|
187
|
+
"tracking_type",
|
|
188
|
+
],
|
|
180
189
|
"read_only": True,
|
|
181
190
|
"provides_location": True,
|
|
182
191
|
},
|
|
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.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/dashboard_screenshot/__init__.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/dashboard_screenshot/capture.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/dashboard_screenshot/provision.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
|
{ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/resources/skills-vendor/AGENTS.md
RENAMED
|
File without changes
|
{ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/resources/skills-vendor/CLAUDE.md
RENAMED
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/resources/skills-vendor/LICENSE
RENAMED
|
File without changes
|
{ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/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
|
|
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.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/smart_search/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/smart_search/_config.py
RENAMED
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/smart_search/_entities.py
RENAMED
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/smart_search/_overview.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/smart_search/_scenes.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/smart_search/_scoring.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/tool_search_hint_middleware.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
|
{ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/tools_config_dashboards.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/tools_config_helpers.py
RENAMED
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/tools_config_scripts.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/tools_dashboard_screenshot.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.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/tools_voice_assistant.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/tools/validation_middleware.py
RENAMED
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/transforms/categorized_search.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/transforms/lite_docstrings.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp/utils/kill_signal_diagnostics.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/src/ha_mcp_dev.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{ha_mcp_dev-7.9.0.dev759 → ha_mcp_dev-7.9.0.dev760}/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
|