ha-mcp-dev 8.4.2.dev2530__tar.gz → 8.4.2.dev2534__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-8.4.2.dev2530/src/ha_mcp_dev.egg-info → ha_mcp_dev-8.4.2.dev2534}/PKG-INFO +1 -1
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/pyproject.toml +1 -1
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/bulk_selector.py +20 -11
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/component_api.py +7 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/component_devices.py +9 -2
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/smart_search/_entities.py +9 -12
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/smart_search/_overview.py +11 -12
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_entities.py +5 -1
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_registry.py +36 -7
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_search.py +42 -14
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_voice_assistant.py +5 -1
- ha_mcp_dev-8.4.2.dev2534/src/ha_mcp/utils/device_registry_semantics.py +171 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/visibility/resolver.py +85 -26
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp_dev.egg-info/SOURCES.txt +1 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/LICENSE +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/MANIFEST.in +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/README.md +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/setup.cfg +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/__init__.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/__main__.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_pypi_marker +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_rich_compat.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/__init__.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/requirements.txt +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/LICENSE +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/MANIFEST.sha256 +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/VENDORED +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/__init__.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/__main__.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/asyncio/__init__.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/asyncio/client.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/asyncio/connection.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/asyncio/messages.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/asyncio/router.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/asyncio/server.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/auth.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/cli.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/client.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/connection.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/datastructures.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/exceptions.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/extensions/__init__.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/extensions/base.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/extensions/permessage_deflate.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/frames.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/headers.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/http11.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/imports.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/legacy/__init__.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/legacy/auth.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/legacy/client.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/legacy/exceptions.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/legacy/framing.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/legacy/handshake.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/legacy/http.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/legacy/protocol.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/legacy/server.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/protocol.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/proxy.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/py.typed +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/server.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/streams.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/sync/__init__.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/sync/client.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/sync/connection.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/sync/messages.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/sync/router.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/sync/server.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/sync/utils.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/trio/__init__.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/trio/client.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/trio/connection.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/trio/messages.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/trio/router.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/trio/server.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/trio/utils.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/typing.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/uri.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/utils.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/version.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_version.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/auth/__init__.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/auth/consent_form.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/auth/oidc_compat.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/auth/provider.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/backup_manager.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/browser_landing.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/client/__init__.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/client/rest_client.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/client/supervisor_client.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/client/websocket_client.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/client/websocket_listener.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/config.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/dashboard_screenshot/__init__.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/dashboard_screenshot/capture.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/dashboard_screenshot/content.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/dashboard_screenshot/paths.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/dashboard_screenshot/provision.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/dashboard_screenshot/theme_guard.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/errors.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/hacs_auto_refresh.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/llm_exposure.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/log_filters.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/policy/__init__.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/policy/approval_queue.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/policy/editing.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/policy/evaluator.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/policy/handlers.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/policy/middleware.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/policy/model.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/policy/persistence.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/policy/value_sources.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/py.typed +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/read_only.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/redaction.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/renamed_tools.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/evals/area-target-over-entity-list/case.yaml +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/evals/arrive-home-automation/case.yaml +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/evals/assist-shortcut-card/case.yaml +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/evals/avg-temperature/case.yaml +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/evals/backup-deletion-confirmation/case.yaml +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/evals/blueprint-authoring/case.yaml +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/evals/button-event-entity/case.yaml +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/evals/device-id-trigger-migration/case.yaml +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/evals/helper-flow-fields-not-platform-yaml/case.yaml +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/evals/media-player-song/case.yaml +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/evals/motion-light-automation-mode/case.yaml +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/evals/rename-entity-impact-analysis/case.yaml +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/evals/template-attributes-yaml-exception/case.yaml +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/evals/timer-finished-automation/case.yaml +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/evals/vacuum-last-clean/case.yaml +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/scripts/check_eval_cases.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/appdaemon.md +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/backups.md +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/blueprint-guide.md +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/scenes.md +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/yaml-only-integrations.md +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/server.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/settings_ui/__init__.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/settings_ui/_handlers_advanced.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/settings_ui/_handlers_backups.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/settings_ui/_handlers_fs.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/settings_ui/_handlers_server.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/settings_ui/_handlers_theme.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/settings_ui/_handlers_tools.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/settings_ui/_i18n.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/settings_ui/_locale_policy.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/settings_ui/_persistence.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/settings_ui/_supervisor.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/settings_ui/_theme.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/settings_ui/_tools_meta.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/settings_ui/locales/README.md +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/settings_ui/locales/cs.json +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/settings_ui/locales/de.json +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/settings_ui/locales/en.json +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/settings_ui/locales/eo.json +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/settings_ui/locales/es.json +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/settings_ui/locales/fr.json +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/settings_ui/locales/it.json +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/settings_ui/locales/ko.json +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/settings_ui/locales/nl.json +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/settings_ui/locales/pl.json +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/settings_ui/locales/ru.json +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/settings_ui/locales/sv.json +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/settings_ui/locales/tlh.json +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/settings_ui/locales/zh-Hans.json +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/settings_ui/settings.css +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/settings_ui/settings.html +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/settings_ui/settings.js +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/smoke_test.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/strict_bps.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/__init__.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/auto_backup.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/backup.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/best_practice_checker.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/best_practice_result.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/best_practice_variables.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/component_config_entries.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/component_config_reads.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/component_registries.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/component_registry_lookup.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/config_entry_flow.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/config_entry_flow_form.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/config_entry_flow_menu.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/config_entry_flow_walker.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/config_entry_identity.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/config_entry_reconfigure.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/config_entry_reload_watch.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/device_control.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/error_log_parsing.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/hacs_registration.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/helpers.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/integration_reconfigure.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/log_common.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/log_sources.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/log_sources_supervisor.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/radio/__init__.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/radio/base.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/radio/matter.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/radio/thread.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/radio/zigbee.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/radio/zwave.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/reference_validator.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/registry.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/renamed_tool_middleware.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/smart_search/__init__.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/smart_search/_base.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/smart_search/_config.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/smart_search/_deep.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/smart_search/_fetch.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/smart_search/_graph.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/smart_search/_scenes.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/smart_search/_scoring.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tool_search_hint_middleware.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_addons.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_areas.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_assist_pipeline.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_blueprints.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_bug_report.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_calendar.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_camera.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_categories.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_code.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_config_automations.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_dashboard_screenshot.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_dev.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_energy.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_filesystem.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_groups.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_hacs.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_history.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_integrations.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_labels.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_logs.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_radio.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_resources.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_security_policy.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_service.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_services.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_system.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_themes.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_todo.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_traces.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_updates.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_utility.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_yaml_read.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_zones.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/util_helpers.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/validation_middleware.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/transforms/__init__.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/transforms/categorized_search.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/transport_security.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/update_check.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/utils/__init__.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/utils/config_hash.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/utils/config_write_lock.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/utils/data_paths.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/utils/domain_handlers.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/utils/entity_membership.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/utils/fuzzy_search.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/utils/operation_manager.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/utils/python_sandbox.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/utils/skill_loader.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/utils/usage_logger.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/visibility/__init__.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/visibility/enforcement.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/visibility/model.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/visibility/persistence.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/tests/__init__.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/tests/test_constants.py +0 -0
- {ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/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 = "8.4.2.
|
|
7
|
+
version = "8.4.2.dev2534"
|
|
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
|
|
@@ -13,6 +13,11 @@ from typing import Annotated, Any, NamedTuple, NotRequired, TypedDict
|
|
|
13
13
|
from pydantic import ConfigDict, Field
|
|
14
14
|
|
|
15
15
|
from ..client.rest_client import HomeAssistantClient
|
|
16
|
+
from ..utils.device_registry_semantics import (
|
|
17
|
+
DeviceRegistrySnapshot,
|
|
18
|
+
build_device_registry_snapshot,
|
|
19
|
+
effective_entity_area_id,
|
|
20
|
+
)
|
|
16
21
|
from ..utils.domain_handlers import get_domain_handler
|
|
17
22
|
from ..utils.entity_membership import normalize_member_entity_ids
|
|
18
23
|
from ..visibility.resolver import VisibilityDataUnavailable, load_hidden_set
|
|
@@ -259,8 +264,10 @@ def _registry_rows(result: Any, label: str) -> list[dict[str, Any]]:
|
|
|
259
264
|
return rows
|
|
260
265
|
|
|
261
266
|
|
|
262
|
-
def _validate_device_registry_rows(
|
|
263
|
-
|
|
267
|
+
def _validate_device_registry_rows(
|
|
268
|
+
device_rows: list[dict[str, Any]],
|
|
269
|
+
) -> DeviceRegistrySnapshot:
|
|
270
|
+
"""Return a canonical snapshot or fail closed on ambiguous device evidence.
|
|
264
271
|
|
|
265
272
|
``visibility.resolver._parse_device_registry`` silently skips any device
|
|
266
273
|
entry without an ``id`` even when the hidden-set resolver runs
|
|
@@ -272,11 +279,18 @@ def _validate_device_registry_rows(device_rows: list[dict[str, Any]]) -> None:
|
|
|
272
279
|
resolver. Infrastructure-class (HA's own registry data is malformed),
|
|
273
280
|
not the caller's selector to fix.
|
|
274
281
|
"""
|
|
275
|
-
if any(not row.get("id") for row in device_rows):
|
|
282
|
+
if any(not isinstance(row.get("id"), str) or not row["id"] for row in device_rows):
|
|
276
283
|
raise BulkSelectorInfrastructureError(
|
|
277
284
|
"Home Assistant device registry returned a malformed entry",
|
|
278
285
|
cause=InfrastructureErrorCause.MALFORMED_DEVICE_REGISTRY,
|
|
279
286
|
)
|
|
287
|
+
snapshot = build_device_registry_snapshot(device_rows)
|
|
288
|
+
if snapshot.conflicting_ids:
|
|
289
|
+
raise BulkSelectorInfrastructureError(
|
|
290
|
+
"Home Assistant device registry returned a conflicting device identity",
|
|
291
|
+
cause=InfrastructureErrorCause.MALFORMED_DEVICE_REGISTRY,
|
|
292
|
+
)
|
|
293
|
+
return snapshot
|
|
280
294
|
|
|
281
295
|
|
|
282
296
|
def _string_list(selector: Mapping[str, Any], key: str) -> list[str]:
|
|
@@ -305,10 +319,7 @@ def _entity_area_id(
|
|
|
305
319
|
entry = entity_registry.get(entity_id)
|
|
306
320
|
if not entry:
|
|
307
321
|
return None
|
|
308
|
-
|
|
309
|
-
return str(area_id)
|
|
310
|
-
device_id = entry.get("device_id")
|
|
311
|
-
return device_areas.get(str(device_id)) if device_id else None
|
|
322
|
+
return effective_entity_area_id(entry, device_areas)
|
|
312
323
|
|
|
313
324
|
|
|
314
325
|
def _expand_entity(
|
|
@@ -686,7 +697,7 @@ async def resolve_bulk_selector(
|
|
|
686
697
|
)
|
|
687
698
|
entity_rows = _registry_rows(topology.entities, "entity registry")
|
|
688
699
|
device_rows = _registry_rows(topology.devices, "device registry")
|
|
689
|
-
_validate_device_registry_rows(device_rows)
|
|
700
|
+
device_snapshot = _validate_device_registry_rows(device_rows)
|
|
690
701
|
selected_areas = _select_area_ids(
|
|
691
702
|
_registry_rows(topology.areas, "area registry"),
|
|
692
703
|
_registry_rows(topology.floors, "floor registry"),
|
|
@@ -704,9 +715,7 @@ async def resolve_bulk_selector(
|
|
|
704
715
|
for row in entity_rows
|
|
705
716
|
if isinstance(row.get("entity_id"), str)
|
|
706
717
|
}
|
|
707
|
-
device_areas =
|
|
708
|
-
str(row["id"]): row.get("area_id") for row in device_rows if row.get("id")
|
|
709
|
-
}
|
|
718
|
+
device_areas = device_snapshot.effective_area_by_id
|
|
710
719
|
hidden, visibility_warnings = await _load_hidden_entities(
|
|
711
720
|
client, topology.entities, topology.states, topology.devices, entity_registry
|
|
712
721
|
)
|
|
@@ -56,6 +56,13 @@ UNKNOWN_COMMAND_CODE = "unknown_command"
|
|
|
56
56
|
# routing gate can't trust command payloads shaped for a different generation.
|
|
57
57
|
SUPPORTED_SCHEMA_VERSION = 1
|
|
58
58
|
|
|
59
|
+
# Additive semantic capability shared by every component path whose result is
|
|
60
|
+
# enriched from the device registry. The pre-2.1.3 ``device_list``/``device_get``
|
|
61
|
+
# commands (and search/overview/entity enrichment built on the same registry)
|
|
62
|
+
# predate Core 2026.9 child devices. Because component and server releases update
|
|
63
|
+
# independently, command presence alone cannot authorize the newer semantics.
|
|
64
|
+
DEVICE_REGISTRY_CHILD_SEMANTICS = "device_registry_child_semantics"
|
|
65
|
+
|
|
59
66
|
# Negative (None) caps entries expire after this many seconds of monotonic
|
|
60
67
|
# time, so a component installed / upgraded mid-session is re-probed and
|
|
61
68
|
# adopted instead of being pinned to "absent" for the whole process lifetime
|
|
@@ -44,6 +44,7 @@ from ..client.rest_client import (
|
|
|
44
44
|
)
|
|
45
45
|
from ..client.websocket_client import get_websocket_client
|
|
46
46
|
from .component_api import (
|
|
47
|
+
DEVICE_REGISTRY_CHILD_SEMANTICS,
|
|
47
48
|
component_supports,
|
|
48
49
|
get_component_caps,
|
|
49
50
|
invalidate_caps,
|
|
@@ -85,7 +86,10 @@ async def fetch_device_via_component(
|
|
|
85
86
|
only the device (``_resolve_ieee`` / capture / remove) are unchanged.
|
|
86
87
|
"""
|
|
87
88
|
caps = await get_component_caps(client)
|
|
88
|
-
if not
|
|
89
|
+
if not (
|
|
90
|
+
component_supports(caps, "device_get")
|
|
91
|
+
and component_supports(caps, DEVICE_REGISTRY_CHILD_SEMANTICS)
|
|
92
|
+
):
|
|
89
93
|
return None
|
|
90
94
|
kwargs: dict[str, Any] = {"device_id": device_id}
|
|
91
95
|
if include_entities:
|
|
@@ -167,7 +171,10 @@ async def fetch_device_list_via_component(client: Any) -> dict[str, Any] | None:
|
|
|
167
171
|
fallback as :func:`fetch_device_via_component`.
|
|
168
172
|
"""
|
|
169
173
|
caps = await get_component_caps(client)
|
|
170
|
-
if not
|
|
174
|
+
if not (
|
|
175
|
+
component_supports(caps, "device_list")
|
|
176
|
+
and component_supports(caps, DEVICE_REGISTRY_CHILD_SEMANTICS)
|
|
177
|
+
):
|
|
171
178
|
return None
|
|
172
179
|
try:
|
|
173
180
|
ws = await get_websocket_client(
|
{ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/smart_search/_entities.py
RENAMED
|
@@ -5,6 +5,10 @@ import logging
|
|
|
5
5
|
from collections.abc import Mapping
|
|
6
6
|
from typing import Any
|
|
7
7
|
|
|
8
|
+
from ...utils.device_registry_semantics import (
|
|
9
|
+
build_device_registry_snapshot,
|
|
10
|
+
effective_entity_area_id,
|
|
11
|
+
)
|
|
8
12
|
from ...utils.entity_membership import normalize_member_entity_ids
|
|
9
13
|
from ...utils.fuzzy_search import calculate_partial_ratio, calculate_ratio
|
|
10
14
|
from ...visibility.resolver import (
|
|
@@ -651,13 +655,9 @@ class EntitySearchMixin(_SearchBase):
|
|
|
651
655
|
def _parse_device_area_map(
|
|
652
656
|
cls, result: Any, warnings: list[str] | None = None
|
|
653
657
|
) -> dict[str, str | None]:
|
|
654
|
-
"""Parse
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
device_id = device.get("id", "")
|
|
658
|
-
if device_id:
|
|
659
|
-
device_area_map[device_id] = device.get("area_id")
|
|
660
|
-
return device_area_map
|
|
658
|
+
"""Parse devices into IDs mapped to direct-or-parent effective areas."""
|
|
659
|
+
device_rows = cls._extract_registry_list(result, "device registry", warnings)
|
|
660
|
+
return build_device_registry_snapshot(list(device_rows)).effective_area_by_id
|
|
661
661
|
|
|
662
662
|
@staticmethod
|
|
663
663
|
def _match_exact_registry_ids(
|
|
@@ -875,7 +875,7 @@ class EntitySearchMixin(_SearchBase):
|
|
|
875
875
|
include_hidden: bool,
|
|
876
876
|
visibility_hidden: set[str],
|
|
877
877
|
) -> tuple[dict[str, str], set[str]]:
|
|
878
|
-
"""Map
|
|
878
|
+
"""Map entity IDs to direct area, then device direct-or-parent effective area.
|
|
879
879
|
|
|
880
880
|
Hidden entities are filtered only when include_hidden is False;
|
|
881
881
|
otherwise they pass through and downstream applies the score penalty so
|
|
@@ -892,10 +892,7 @@ class EntitySearchMixin(_SearchBase):
|
|
|
892
892
|
continue
|
|
893
893
|
if is_hidden:
|
|
894
894
|
hidden_entity_ids.add(entity_id)
|
|
895
|
-
area_id = reg_info
|
|
896
|
-
device_id = reg_info.get("device_id")
|
|
897
|
-
if not area_id and device_id:
|
|
898
|
-
area_id = device_area_map.get(device_id)
|
|
895
|
+
area_id = effective_entity_area_id(reg_info, device_area_map)
|
|
899
896
|
if area_id:
|
|
900
897
|
entity_area_resolved[entity_id] = area_id
|
|
901
898
|
return entity_area_resolved, hidden_entity_ids
|
{ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/smart_search/_overview.py
RENAMED
|
@@ -6,6 +6,10 @@ import random
|
|
|
6
6
|
from itertools import islice
|
|
7
7
|
from typing import Any
|
|
8
8
|
|
|
9
|
+
from ...utils.device_registry_semantics import (
|
|
10
|
+
build_device_registry_snapshot,
|
|
11
|
+
effective_entity_area_id,
|
|
12
|
+
)
|
|
9
13
|
from ...visibility.resolver import load_hidden_set
|
|
10
14
|
from ..helpers import exception_to_structured_error
|
|
11
15
|
from ._base import _SearchBase
|
|
@@ -237,23 +241,18 @@ class SystemOverviewMixin(_SearchBase):
|
|
|
237
241
|
entity_registry: list[dict[str, Any]],
|
|
238
242
|
device_registry: list[dict[str, Any]],
|
|
239
243
|
) -> dict[str, str | None]:
|
|
240
|
-
"""Map
|
|
241
|
-
device_area_map
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
if device_id:
|
|
245
|
-
device_area_map[device_id] = device.get("area_id")
|
|
244
|
+
"""Map entity IDs to direct area, then device direct-or-parent effective area."""
|
|
245
|
+
device_area_map = build_device_registry_snapshot(
|
|
246
|
+
list(device_registry)
|
|
247
|
+
).effective_area_by_id
|
|
246
248
|
|
|
247
249
|
entity_area_map: dict[str, str | None] = {}
|
|
248
250
|
for entry in entity_registry:
|
|
249
251
|
entity_id = entry.get("entity_id")
|
|
250
|
-
area_id = entry.get("area_id")
|
|
251
|
-
if not area_id:
|
|
252
|
-
device_id = entry.get("device_id")
|
|
253
|
-
if device_id:
|
|
254
|
-
area_id = device_area_map.get(device_id)
|
|
255
252
|
if entity_id:
|
|
256
|
-
entity_area_map[entity_id] =
|
|
253
|
+
entity_area_map[entity_id] = effective_entity_area_id(
|
|
254
|
+
entry, device_area_map
|
|
255
|
+
)
|
|
257
256
|
return entity_area_map
|
|
258
257
|
|
|
259
258
|
@staticmethod
|
|
@@ -22,6 +22,7 @@ from ..client.websocket_client import get_websocket_client
|
|
|
22
22
|
from ..errors import ErrorCode, create_error_response
|
|
23
23
|
from .auto_backup import with_auto_backup
|
|
24
24
|
from .component_api import (
|
|
25
|
+
DEVICE_REGISTRY_CHILD_SEMANTICS,
|
|
25
26
|
component_supports,
|
|
26
27
|
get_component_caps,
|
|
27
28
|
invalidate_caps,
|
|
@@ -118,7 +119,10 @@ async def fetch_entity_enrichment_via_component(
|
|
|
118
119
|
if not entity_ids:
|
|
119
120
|
return None
|
|
120
121
|
caps = await get_component_caps(client)
|
|
121
|
-
if not
|
|
122
|
+
if not (
|
|
123
|
+
component_supports(caps, "entity_enrich")
|
|
124
|
+
and component_supports(caps, DEVICE_REGISTRY_CHILD_SEMANTICS)
|
|
125
|
+
):
|
|
122
126
|
return None
|
|
123
127
|
ids = list(entity_ids)
|
|
124
128
|
chunks = [
|
|
@@ -16,6 +16,10 @@ from pydantic import Field
|
|
|
16
16
|
|
|
17
17
|
from ..client.rest_client import HomeAssistantAPIError, HomeAssistantConnectionError
|
|
18
18
|
from ..errors import ErrorCode, create_error_response
|
|
19
|
+
from ..utils.device_registry_semantics import (
|
|
20
|
+
EFFECTIVE_AREA_MARKER,
|
|
21
|
+
annotate_device_rows_with_effective_area,
|
|
22
|
+
)
|
|
19
23
|
from .auto_backup import with_auto_backup
|
|
20
24
|
from .component_devices import (
|
|
21
25
|
fetch_device_list_via_component,
|
|
@@ -137,7 +141,7 @@ def _get_device_info(device: dict[str, Any]) -> dict[str, Any]:
|
|
|
137
141
|
"manufacturer": device.get("manufacturer"),
|
|
138
142
|
"model": device.get("model"),
|
|
139
143
|
"sw_version": device.get("sw_version"),
|
|
140
|
-
"area_id": device.get("area_id"),
|
|
144
|
+
"area_id": device.get(EFFECTIVE_AREA_MARKER, device.get("area_id")),
|
|
141
145
|
"integration_type": integration_type,
|
|
142
146
|
"integration_sources": integration_sources,
|
|
143
147
|
"via_device_id": device.get("via_device_id"),
|
|
@@ -158,6 +162,25 @@ def _get_device_info(device: dict[str, Any]) -> dict[str, Any]:
|
|
|
158
162
|
return device_info
|
|
159
163
|
|
|
160
164
|
|
|
165
|
+
def _device_config_entries(device: dict[str, Any]) -> list[str]:
|
|
166
|
+
"""Return the exact owning config-entry IDs for a main or child row.
|
|
167
|
+
|
|
168
|
+
Ordinary ``DeviceEntry`` rows carry ``config_entries`` while Core 2026.9
|
|
169
|
+
``ChildDeviceEntry`` rows carry one singular ``config_entry_id``. Never mix,
|
|
170
|
+
coerce, or repair malformed ownership evidence: an explicitly present plural
|
|
171
|
+
field is authoritative and must be a list of non-empty strings.
|
|
172
|
+
"""
|
|
173
|
+
if "config_entries" in device:
|
|
174
|
+
raw = device.get("config_entries")
|
|
175
|
+
if not isinstance(raw, list) or not all(
|
|
176
|
+
isinstance(entry_id, str) and entry_id for entry_id in raw
|
|
177
|
+
):
|
|
178
|
+
return []
|
|
179
|
+
return list(raw)
|
|
180
|
+
entry_id = device.get("config_entry_id")
|
|
181
|
+
return [entry_id] if isinstance(entry_id, str) and entry_id else []
|
|
182
|
+
|
|
183
|
+
|
|
161
184
|
def _build_entity_maps(
|
|
162
185
|
all_entities: list[dict[str, Any]], need_full: bool
|
|
163
186
|
) -> tuple[dict[str, str], dict[str, list[dict[str, Any]]]]:
|
|
@@ -251,8 +274,8 @@ async def _fetch_device_rows(client: Any) -> list[dict[str, Any]]:
|
|
|
251
274
|
"""
|
|
252
275
|
result = await fetch_device_list_via_component(client)
|
|
253
276
|
if result is not None:
|
|
254
|
-
return list(result.get("devices", []))
|
|
255
|
-
return await _legacy_device_rows(client)
|
|
277
|
+
return annotate_device_rows_with_effective_area(list(result.get("devices", [])))
|
|
278
|
+
return annotate_device_rows_with_effective_area(await _legacy_device_rows(client))
|
|
256
279
|
|
|
257
280
|
|
|
258
281
|
# HA core replies with this error code (ERR_NOT_FOUND) from
|
|
@@ -342,6 +365,9 @@ async def _single_device_and_entities(
|
|
|
342
365
|
device = result.get("device")
|
|
343
366
|
entities = result.get("entities")
|
|
344
367
|
if isinstance(device, dict) and isinstance(entities, list):
|
|
368
|
+
device = dict(device)
|
|
369
|
+
if "effective_area_id" in result:
|
|
370
|
+
device[EFFECTIVE_AREA_MARKER] = result.get("effective_area_id")
|
|
345
371
|
_, device_to_entities = _build_entity_maps(entities, need_full=True)
|
|
346
372
|
return [device], device_to_entities
|
|
347
373
|
# Either an authoritative not-found (device is None) — fall through so the
|
|
@@ -350,7 +376,9 @@ async def _single_device_and_entities(
|
|
|
350
376
|
# entity registries.
|
|
351
377
|
# Device registry first, then entity registry — the legacy wire order,
|
|
352
378
|
# which the #1297 error-contract test pins with an ordered mock.
|
|
353
|
-
all_devices =
|
|
379
|
+
all_devices = annotate_device_rows_with_effective_area(
|
|
380
|
+
await _legacy_device_rows(client)
|
|
381
|
+
)
|
|
354
382
|
all_entities = await _fetch_entity_rows(client)
|
|
355
383
|
_, device_to_entities = _build_entity_maps(all_entities, need_full=True)
|
|
356
384
|
return all_devices, device_to_entities
|
|
@@ -547,7 +575,7 @@ async def _get_single_device_result(
|
|
|
547
575
|
device_info["serial_number"] = device.get("serial_number")
|
|
548
576
|
device_info["disabled_by"] = device.get("disabled_by")
|
|
549
577
|
device_info["labels"] = device.get("labels", [])
|
|
550
|
-
device_info["config_entries"] = device
|
|
578
|
+
device_info["config_entries"] = _device_config_entries(device)
|
|
551
579
|
device_info["connections"] = device.get("connections", [])
|
|
552
580
|
device_info["identifiers"] = device.get("identifiers", [])
|
|
553
581
|
|
|
@@ -597,7 +625,8 @@ def _filter_devices(
|
|
|
597
625
|
named_types = ["zigbee2mqtt", "zha", "zwave_js"]
|
|
598
626
|
matched: list[dict[str, Any]] = []
|
|
599
627
|
for device in all_devices:
|
|
600
|
-
|
|
628
|
+
effective_area = device.get(EFFECTIVE_AREA_MARKER, device.get("area_id"))
|
|
629
|
+
if area_id and effective_area != area_id:
|
|
601
630
|
continue
|
|
602
631
|
device_man = (device.get("manufacturer") or "").lower()
|
|
603
632
|
if manufacturer_lower and manufacturer_lower not in device_man:
|
|
@@ -1134,7 +1163,7 @@ class RegistryTools:
|
|
|
1134
1163
|
)
|
|
1135
1164
|
device = await _lookup_device_for_remove(self._client, device_id)
|
|
1136
1165
|
|
|
1137
|
-
config_entries = device
|
|
1166
|
+
config_entries = _device_config_entries(device)
|
|
1138
1167
|
device_name = device.get("name_by_user") or device.get("name")
|
|
1139
1168
|
if not config_entries:
|
|
1140
1169
|
raise_tool_error(
|
|
@@ -25,6 +25,10 @@ from ..client.websocket_client import get_websocket_client
|
|
|
25
25
|
from ..config import get_global_settings
|
|
26
26
|
from ..errors import create_validation_error
|
|
27
27
|
from ..transforms.categorized_search import DEFAULT_PINNED_TOOLS
|
|
28
|
+
from ..utils.device_registry_semantics import (
|
|
29
|
+
build_device_registry_snapshot,
|
|
30
|
+
effective_entity_area_id,
|
|
31
|
+
)
|
|
28
32
|
from ..utils.entity_membership import normalize_member_entity_ids
|
|
29
33
|
from ..utils.fuzzy_search import apply_hidden_penalty
|
|
30
34
|
from ..visibility.model import VisibilityWire, wire_has_allowlist_dimensions
|
|
@@ -34,6 +38,7 @@ from ..visibility.resolver import (
|
|
|
34
38
|
visibility_state_and_wire,
|
|
35
39
|
)
|
|
36
40
|
from .component_api import (
|
|
41
|
+
DEVICE_REGISTRY_CHILD_SEMANTICS,
|
|
37
42
|
component_supports,
|
|
38
43
|
get_component_caps,
|
|
39
44
|
invalidate_caps,
|
|
@@ -869,6 +874,15 @@ def _ws_result_map(resp: Any) -> dict[str, dict[str, Any]]:
|
|
|
869
874
|
return {}
|
|
870
875
|
|
|
871
876
|
|
|
877
|
+
def _ws_registry_rows(resp: Any) -> list[Any]:
|
|
878
|
+
"""Return rows from a successful registry-list response, else an empty list."""
|
|
879
|
+
if isinstance(resp, dict) and resp.get("success"):
|
|
880
|
+
result = resp.get("result")
|
|
881
|
+
if isinstance(result, list):
|
|
882
|
+
return result
|
|
883
|
+
return []
|
|
884
|
+
|
|
885
|
+
|
|
872
886
|
def _ws_registry_index(resp: Any, key: str) -> dict[str, dict[str, Any]]:
|
|
873
887
|
"""Index a ``config/*_registry/list`` reply by its id field (area_id/floor_id/…).
|
|
874
888
|
|
|
@@ -877,10 +891,9 @@ def _ws_registry_index(resp: Any, key: str) -> dict[str, dict[str, Any]]:
|
|
|
877
891
|
empty rather than raising.
|
|
878
892
|
"""
|
|
879
893
|
out: dict[str, dict[str, Any]] = {}
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
out[item[key]] = item
|
|
894
|
+
for item in _ws_registry_rows(resp):
|
|
895
|
+
if isinstance(item, dict) and item.get(key):
|
|
896
|
+
out[item[key]] = item
|
|
884
897
|
return out
|
|
885
898
|
|
|
886
899
|
|
|
@@ -902,13 +915,15 @@ def _entity_enrichment_fields(
|
|
|
902
915
|
floors: dict[str, dict[str, Any]],
|
|
903
916
|
labels: dict[str, dict[str, Any]],
|
|
904
917
|
devices: dict[str, dict[str, Any]],
|
|
918
|
+
device_areas: dict[str, str | None],
|
|
905
919
|
requested: tuple[str, ...],
|
|
906
920
|
) -> dict[str, Any]:
|
|
907
921
|
"""Compute the requested enrichment fields for one entity from registry data.
|
|
908
922
|
|
|
909
923
|
Mirrors the component's ``_registry_enrichment`` so the legacy and
|
|
910
924
|
component-served ``result_fields`` values agree: device-inherited area/labels
|
|
911
|
-
(the entity's own value wins, else the device's
|
|
925
|
+
(the entity's own value wins, else the device's direct-or-parent effective
|
|
926
|
+
area), area→floor resolution, and
|
|
912
927
|
label id→name (falling back to the id when a label has no name). ``aliases``
|
|
913
928
|
pass through from the registry entry. Only the requested keys are returned.
|
|
914
929
|
|
|
@@ -919,13 +934,11 @@ def _entity_enrichment_fields(
|
|
|
919
934
|
the sentinel stands for is already matched via the friendly name).
|
|
920
935
|
"""
|
|
921
936
|
aliases = sorted(a for a in (entry.get("aliases") or []) if isinstance(a, str))
|
|
922
|
-
area_id = entry
|
|
937
|
+
area_id = effective_entity_area_id(entry, device_areas)
|
|
923
938
|
label_ids = set(entry.get("labels") or [])
|
|
924
939
|
device_id = entry.get("device_id")
|
|
925
940
|
device = devices.get(device_id) if device_id else None
|
|
926
941
|
if device:
|
|
927
|
-
if area_id is None:
|
|
928
|
-
area_id = device.get("area_id")
|
|
929
942
|
label_ids |= set(device.get("labels") or [])
|
|
930
943
|
area = areas.get(area_id) if area_id else None
|
|
931
944
|
area_name = area.get("name") if area else None
|
|
@@ -2424,9 +2437,13 @@ class SearchTools:
|
|
|
2424
2437
|
and _component_serves_search_types(req)
|
|
2425
2438
|
):
|
|
2426
2439
|
caps = await get_component_caps(self._client)
|
|
2427
|
-
if
|
|
2428
|
-
|
|
2429
|
-
|
|
2440
|
+
if (
|
|
2441
|
+
component_supports(caps, "search")
|
|
2442
|
+
and component_supports(caps, DEVICE_REGISTRY_CHILD_SEMANTICS)
|
|
2443
|
+
and (
|
|
2444
|
+
not _requested_membership(parsed_result_fields)
|
|
2445
|
+
or component_supports(caps, "search_entity_membership")
|
|
2446
|
+
)
|
|
2430
2447
|
):
|
|
2431
2448
|
(
|
|
2432
2449
|
route_component,
|
|
@@ -3153,10 +3170,19 @@ class SearchTools:
|
|
|
3153
3170
|
areas = _ws_registry_index(names[0], "area_id") if need_names else {}
|
|
3154
3171
|
floors = _ws_registry_index(names[1], "floor_id") if need_names else {}
|
|
3155
3172
|
labels = _ws_registry_index(names[2], "label_id") if need_names else {}
|
|
3156
|
-
|
|
3173
|
+
device_rows = _ws_registry_rows(names[3]) if need_names else []
|
|
3174
|
+
device_snapshot = build_device_registry_snapshot(device_rows)
|
|
3175
|
+
devices = device_snapshot.by_id
|
|
3176
|
+
device_areas = device_snapshot.effective_area_by_id
|
|
3157
3177
|
enrichment = {
|
|
3158
3178
|
eid: _entity_enrichment_fields(
|
|
3159
|
-
entries.get(eid) or {},
|
|
3179
|
+
entries.get(eid) or {},
|
|
3180
|
+
areas,
|
|
3181
|
+
floors,
|
|
3182
|
+
labels,
|
|
3183
|
+
devices,
|
|
3184
|
+
device_areas,
|
|
3185
|
+
requested,
|
|
3160
3186
|
)
|
|
3161
3187
|
for eid in entity_ids
|
|
3162
3188
|
}
|
|
@@ -4379,7 +4405,9 @@ class SearchTools:
|
|
|
4379
4405
|
applied by ``ha_get_overview`` after this, identically on both paths.
|
|
4380
4406
|
"""
|
|
4381
4407
|
caps = await get_component_caps(self._client)
|
|
4382
|
-
if component_supports(caps, "overview")
|
|
4408
|
+
if component_supports(caps, "overview") and component_supports(
|
|
4409
|
+
caps, DEVICE_REGISTRY_CHILD_SEMANTICS
|
|
4410
|
+
):
|
|
4383
4411
|
component_result = await self._overview_via_component(inputs)
|
|
4384
4412
|
if component_result is not None:
|
|
4385
4413
|
return component_result
|
{ha_mcp_dev-8.4.2.dev2530 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_voice_assistant.py
RENAMED
|
@@ -24,6 +24,7 @@ from ..client.rest_client import (
|
|
|
24
24
|
from ..client.websocket_client import get_websocket_client
|
|
25
25
|
from ..errors import ErrorCode, create_error_response
|
|
26
26
|
from .component_api import (
|
|
27
|
+
DEVICE_REGISTRY_CHILD_SEMANTICS,
|
|
27
28
|
component_supports,
|
|
28
29
|
get_component_caps,
|
|
29
30
|
invalidate_caps,
|
|
@@ -89,7 +90,10 @@ class VoiceAssistantTools:
|
|
|
89
90
|
``component_devices.fetch_device_via_component``.
|
|
90
91
|
"""
|
|
91
92
|
caps = await get_component_caps(self._client)
|
|
92
|
-
if not
|
|
93
|
+
if not (
|
|
94
|
+
component_supports(caps, "exposure")
|
|
95
|
+
and component_supports(caps, DEVICE_REGISTRY_CHILD_SEMANTICS)
|
|
96
|
+
):
|
|
93
97
|
return None
|
|
94
98
|
kwargs: dict[str, Any] = {}
|
|
95
99
|
if entity_id is not None:
|