ha-mcp-dev 7.14.2.dev2098__tar.gz → 7.14.2.dev2103__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.14.2.dev2098/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.14.2.dev2103}/PKG-INFO +1 -1
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/pyproject.toml +1 -1
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/client/rest_client.py +180 -28
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/tools_config_scenes.py +97 -13
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/tools_search.py +209 -47
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/LICENSE +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/MANIFEST.in +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/README.md +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/setup.cfg +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/__main__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/_pypi_marker +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/_version.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/auth/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/auth/consent_form.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/auth/provider.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/backup_manager.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/browser_landing.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/client/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/client/supervisor_client.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/client/websocket_client.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/client/websocket_listener.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/config.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/dashboard_screenshot/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/dashboard_screenshot/capture.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/dashboard_screenshot/content.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/dashboard_screenshot/paths.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/dashboard_screenshot/provision.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/dashboard_screenshot/theme_guard.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/errors.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/llm_exposure.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/policy/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/policy/approval_queue.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/policy/evaluator.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/policy/handlers.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/policy/middleware.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/policy/model.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/policy/persistence.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/policy/value_sources.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/py.typed +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/read_only.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/appdaemon.md +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/blueprint-guide.md +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/scenes.md +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/yaml-only-integrations.md +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/server.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/settings_ui/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/settings_ui/_handlers_advanced.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/settings_ui/_handlers_backups.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/settings_ui/_handlers_fs.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/settings_ui/_handlers_server.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/settings_ui/_handlers_theme.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/settings_ui/_handlers_tools.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/settings_ui/_i18n.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/settings_ui/_persistence.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/settings_ui/_supervisor.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/settings_ui/_theme.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/settings_ui/_tools_meta.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/settings_ui/locales/README.md +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/settings_ui/locales/de.json +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/settings_ui/locales/en.json +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/settings_ui/locales/ru.json +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/settings_ui/locales/zh-Hans.json +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/settings_ui/settings.css +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/settings_ui/settings.html +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/settings_ui/settings.js +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/smoke_test.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/strict_bps.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/auto_backup.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/backup.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/best_practice_checker.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/component_api.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/component_config_reads.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/component_devices.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/component_registries.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/component_registry_lookup.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/config_entry_flow.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/device_control.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/enhanced.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/helpers.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/radio/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/radio/base.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/radio/matter.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/radio/thread.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/radio/zigbee.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/radio/zwave.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/reference_validator.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/registry.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/smart_search/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/smart_search/_base.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/smart_search/_config.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/smart_search/_deep.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/smart_search/_entities.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/smart_search/_fetch.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/smart_search/_overview.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/smart_search/_scenes.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/smart_search/_scoring.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/tool_search_hint_middleware.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/tools_addons.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/tools_areas.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/tools_blueprints.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/tools_bug_report.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/tools_calendar.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/tools_camera.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/tools_categories.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/tools_code.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/tools_config_automations.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/tools_dashboard_screenshot.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/tools_dev.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/tools_energy.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/tools_entities.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/tools_filesystem.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/tools_groups.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/tools_hacs.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/tools_history.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/tools_integrations.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/tools_labels.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/tools_radio.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/tools_registry.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/tools_resources.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/tools_service.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/tools_services.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/tools_system.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/tools_themes.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/tools_todo.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/tools_traces.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/tools_updates.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/tools_utility.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/tools_yaml_read.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/tools_zones.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/util_helpers.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/validation_middleware.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/transforms/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/transforms/categorized_search.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/transport_security.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/update_check.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/utils/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/utils/config_hash.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/utils/data_paths.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/utils/domain_handlers.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/utils/fuzzy_search.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/utils/operation_manager.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/utils/python_sandbox.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/utils/skill_loader.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/utils/usage_logger.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/visibility/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/visibility/model.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/visibility/persistence.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/visibility/resolver.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/tests/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/tests/test_constants.py +0 -0
- {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/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.14.2.
|
|
7
|
+
version = "7.14.2.dev2103"
|
|
8
8
|
description = "Home Assistant MCP Server - Complete control of Home Assistant through MCP"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
# Upper bound widened to <3.15 so the package installs on Home Assistant's
|
|
@@ -8,7 +8,8 @@ import logging
|
|
|
8
8
|
import os
|
|
9
9
|
import ssl
|
|
10
10
|
import time
|
|
11
|
-
from
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from typing import Any, NoReturn
|
|
12
13
|
|
|
13
14
|
import httpx
|
|
14
15
|
from websockets.exceptions import WebSocketException
|
|
@@ -97,6 +98,54 @@ class HomeAssistantAPIError(HomeAssistantError):
|
|
|
97
98
|
self.response_data = response_data
|
|
98
99
|
|
|
99
100
|
|
|
101
|
+
@dataclass(frozen=True)
|
|
102
|
+
class SceneResolution:
|
|
103
|
+
"""Outcome of resolving a scene identifier via the entity registry.
|
|
104
|
+
|
|
105
|
+
``registry_hit`` records whether ``config/entity_registry/get`` returned a
|
|
106
|
+
usable ``unique_id`` - i.e. the entity actually EXISTS. It is the signal that
|
|
107
|
+
separates a genuinely-missing scene from one that exists but is not editable
|
|
108
|
+
through HA's scene config API (a Hue/vendor scene, or a raw-YAML scene): both
|
|
109
|
+
resolve fine yet 404 on ``config/scene/config/{id}``, because that endpoint is
|
|
110
|
+
backed only by the managed ``scenes.yaml`` (issue #1971). ``platform`` carries
|
|
111
|
+
the registry entry's integration domain (``"hue"``, ``"homeassistant"``, …)
|
|
112
|
+
for error-message enrichment only - it never drives the classification, since
|
|
113
|
+
a raw-YAML scene is ``platform="homeassistant"`` yet still 404s.
|
|
114
|
+
"""
|
|
115
|
+
|
|
116
|
+
storage_key: str
|
|
117
|
+
registry_hit: bool
|
|
118
|
+
platform: str | None
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class SceneStorageConfigNotFoundError(HomeAssistantAPIError):
|
|
122
|
+
"""A scene entity resolved in the registry, but ``config/scene/config/{id}``
|
|
123
|
+
(backed by ``scenes.yaml``) has no matching entry.
|
|
124
|
+
|
|
125
|
+
The entity EXISTS; it is simply not an editable Home Assistant storage scene
|
|
126
|
+
(Hue/vendor scene, or a raw-YAML scene). Raised instead of a bare 404 so the
|
|
127
|
+
tools layer can surface ``CONFIG_NOT_FOUND`` rather than the misleading
|
|
128
|
+
``ENTITY_NOT_FOUND``/``RESOURCE_NOT_FOUND`` (issue #1971). Subclasses
|
|
129
|
+
``HomeAssistantAPIError`` so existing ``except HomeAssistantAPIError`` sites
|
|
130
|
+
keep treating it as the 404 it is.
|
|
131
|
+
"""
|
|
132
|
+
|
|
133
|
+
def __init__(
|
|
134
|
+
self,
|
|
135
|
+
scene_id: str,
|
|
136
|
+
*,
|
|
137
|
+
platform: str | None = None,
|
|
138
|
+
storage_key: str | None = None,
|
|
139
|
+
):
|
|
140
|
+
self.scene_id = scene_id
|
|
141
|
+
self.platform = platform
|
|
142
|
+
self.storage_key = storage_key
|
|
143
|
+
msg = f"Scene not found in editable storage: {scene_id}"
|
|
144
|
+
if storage_key and storage_key != scene_id:
|
|
145
|
+
msg += f" (resolved storage key: {storage_key})"
|
|
146
|
+
super().__init__(msg, status_code=404)
|
|
147
|
+
|
|
148
|
+
|
|
100
149
|
class HomeAssistantCommandError(HomeAssistantError):
|
|
101
150
|
"""WebSocket command returned success=False.
|
|
102
151
|
|
|
@@ -1773,20 +1822,23 @@ class HomeAssistantClient:
|
|
|
1773
1822
|
) from e
|
|
1774
1823
|
raise
|
|
1775
1824
|
|
|
1776
|
-
async def
|
|
1777
|
-
"""
|
|
1778
|
-
Resolve a scene identifier to its storage key via the entity registry.
|
|
1825
|
+
async def _resolve_scene(self, identifier: str) -> SceneResolution:
|
|
1826
|
+
"""Resolve a scene identifier to its storage key AND registry metadata.
|
|
1779
1827
|
|
|
1780
|
-
|
|
1781
|
-
the
|
|
1782
|
-
|
|
1783
|
-
|
|
1828
|
+
Backs :meth:`resolve_scene_id`. Returns the storage key together with
|
|
1829
|
+
``registry_hit`` (did the entity actually resolve?) and ``platform`` - the
|
|
1830
|
+
two signals a bare storage key throws away. Callers that hit a config-API
|
|
1831
|
+
404 need ``registry_hit`` to tell "entity missing" from "entity exists but
|
|
1832
|
+
has no editable storage config", and ``platform`` to name the owning
|
|
1833
|
+
integration in the resulting error (issue #1971).
|
|
1784
1834
|
|
|
1785
1835
|
Args:
|
|
1786
1836
|
identifier: Scene ID (with or without ``scene.`` prefix)
|
|
1787
1837
|
|
|
1788
1838
|
Returns:
|
|
1789
|
-
|
|
1839
|
+
A :class:`SceneResolution`. On any lookup failure it falls back to the
|
|
1840
|
+
bare id with ``registry_hit=False`` - a write path must not treat the
|
|
1841
|
+
guessed bare id as a confirmed resolve.
|
|
1790
1842
|
"""
|
|
1791
1843
|
bare_id = identifier.removeprefix("scene.")
|
|
1792
1844
|
entity_id = f"scene.{bare_id}"
|
|
@@ -1795,13 +1847,18 @@ class HomeAssistantClient:
|
|
|
1795
1847
|
{"type": "config/entity_registry/get", "entity_id": entity_id}
|
|
1796
1848
|
)
|
|
1797
1849
|
if result.get("success") is not False:
|
|
1798
|
-
|
|
1850
|
+
entry = result.get("result") or {}
|
|
1851
|
+
unique_id = entry.get("unique_id")
|
|
1799
1852
|
if unique_id:
|
|
1800
1853
|
if unique_id != bare_id:
|
|
1801
1854
|
logger.debug(
|
|
1802
1855
|
f"Resolved scene entity_id {entity_id} to storage key {unique_id}"
|
|
1803
1856
|
)
|
|
1804
|
-
return
|
|
1857
|
+
return SceneResolution(
|
|
1858
|
+
storage_key=str(unique_id),
|
|
1859
|
+
registry_hit=True,
|
|
1860
|
+
platform=entry.get("platform"),
|
|
1861
|
+
)
|
|
1805
1862
|
except HomeAssistantConnectionError:
|
|
1806
1863
|
# A dead transport must not become a silent guess: the fallback
|
|
1807
1864
|
# below assumes the storage key equals the bare entity_id, which is
|
|
@@ -1814,19 +1871,111 @@ class HomeAssistantClient:
|
|
|
1814
1871
|
f"Entity registry lookup failed for {entity_id}, using bare id: {bare_id}",
|
|
1815
1872
|
exc_info=True,
|
|
1816
1873
|
)
|
|
1817
|
-
return bare_id
|
|
1874
|
+
return SceneResolution(storage_key=bare_id, registry_hit=False, platform=None)
|
|
1875
|
+
|
|
1876
|
+
async def resolve_scene_id(self, identifier: str) -> str:
|
|
1877
|
+
"""
|
|
1878
|
+
Resolve a scene identifier to its storage key via the entity registry.
|
|
1879
|
+
|
|
1880
|
+
Scenes may be renamed in the HA UI, changing the entity_id but keeping
|
|
1881
|
+
the original storage key. Like :meth:`_resolve_script_id`, this returns
|
|
1882
|
+
the bare storage key and needs a WebSocket entity registry lookup because
|
|
1883
|
+
the state attributes do not surface it; the parity ends there, since the
|
|
1884
|
+
underlying :meth:`_resolve_scene` additionally carries registry metadata
|
|
1885
|
+
(``registry_hit``/``platform``) that the script resolver has no analogue for.
|
|
1886
|
+
|
|
1887
|
+
Thin wrapper over :meth:`_resolve_scene` for callers that only need the
|
|
1888
|
+
storage key (e.g. the write path); the read/delete paths use the richer
|
|
1889
|
+
:class:`SceneResolution` directly.
|
|
1890
|
+
|
|
1891
|
+
Args:
|
|
1892
|
+
identifier: Scene ID (with or without ``scene.`` prefix)
|
|
1893
|
+
|
|
1894
|
+
Returns:
|
|
1895
|
+
The storage key for the configuration API
|
|
1896
|
+
"""
|
|
1897
|
+
return (await self._resolve_scene(identifier)).storage_key
|
|
1898
|
+
|
|
1899
|
+
async def _scene_state_exists(self, storage_key: str) -> bool:
|
|
1900
|
+
"""Return True if ``scene.<storage_key>`` exists in the state machine.
|
|
1901
|
+
|
|
1902
|
+
An ``id``-less YAML scene has no registry ``unique_id`` (so
|
|
1903
|
+
``registry_hit`` is False) yet still registers a state-machine entity.
|
|
1904
|
+
This one-shot existence check separates that real-but-not-storage scene
|
|
1905
|
+
from a genuinely-absent one after a config-API 404 (issue #1971). A 404
|
|
1906
|
+
here is a clean "absent"; a transport failure propagates (fail loud).
|
|
1907
|
+
"""
|
|
1908
|
+
try:
|
|
1909
|
+
await self.get_entity_state(f"scene.{storage_key}")
|
|
1910
|
+
return True
|
|
1911
|
+
except HomeAssistantAPIError as e:
|
|
1912
|
+
if e.status_code == 404:
|
|
1913
|
+
return False
|
|
1914
|
+
raise
|
|
1915
|
+
|
|
1916
|
+
async def _raise_scene_config_404(
|
|
1917
|
+
self,
|
|
1918
|
+
scene_id: str,
|
|
1919
|
+
resolved_id: str,
|
|
1920
|
+
resolution: SceneResolution | None,
|
|
1921
|
+
) -> NoReturn:
|
|
1922
|
+
"""Translate a scene config-API 404 into the right exception.
|
|
1923
|
+
|
|
1924
|
+
When the registry resolve succeeded (``registry_hit``), the entity exists
|
|
1925
|
+
and the 404 means "no editable storage config" → raise
|
|
1926
|
+
:class:`SceneStorageConfigNotFoundError` carrying the platform. On a
|
|
1927
|
+
registry miss the entity may still be an ``id``-less YAML scene, which has
|
|
1928
|
+
no registry ``unique_id`` yet exists in the state machine: a state hit
|
|
1929
|
+
upgrades the 404 to the same not-storage-scene case with ``platform=None``
|
|
1930
|
+
(issue #1971). Only a state miss too is the historic genuinely-absent bare
|
|
1931
|
+
404. The state check is skipped when ``resolution`` is None (the internal
|
|
1932
|
+
``_resolved`` re-fetch fast path, where a 404 is a genuine disappearance).
|
|
1933
|
+
Always raises.
|
|
1934
|
+
"""
|
|
1935
|
+
if resolution is not None:
|
|
1936
|
+
if resolution.registry_hit:
|
|
1937
|
+
raise SceneStorageConfigNotFoundError(
|
|
1938
|
+
scene_id, platform=resolution.platform, storage_key=resolved_id
|
|
1939
|
+
)
|
|
1940
|
+
if await self._scene_state_exists(resolved_id):
|
|
1941
|
+
raise SceneStorageConfigNotFoundError(
|
|
1942
|
+
scene_id, platform=None, storage_key=resolved_id
|
|
1943
|
+
)
|
|
1944
|
+
msg = f"Scene not found: {scene_id}"
|
|
1945
|
+
if resolved_id != scene_id:
|
|
1946
|
+
msg += f" (resolved storage key: {resolved_id})"
|
|
1947
|
+
raise HomeAssistantAPIError(msg, status_code=404)
|
|
1818
1948
|
|
|
1819
1949
|
async def get_scene_config(
|
|
1820
|
-
self,
|
|
1950
|
+
self,
|
|
1951
|
+
scene_id: str,
|
|
1952
|
+
*,
|
|
1953
|
+
_resolved: bool = False,
|
|
1954
|
+
resolution: SceneResolution | None = None,
|
|
1821
1955
|
) -> dict[str, Any]:
|
|
1822
1956
|
"""Get Home Assistant scene configuration by scene_id.
|
|
1823
1957
|
|
|
1958
|
+
``resolution`` lets a caller that already ran :meth:`_resolve_scene` pass
|
|
1959
|
+
the full outcome through: the endpoint uses its ``storage_key`` and its
|
|
1960
|
+
``registry_hit``/``platform`` drive the config-404 classification (issue
|
|
1961
|
+
#1971) with no extra lookup. It takes precedence over ``_resolved``.
|
|
1962
|
+
|
|
1824
1963
|
``_resolved=True`` signals ``scene_id`` is already the resolved storage
|
|
1825
1964
|
key (the caller ran :meth:`resolve_scene_id`), skipping the redundant
|
|
1826
1965
|
registry lookup. ``resolve_scene_id`` is idempotent, so the result is
|
|
1827
|
-
identical either way.
|
|
1966
|
+
identical either way. This fast path is the internal re-fetch of an
|
|
1967
|
+
already-resolved scene (its sole caller is the post-write re-fetch), where
|
|
1968
|
+
a subsequent 404 is a genuine disappearance, not the not-storage-scene
|
|
1969
|
+
case; the ``registry_hit``/``platform`` signals a config 404 needs are
|
|
1970
|
+
deliberately unavailable there.
|
|
1828
1971
|
"""
|
|
1829
|
-
|
|
1972
|
+
if resolution is not None:
|
|
1973
|
+
resolved_id = resolution.storage_key
|
|
1974
|
+
elif _resolved:
|
|
1975
|
+
resolved_id = scene_id
|
|
1976
|
+
else:
|
|
1977
|
+
resolution = await self._resolve_scene(scene_id)
|
|
1978
|
+
resolved_id = resolution.storage_key
|
|
1830
1979
|
try:
|
|
1831
1980
|
endpoint = f"config/scene/config/{resolved_id}"
|
|
1832
1981
|
response = await self._request("GET", endpoint)
|
|
@@ -1834,10 +1983,7 @@ class HomeAssistantClient:
|
|
|
1834
1983
|
return {"success": True, "scene_id": resolved_id, "config": response}
|
|
1835
1984
|
except HomeAssistantAPIError as e:
|
|
1836
1985
|
if e.status_code == 404:
|
|
1837
|
-
|
|
1838
|
-
if resolved_id != scene_id:
|
|
1839
|
-
msg += f" (resolved storage key: {resolved_id})"
|
|
1840
|
-
raise HomeAssistantAPIError(msg, status_code=404) from e
|
|
1986
|
+
await self._raise_scene_config_404(scene_id, resolved_id, resolution)
|
|
1841
1987
|
raise
|
|
1842
1988
|
except Exception as e:
|
|
1843
1989
|
logger.error(f"Failed to get scene config for {scene_id}: {e}")
|
|
@@ -1904,15 +2050,24 @@ class HomeAssistantClient:
|
|
|
1904
2050
|
raise
|
|
1905
2051
|
|
|
1906
2052
|
async def delete_scene_config(
|
|
1907
|
-
self,
|
|
2053
|
+
self,
|
|
2054
|
+
scene_id: str,
|
|
2055
|
+
*,
|
|
2056
|
+
resolution: SceneResolution | None = None,
|
|
1908
2057
|
) -> dict[str, Any]:
|
|
1909
2058
|
"""Delete Home Assistant scene configuration.
|
|
1910
2059
|
|
|
1911
|
-
``
|
|
1912
|
-
|
|
1913
|
-
|
|
2060
|
+
``resolution`` lets a caller that already ran :meth:`_resolve_scene` pass
|
|
2061
|
+
the full outcome through - the endpoint uses its ``storage_key`` while
|
|
2062
|
+
``scene_id`` stays the caller's slug for messages, and its ``registry_hit``
|
|
2063
|
+
drives the config-404 classification (issue #1971) with no extra lookup.
|
|
2064
|
+
Otherwise the id is resolved here.
|
|
1914
2065
|
"""
|
|
1915
|
-
|
|
2066
|
+
if resolution is not None:
|
|
2067
|
+
resolved_id = resolution.storage_key
|
|
2068
|
+
else:
|
|
2069
|
+
resolution = await self._resolve_scene(scene_id)
|
|
2070
|
+
resolved_id = resolution.storage_key
|
|
1916
2071
|
try:
|
|
1917
2072
|
endpoint = f"config/scene/config/{resolved_id}"
|
|
1918
2073
|
response = await self._request("DELETE", endpoint)
|
|
@@ -1925,10 +2080,7 @@ class HomeAssistantClient:
|
|
|
1925
2080
|
}
|
|
1926
2081
|
except HomeAssistantAPIError as e:
|
|
1927
2082
|
if e.status_code == 404:
|
|
1928
|
-
|
|
1929
|
-
if resolved_id != scene_id:
|
|
1930
|
-
msg += f" (resolved storage key: {resolved_id})"
|
|
1931
|
-
raise HomeAssistantAPIError(msg, status_code=404) from e
|
|
2083
|
+
await self._raise_scene_config_404(scene_id, resolved_id, resolution)
|
|
1932
2084
|
elif e.status_code == 405:
|
|
1933
2085
|
raise HomeAssistantAPIError(
|
|
1934
2086
|
f"Cannot delete scene '{scene_id}': The HTTP DELETE method is blocked. "
|
{ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2103}/src/ha_mcp/tools/tools_config_scenes.py
RENAMED
|
@@ -9,7 +9,7 @@ keyed by entity_id (not a list).
|
|
|
9
9
|
|
|
10
10
|
import asyncio
|
|
11
11
|
import logging
|
|
12
|
-
from typing import Annotated, Any, cast
|
|
12
|
+
from typing import Annotated, Any, NoReturn, cast
|
|
13
13
|
|
|
14
14
|
from fastmcp.exceptions import ToolError
|
|
15
15
|
from fastmcp.tools import tool
|
|
@@ -19,6 +19,7 @@ from ..client.rest_client import (
|
|
|
19
19
|
HomeAssistantAPIError,
|
|
20
20
|
HomeAssistantAuthError,
|
|
21
21
|
HomeAssistantConnectionError,
|
|
22
|
+
SceneStorageConfigNotFoundError,
|
|
22
23
|
)
|
|
23
24
|
from ..errors import ErrorCode, create_error_response
|
|
24
25
|
from ..strict_bps import BestPracticeKeyParam
|
|
@@ -63,6 +64,50 @@ _SCENE_SKILL_FILES: tuple[str, ...] = ("SKILL.md",)
|
|
|
63
64
|
logger = logging.getLogger(__name__)
|
|
64
65
|
|
|
65
66
|
|
|
67
|
+
def _raise_scene_not_storage_error(scene_id: str, platform: str | None) -> NoReturn:
|
|
68
|
+
"""Raise ``CONFIG_NOT_FOUND`` for a scene that exists but has no editable config.
|
|
69
|
+
|
|
70
|
+
The scene entity was confirmed to exist - in the entity registry, or, for an
|
|
71
|
+
``id``-less YAML scene that has no registry entry, in the state machine - but
|
|
72
|
+
``config/scene/config/{id}``, backed only by the managed ``scenes.yaml``, has
|
|
73
|
+
no entry for it. That is the not-a-storage-scene case (a Hue/vendor scene, or
|
|
74
|
+
a scene defined directly in YAML): the entity EXISTS, so ``ENTITY_NOT_FOUND``
|
|
75
|
+
would be wrong (issue #1971). ``platform`` names the owning integration when
|
|
76
|
+
the registry exposed it; the message stays generic otherwise.
|
|
77
|
+
"""
|
|
78
|
+
entity_id = f"scene.{scene_id.removeprefix('scene.')}"
|
|
79
|
+
suggestions = [
|
|
80
|
+
"Activate it with the scene.turn_on service: "
|
|
81
|
+
f"ha_call_service('scene', 'turn_on', {{'entity_id': '{entity_id}'}}).",
|
|
82
|
+
]
|
|
83
|
+
if platform and platform != "homeassistant":
|
|
84
|
+
platform_note = (
|
|
85
|
+
f" It is provided by the '{platform}' integration, which manages its "
|
|
86
|
+
"own scenes outside Home Assistant's editable scene storage."
|
|
87
|
+
)
|
|
88
|
+
suggestions.append(
|
|
89
|
+
f"Edit this scene in the '{platform}' integration or its companion app; "
|
|
90
|
+
"ha_config_get_scene / ha_config_set_scene only edit Home Assistant "
|
|
91
|
+
"UI/storage scenes."
|
|
92
|
+
)
|
|
93
|
+
else:
|
|
94
|
+
platform_note = ""
|
|
95
|
+
suggestions.append(
|
|
96
|
+
"ha_config_get_scene / ha_config_set_scene only read and edit Home "
|
|
97
|
+
"Assistant UI/storage scenes (Settings > Automations & Scenes > Scenes). "
|
|
98
|
+
"Scenes defined directly in YAML are not editable through this API."
|
|
99
|
+
)
|
|
100
|
+
raise_tool_error(
|
|
101
|
+
create_error_response(
|
|
102
|
+
ErrorCode.CONFIG_NOT_FOUND,
|
|
103
|
+
f"Scene '{entity_id}' exists but has no editable Home Assistant "
|
|
104
|
+
f"storage configuration.{platform_note}",
|
|
105
|
+
suggestions=suggestions,
|
|
106
|
+
context={"scene_id": scene_id, "platform": platform},
|
|
107
|
+
)
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
66
111
|
class ConfigSceneTools:
|
|
67
112
|
"""Scene configuration management tools for Home Assistant."""
|
|
68
113
|
|
|
@@ -352,6 +397,11 @@ class ConfigSceneTools:
|
|
|
352
397
|
return await self._legacy_get_scene(scene_id)
|
|
353
398
|
except ToolError:
|
|
354
399
|
raise
|
|
400
|
+
except SceneStorageConfigNotFoundError as e:
|
|
401
|
+
# The entity resolved but scenes.yaml has no config for it - a Hue/
|
|
402
|
+
# vendor or raw-YAML scene. Surface CONFIG_NOT_FOUND, not the
|
|
403
|
+
# ENTITY_NOT_FOUND the generic handler below would emit (#1971).
|
|
404
|
+
_raise_scene_not_storage_error(e.scene_id, e.platform)
|
|
355
405
|
except Exception as e:
|
|
356
406
|
# Pass `entity_id` so a 404 from rest_client surfaces as
|
|
357
407
|
# ENTITY_NOT_FOUND (not the generic RESOURCE_NOT_FOUND fallback).
|
|
@@ -746,6 +796,12 @@ class ConfigSceneTools:
|
|
|
746
796
|
|
|
747
797
|
except ToolError as te:
|
|
748
798
|
raise augment_tool_error_with_skill_content(te, bp_warnings=None) from None
|
|
799
|
+
except SceneStorageConfigNotFoundError as e:
|
|
800
|
+
# Set on a non-storage scene: the hash-verify / python_transform
|
|
801
|
+
# pre-fetch read 404s because the entity has no editable scenes.yaml
|
|
802
|
+
# config (Hue/vendor or raw-YAML). Same CONFIG_NOT_FOUND
|
|
803
|
+
# classification as get/delete (#1971), not a generic write failure.
|
|
804
|
+
_raise_scene_not_storage_error(e.scene_id, e.platform)
|
|
749
805
|
except Exception as e:
|
|
750
806
|
error = exception_to_structured_error(
|
|
751
807
|
e,
|
|
@@ -1079,18 +1135,36 @@ class ConfigSceneTools:
|
|
|
1079
1135
|
# so subsequent upsert/response builders use the storage key
|
|
1080
1136
|
# consistently (issue #1168 R3 blocker 6).
|
|
1081
1137
|
#
|
|
1082
|
-
# Path branching:
|
|
1083
|
-
#
|
|
1084
|
-
#
|
|
1085
|
-
#
|
|
1086
|
-
#
|
|
1087
|
-
#
|
|
1138
|
+
# Path branching: the hash arm's inner fetch reads the config
|
|
1139
|
+
# first, so a non-storage scene (Hue/vendor, raw-YAML) surfaces
|
|
1140
|
+
# as CONFIG_NOT_FOUND and a genuinely-missing scene as the bare
|
|
1141
|
+
# 404 – you cannot lock against a config that isn't there. The
|
|
1142
|
+
# no-hash arm has no such read, so it resolves richly and, when
|
|
1143
|
+
# the entity already exists, pre-checks the config API below
|
|
1144
|
+
# before the upsert (issue #1971) so a plain ``set`` on a
|
|
1145
|
+
# non-storage scene cannot silently shadow-create a duplicate.
|
|
1088
1146
|
if config_hash:
|
|
1089
1147
|
_, resolved_id = await self._fetch_and_verify_hash(
|
|
1090
1148
|
scene_id, config_hash, "set"
|
|
1091
1149
|
)
|
|
1092
1150
|
else:
|
|
1093
|
-
|
|
1151
|
+
resolution = await self._client._resolve_scene(scene_id)
|
|
1152
|
+
resolved_id = resolution.storage_key
|
|
1153
|
+
# If the scene already exists this is an EDIT, and without a hash
|
|
1154
|
+
# there is no prior read: a POST to ``config/scene/config/<key>``
|
|
1155
|
+
# for a Hue/vendor or YAML scene would CREATE a duplicate managed
|
|
1156
|
+
# ``scenes.yaml`` entry instead of erroring. Pre-check the config
|
|
1157
|
+
# API first – its 404 raises SceneStorageConfigNotFoundError, which
|
|
1158
|
+
# the outer handler maps to CONFIG_NOT_FOUND.
|
|
1159
|
+
#
|
|
1160
|
+
# Existence takes both signals: an ``id``-less YAML scene has no
|
|
1161
|
+
# registry unique_id yet still holds a state-machine entity, and
|
|
1162
|
+
# shadow-creates just the same. Only a scene missing from both is a
|
|
1163
|
+
# real create, at the price of one extra ``/states/`` GET there.
|
|
1164
|
+
if resolution.registry_hit or await self._client._scene_state_exists(
|
|
1165
|
+
resolved_id
|
|
1166
|
+
):
|
|
1167
|
+
await self._client.get_scene_config(scene_id, resolution=resolution)
|
|
1094
1168
|
|
|
1095
1169
|
# Cross-check literal service and entity references against the
|
|
1096
1170
|
# live registries. Soft warnings only.
|
|
@@ -1229,8 +1303,12 @@ class ConfigSceneTools:
|
|
|
1229
1303
|
# callsite (entity_id resolver, delete call, response) uses the
|
|
1230
1304
|
# storage key consistently — outer ``scene_id`` matches the
|
|
1231
1305
|
# inner body regardless of whether the caller passed the
|
|
1232
|
-
# entity_id slug or the storage key.
|
|
1233
|
-
|
|
1306
|
+
# entity_id slug or the storage key. The rich resolution also
|
|
1307
|
+
# carries ``registry_hit``/``platform`` so a config-API 404 on a
|
|
1308
|
+
# non-storage scene (Hue/vendor, raw-YAML) classifies as
|
|
1309
|
+
# CONFIG_NOT_FOUND rather than the misleading generic 404 (#1971).
|
|
1310
|
+
resolution = await self._client._resolve_scene(scene_id)
|
|
1311
|
+
resolved_id = resolution.storage_key
|
|
1234
1312
|
|
|
1235
1313
|
# Resolve actual entity_id BEFORE delete — once the registry
|
|
1236
1314
|
# entry is gone, the unique_id lookup can no longer find it.
|
|
@@ -1240,9 +1318,11 @@ class ConfigSceneTools:
|
|
|
1240
1318
|
resolved_id, allow_component=True
|
|
1241
1319
|
)
|
|
1242
1320
|
|
|
1243
|
-
#
|
|
1244
|
-
#
|
|
1245
|
-
result = await self._client.delete_scene_config(
|
|
1321
|
+
# Thread the resolution through so delete_scene_config reuses the
|
|
1322
|
+
# storage key (no redundant re-resolve) and can classify a 404.
|
|
1323
|
+
result = await self._client.delete_scene_config(
|
|
1324
|
+
scene_id, resolution=resolution
|
|
1325
|
+
)
|
|
1246
1326
|
|
|
1247
1327
|
if wait:
|
|
1248
1328
|
try:
|
|
@@ -1264,6 +1344,10 @@ class ConfigSceneTools:
|
|
|
1264
1344
|
}
|
|
1265
1345
|
except ToolError:
|
|
1266
1346
|
raise
|
|
1347
|
+
except SceneStorageConfigNotFoundError as e:
|
|
1348
|
+
# Entity resolved but scenes.yaml has no config for it - a Hue/
|
|
1349
|
+
# vendor or raw-YAML scene, not a genuinely missing entity (#1971).
|
|
1350
|
+
_raise_scene_not_storage_error(e.scene_id, e.platform)
|
|
1267
1351
|
except Exception as e:
|
|
1268
1352
|
exception_to_structured_error(
|
|
1269
1353
|
e,
|