ha-mcp-dev 8.3.0.dev2379__tar.gz → 8.3.0.dev2382__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.3.0.dev2379/src/ha_mcp_dev.egg-info → ha_mcp_dev-8.3.0.dev2382}/PKG-INFO +1 -1
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/pyproject.toml +1 -1
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/device_control.py +166 -109
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/smart_search/_base.py +6 -1
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/smart_search/_entities.py +22 -10
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_search.py +10 -7
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_service.py +26 -6
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/LICENSE +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/MANIFEST.in +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/README.md +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/setup.cfg +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/__main__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_pypi_marker +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_rich_compat.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/requirements.txt +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/LICENSE +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/MANIFEST.sha256 +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/VENDORED +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/__main__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/asyncio/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/asyncio/client.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/asyncio/connection.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/asyncio/messages.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/asyncio/router.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/asyncio/server.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/auth.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/cli.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/client.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/connection.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/datastructures.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/exceptions.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/extensions/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/extensions/base.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/extensions/permessage_deflate.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/frames.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/headers.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/http11.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/imports.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/legacy/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/legacy/auth.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/legacy/client.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/legacy/exceptions.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/legacy/framing.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/legacy/handshake.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/legacy/http.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/legacy/protocol.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/legacy/server.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/protocol.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/proxy.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/py.typed +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/server.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/streams.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/sync/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/sync/client.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/sync/connection.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/sync/messages.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/sync/router.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/sync/server.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/sync/utils.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/trio/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/trio/client.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/trio/connection.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/trio/messages.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/trio/router.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/trio/server.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/trio/utils.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/typing.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/uri.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/utils.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/version.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_version.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/auth/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/auth/consent_form.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/auth/oidc_compat.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/auth/provider.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/backup_manager.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/browser_landing.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/client/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/client/rest_client.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/client/supervisor_client.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/client/websocket_client.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/client/websocket_listener.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/config.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/dashboard_screenshot/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/dashboard_screenshot/capture.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/dashboard_screenshot/content.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/dashboard_screenshot/paths.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/dashboard_screenshot/provision.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/dashboard_screenshot/theme_guard.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/errors.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/hacs_auto_refresh.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/llm_exposure.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/policy/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/policy/approval_queue.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/policy/editing.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/policy/evaluator.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/policy/handlers.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/policy/middleware.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/policy/model.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/policy/persistence.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/policy/value_sources.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/py.typed +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/read_only.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/redaction.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/renamed_tools.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/appdaemon.md +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/blueprint-guide.md +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/scenes.md +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/yaml-only-integrations.md +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/server.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/_handlers_advanced.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/_handlers_backups.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/_handlers_fs.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/_handlers_server.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/_handlers_theme.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/_handlers_tools.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/_i18n.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/_persistence.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/_supervisor.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/_theme.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/_tools_meta.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/locales/README.md +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/locales/cs.json +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/locales/de.json +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/locales/en.json +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/locales/eo.json +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/locales/es.json +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/locales/fr.json +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/locales/it.json +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/locales/nl.json +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/locales/pl.json +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/locales/ru.json +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/locales/sv.json +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/locales/zh-Hans.json +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/settings.css +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/settings.html +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/settings.js +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/smoke_test.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/strict_bps.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/auto_backup.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/backup.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/best_practice_checker.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/best_practice_result.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/best_practice_variables.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/component_api.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/component_config_entries.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/component_config_reads.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/component_devices.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/component_registries.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/component_registry_lookup.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/config_entry_flow.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/config_entry_flow_form.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/config_entry_flow_menu.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/config_entry_flow_walker.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/config_entry_identity.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/config_entry_reconfigure.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/config_entry_reload_watch.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/error_log_parsing.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/hacs_registration.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/helpers.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/integration_reconfigure.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/radio/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/radio/base.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/radio/matter.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/radio/thread.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/radio/zigbee.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/radio/zwave.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/reference_validator.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/registry.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/renamed_tool_middleware.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/smart_search/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/smart_search/_config.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/smart_search/_deep.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/smart_search/_fetch.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/smart_search/_overview.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/smart_search/_scenes.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/smart_search/_scoring.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tool_search_hint_middleware.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_addons.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_areas.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_assist_pipeline.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_blueprints.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_bug_report.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_calendar.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_camera.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_categories.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_code.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_config_automations.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_dashboard_screenshot.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_dev.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_energy.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_entities.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_filesystem.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_groups.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_hacs.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_history.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_integrations.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_labels.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_radio.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_registry.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_resources.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_security_policy.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_services.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_system.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_themes.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_todo.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_traces.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_updates.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_utility.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_yaml_read.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_zones.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/util_helpers.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/validation_middleware.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/transforms/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/transforms/categorized_search.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/transport_security.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/update_check.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/utils/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/utils/config_hash.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/utils/config_write_lock.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/utils/data_paths.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/utils/domain_handlers.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/utils/fuzzy_search.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/utils/operation_manager.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/utils/python_sandbox.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/utils/skill_loader.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/utils/usage_logger.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/visibility/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/visibility/enforcement.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/visibility/model.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/visibility/persistence.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/visibility/resolver.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/tests/__init__.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/tests/test_constants.py +0 -0
- {ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/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.3.0.
|
|
7
|
+
version = "8.3.0.dev2382"
|
|
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
|
|
@@ -42,10 +42,23 @@ from .helpers import (
|
|
|
42
42
|
safe_info,
|
|
43
43
|
safe_progress,
|
|
44
44
|
)
|
|
45
|
+
from .tools_service import BulkControlOperation
|
|
45
46
|
from .util_helpers import _SERVICE_TO_STATE
|
|
46
47
|
|
|
47
48
|
logger = logging.getLogger(__name__)
|
|
48
49
|
|
|
50
|
+
# The bulk row contract is declared once, on the advertised transport schema.
|
|
51
|
+
# Reading its keys back here keeps the runtime batch validator from drifting
|
|
52
|
+
# out of sync with what models are told they may send.
|
|
53
|
+
_BULK_OPERATION_KEYS: frozenset[str] = frozenset(BulkControlOperation.__annotations__)
|
|
54
|
+
|
|
55
|
+
# The row shape models get wrong most often is a service-style row, so this
|
|
56
|
+
# leads every suggestion list a rejected bulk batch produces.
|
|
57
|
+
_BULK_ROW_SHAPE_SUGGESTION = (
|
|
58
|
+
"Use entity_id and action (e.g. action='off', not service='turn_off'); "
|
|
59
|
+
"send no domain or service keys and put action data in parameters"
|
|
60
|
+
)
|
|
61
|
+
|
|
49
62
|
# The ha_mcp_tools/bulk_call_service WS command (Phase 3, D5a): the BATCH write
|
|
50
63
|
# capability. When the component advertises ``bulk_call_service`` the consumer
|
|
51
64
|
# resolves every op's domain/service server-side (D6), sends one register-before-fire
|
|
@@ -347,6 +360,10 @@ class DeviceControlTools:
|
|
|
347
360
|
"set": "set_temperature",
|
|
348
361
|
}
|
|
349
362
|
|
|
363
|
+
# Silent allowlist: _add_domain_params copies only these keys into the
|
|
364
|
+
# service call and drops everything else without an error, and a domain
|
|
365
|
+
# absent from this table loses every parameter. Extend it when adding
|
|
366
|
+
# support for a new parameter, or the parameter vanishes at dispatch.
|
|
350
367
|
_DOMAIN_PARAMS: ClassVar[dict[str, list[str]]] = {
|
|
351
368
|
"light": [
|
|
352
369
|
"brightness",
|
|
@@ -474,10 +491,10 @@ class DeviceControlTools:
|
|
|
474
491
|
"""Check status of a device operation, waiting up to ``timeout_seconds`` for completion.
|
|
475
492
|
|
|
476
493
|
Polls the in-memory operation registry (mutated by the WebSocket
|
|
477
|
-
listener as state changes arrive)
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
``control_device_smart``.
|
|
494
|
+
listener as state changes arrive) while the operation is pending, at
|
|
495
|
+
0.2s intervals except for a final shorter poll that lands exactly on
|
|
496
|
+
the deadline. Returns the final structured status —
|
|
497
|
+
completed/failed/timeout/pending — produced by ``control_device_smart``.
|
|
481
498
|
"""
|
|
482
499
|
operation = get_operation_from_memory(operation_id)
|
|
483
500
|
|
|
@@ -607,31 +624,58 @@ class DeviceControlTools:
|
|
|
607
624
|
@staticmethod
|
|
608
625
|
def _normalize_bulk_parameters(
|
|
609
626
|
value: Any,
|
|
610
|
-
) -> tuple[dict[str, Any] | None, ErrorCode | None]:
|
|
611
|
-
"""Normalize
|
|
627
|
+
) -> tuple[dict[str, Any] | None, ErrorCode | None, str]:
|
|
628
|
+
"""Normalize a present ``parameters`` value and identify invalid ones.
|
|
629
|
+
|
|
630
|
+
Returns ``(parameters, error_code, detail)``. ``detail`` carries the
|
|
631
|
+
decoder's own reason and offset for a JSON failure — without it the
|
|
632
|
+
caller can only say "invalid JSON", which does not tell the sender
|
|
633
|
+
where its string went wrong. It is empty for every other outcome.
|
|
634
|
+
"""
|
|
612
635
|
if isinstance(value, str):
|
|
613
636
|
try:
|
|
614
637
|
value = json.loads(value)
|
|
615
|
-
except json.JSONDecodeError:
|
|
616
|
-
return
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
638
|
+
except json.JSONDecodeError as exc:
|
|
639
|
+
return (
|
|
640
|
+
None,
|
|
641
|
+
ErrorCode.VALIDATION_INVALID_JSON,
|
|
642
|
+
f"{exc.msg} at position {exc.pos}",
|
|
643
|
+
)
|
|
644
|
+
except RecursionError:
|
|
645
|
+
# json.loads recurses per nesting level; a pathologically
|
|
646
|
+
# nested string must skip the row, not abort the batch.
|
|
647
|
+
return (
|
|
648
|
+
None,
|
|
649
|
+
ErrorCode.VALIDATION_INVALID_JSON,
|
|
650
|
+
"JSON nested too deeply",
|
|
651
|
+
)
|
|
652
|
+
if not isinstance(value, dict):
|
|
653
|
+
# A present-but-null value is malformed, same as timeout_seconds
|
|
654
|
+
# and validate_first; an absent key never reaches this helper.
|
|
655
|
+
return None, ErrorCode.VALIDATION_INVALID_PARAMETER, ""
|
|
656
|
+
return value, None, ""
|
|
620
657
|
|
|
621
658
|
@staticmethod
|
|
622
|
-
def _normalize_bulk_timeout(value: Any) ->
|
|
623
|
-
"""Normalize
|
|
624
|
-
|
|
625
|
-
|
|
659
|
+
def _normalize_bulk_timeout(value: Any) -> float | None:
|
|
660
|
+
"""Normalize a non-negative finite timeout, or ``None`` when invalid.
|
|
661
|
+
|
|
662
|
+
Bools are rejected before ``float()`` sees them: ``float(True)`` is 1.0,
|
|
663
|
+
which would silently accept ``timeout_seconds: true`` as a one-second
|
|
664
|
+
wait. Strings are rejected for the same reason — the advertised schema
|
|
665
|
+
is ``strict``, so coercing ``"10"`` here would silently accept what the
|
|
666
|
+
schema tells the model is invalid. ``None`` is invalid here too — the
|
|
667
|
+
caller only calls this when the key is present, and a present-but-null
|
|
668
|
+
timeout is a malformed row.
|
|
669
|
+
"""
|
|
670
|
+
if isinstance(value, (bool, str)) or value is None:
|
|
671
|
+
return None
|
|
626
672
|
try:
|
|
627
673
|
timeout = float(value)
|
|
628
|
-
except (TypeError, ValueError):
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
else (None, False)
|
|
634
|
-
)
|
|
674
|
+
except (TypeError, ValueError, OverflowError):
|
|
675
|
+
# OverflowError: an int too large for float must skip the row,
|
|
676
|
+
# not abort the batch through the generic exception handler.
|
|
677
|
+
return None
|
|
678
|
+
return timeout if math.isfinite(timeout) and timeout >= 0 else None
|
|
635
679
|
|
|
636
680
|
@staticmethod
|
|
637
681
|
def _normalized_bulk_operation(
|
|
@@ -647,6 +691,28 @@ class DeviceControlTools:
|
|
|
647
691
|
normalized["timeout_seconds"] = timeout
|
|
648
692
|
return normalized
|
|
649
693
|
|
|
694
|
+
@staticmethod
|
|
695
|
+
def _skip_bulk_operation(
|
|
696
|
+
skipped_operations: list[dict[str, Any]],
|
|
697
|
+
op: Any,
|
|
698
|
+
code: ErrorCode,
|
|
699
|
+
error: str,
|
|
700
|
+
context: dict[str, Any],
|
|
701
|
+
suggestions: list[str] | None = None,
|
|
702
|
+
) -> None:
|
|
703
|
+
"""Log one rejected bulk row and record its structured skip entry.
|
|
704
|
+
|
|
705
|
+
``create_error_response`` lifts every ``context`` key to the top level,
|
|
706
|
+
so ``index`` needs no separate write; ``operation`` is not a context
|
|
707
|
+
key and is attached here so callers can see the row they sent.
|
|
708
|
+
"""
|
|
709
|
+
logger.warning(f"Bulk control: {error}")
|
|
710
|
+
err_response = create_error_response(
|
|
711
|
+
code, error, suggestions=suggestions, context=context
|
|
712
|
+
)
|
|
713
|
+
err_response["operation"] = op
|
|
714
|
+
skipped_operations.append(err_response)
|
|
715
|
+
|
|
650
716
|
@classmethod
|
|
651
717
|
def _validate_bulk_operations(
|
|
652
718
|
cls,
|
|
@@ -656,41 +722,28 @@ class DeviceControlTools:
|
|
|
656
722
|
valid: list[tuple[int, dict[str, Any], str, str]] = []
|
|
657
723
|
for i, op in enumerate(operations):
|
|
658
724
|
if not isinstance(op, dict):
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
ErrorCode.
|
|
725
|
+
cls._skip_bulk_operation(
|
|
726
|
+
skipped_operations,
|
|
727
|
+
op,
|
|
728
|
+
ErrorCode.VALIDATION_INVALID_PARAMETER,
|
|
729
|
+
f"Operation at index {i} is not a dict: {type(op).__name__}",
|
|
730
|
+
{"index": i},
|
|
663
731
|
)
|
|
664
|
-
err_response["index"] = i
|
|
665
|
-
err_response["operation"] = op
|
|
666
|
-
skipped_operations.append(err_response)
|
|
667
732
|
continue
|
|
668
733
|
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
"parameters",
|
|
673
|
-
"timeout_seconds",
|
|
674
|
-
"validate_first",
|
|
675
|
-
}
|
|
676
|
-
obsolete_keys = sorted(set(op) - allowed_keys)
|
|
734
|
+
# str() before sorting: a direct Python caller can pass non-string
|
|
735
|
+
# keys, and mixed key types make sorted()/join() raise mid-batch.
|
|
736
|
+
obsolete_keys = sorted(str(key) for key in set(op) - _BULK_OPERATION_KEYS)
|
|
677
737
|
if obsolete_keys:
|
|
678
738
|
key_list = ", ".join(obsolete_keys)
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
logger.warning(f"Bulk control: {error}")
|
|
683
|
-
err_response = create_error_response(
|
|
739
|
+
cls._skip_bulk_operation(
|
|
740
|
+
skipped_operations,
|
|
741
|
+
op,
|
|
684
742
|
ErrorCode.VALIDATION_INVALID_PARAMETER,
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
],
|
|
689
|
-
context={"index": i, "unsupported_keys": obsolete_keys},
|
|
743
|
+
f"Operation at index {i} contains unsupported key(s): {key_list}",
|
|
744
|
+
{"index": i, "unsupported_keys": obsolete_keys},
|
|
745
|
+
suggestions=[_BULK_ROW_SHAPE_SUGGESTION],
|
|
690
746
|
)
|
|
691
|
-
err_response["index"] = i
|
|
692
|
-
err_response["operation"] = op
|
|
693
|
-
skipped_operations.append(err_response)
|
|
694
747
|
continue
|
|
695
748
|
|
|
696
749
|
entity_id = op.get("entity_id")
|
|
@@ -698,83 +751,70 @@ class DeviceControlTools:
|
|
|
698
751
|
missing = [f for f in ("entity_id", "action") if not op.get(f)]
|
|
699
752
|
|
|
700
753
|
if missing:
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
ErrorCode.VALIDATION_MISSING_PARAMETER,
|
|
754
|
+
cls._skip_bulk_operation(
|
|
755
|
+
skipped_operations,
|
|
756
|
+
op,
|
|
757
|
+
ErrorCode.VALIDATION_MISSING_PARAMETER,
|
|
758
|
+
f"Operation at index {i} missing required fields: "
|
|
759
|
+
f"{', '.join(missing)}",
|
|
760
|
+
{"index": i},
|
|
705
761
|
)
|
|
706
|
-
err_response["index"] = i
|
|
707
|
-
err_response["operation"] = op
|
|
708
|
-
skipped_operations.append(err_response)
|
|
709
762
|
continue
|
|
710
763
|
|
|
711
764
|
if not isinstance(entity_id, str) or not isinstance(action, str):
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
765
|
+
cls._skip_bulk_operation(
|
|
766
|
+
skipped_operations,
|
|
767
|
+
op,
|
|
715
768
|
ErrorCode.VALIDATION_INVALID_PARAMETER,
|
|
716
|
-
|
|
717
|
-
|
|
769
|
+
f"Operation at index {i} requires string entity_id and "
|
|
770
|
+
"action fields",
|
|
771
|
+
{"index": i},
|
|
718
772
|
)
|
|
719
|
-
err_response["index"] = i
|
|
720
|
-
err_response["operation"] = op
|
|
721
|
-
skipped_operations.append(err_response)
|
|
722
773
|
continue
|
|
723
774
|
|
|
724
|
-
parameters, parameter_error =
|
|
725
|
-
|
|
775
|
+
parameters, parameter_error, parameter_detail = (
|
|
776
|
+
cls._normalize_bulk_parameters(op["parameters"])
|
|
777
|
+
if "parameters" in op
|
|
778
|
+
else (None, None, "")
|
|
726
779
|
)
|
|
727
780
|
if parameter_error is not None:
|
|
728
|
-
|
|
729
|
-
f"Operation at index {i} has invalid JSON parameters"
|
|
781
|
+
parameter_message = (
|
|
782
|
+
f"Operation at index {i} has invalid JSON parameters: "
|
|
783
|
+
f"{parameter_detail}"
|
|
730
784
|
if parameter_error == ErrorCode.VALIDATION_INVALID_JSON
|
|
731
785
|
else f"Operation at index {i} parameters must be a JSON object"
|
|
732
786
|
)
|
|
733
|
-
|
|
734
|
-
|
|
787
|
+
cls._skip_bulk_operation(
|
|
788
|
+
skipped_operations,
|
|
789
|
+
op,
|
|
735
790
|
parameter_error,
|
|
736
|
-
|
|
737
|
-
|
|
791
|
+
parameter_message,
|
|
792
|
+
{"index": i},
|
|
738
793
|
)
|
|
739
|
-
err_response["index"] = i
|
|
740
|
-
err_response["operation"] = op
|
|
741
|
-
skipped_operations.append(err_response)
|
|
742
794
|
continue
|
|
743
795
|
|
|
744
|
-
timeout = None
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
timeout
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
err_response = create_error_response(
|
|
758
|
-
ErrorCode.VALIDATION_INVALID_PARAMETER,
|
|
759
|
-
error,
|
|
760
|
-
context={"index": i},
|
|
761
|
-
)
|
|
762
|
-
err_response["index"] = i
|
|
763
|
-
err_response["operation"] = op
|
|
764
|
-
skipped_operations.append(err_response)
|
|
765
|
-
continue
|
|
796
|
+
timeout: float | None = None
|
|
797
|
+
if "timeout_seconds" in op:
|
|
798
|
+
timeout = cls._normalize_bulk_timeout(op["timeout_seconds"])
|
|
799
|
+
if timeout is None:
|
|
800
|
+
cls._skip_bulk_operation(
|
|
801
|
+
skipped_operations,
|
|
802
|
+
op,
|
|
803
|
+
ErrorCode.VALIDATION_INVALID_PARAMETER,
|
|
804
|
+
f"Operation at index {i} timeout_seconds must be a "
|
|
805
|
+
"non-negative number",
|
|
806
|
+
{"index": i},
|
|
807
|
+
)
|
|
808
|
+
continue
|
|
766
809
|
|
|
767
|
-
if "validate_first" in op and
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
810
|
+
if "validate_first" in op and not isinstance(op["validate_first"], bool):
|
|
811
|
+
cls._skip_bulk_operation(
|
|
812
|
+
skipped_operations,
|
|
813
|
+
op,
|
|
771
814
|
ErrorCode.VALIDATION_INVALID_PARAMETER,
|
|
772
|
-
|
|
773
|
-
|
|
815
|
+
f"Operation at index {i} validate_first must be a boolean",
|
|
816
|
+
{"index": i},
|
|
774
817
|
)
|
|
775
|
-
err_response["index"] = i
|
|
776
|
-
err_response["operation"] = op
|
|
777
|
-
skipped_operations.append(err_response)
|
|
778
818
|
continue
|
|
779
819
|
|
|
780
820
|
normalized_op = cls._normalized_bulk_operation(op, parameters, timeout)
|
|
@@ -824,8 +864,24 @@ class DeviceControlTools:
|
|
|
824
864
|
f"mode={'parallel' if parallel else 'sequential'}",
|
|
825
865
|
)
|
|
826
866
|
if not valid_operations:
|
|
827
|
-
|
|
828
|
-
|
|
867
|
+
# Nothing dispatched, so nothing to fail soft for: the batch
|
|
868
|
+
# carve-out from "all tool-level failures raise" exists to
|
|
869
|
+
# protect successful siblings, and there are none. Raising
|
|
870
|
+
# before the component probe keeps the no-dispatch guarantee.
|
|
871
|
+
raise_tool_error(
|
|
872
|
+
create_error_response(
|
|
873
|
+
ErrorCode.VALIDATION_INVALID_PARAMETER,
|
|
874
|
+
f"All {len(operations)} operation(s) failed validation; "
|
|
875
|
+
"nothing was dispatched",
|
|
876
|
+
suggestions=[
|
|
877
|
+
_BULK_ROW_SHAPE_SUGGESTION,
|
|
878
|
+
"Each operation requires 'entity_id' and 'action'",
|
|
879
|
+
"Check skipped_details for the per-operation reason",
|
|
880
|
+
"Example: {'entity_id': 'light.living_room', "
|
|
881
|
+
+ "'action': 'on'}",
|
|
882
|
+
],
|
|
883
|
+
context={"skipped_details": skipped_operations},
|
|
884
|
+
)
|
|
829
885
|
)
|
|
830
886
|
|
|
831
887
|
# Route through the component's bulk_call_service capability when
|
|
@@ -1439,6 +1495,7 @@ class DeviceControlTools:
|
|
|
1439
1495
|
if skipped_operations:
|
|
1440
1496
|
response["skipped_details"] = skipped_operations
|
|
1441
1497
|
response["suggestions"] = [
|
|
1498
|
+
_BULK_ROW_SHAPE_SUGGESTION,
|
|
1442
1499
|
"Some operations were skipped due to validation errors",
|
|
1443
1500
|
"Each operation requires 'entity_id' and 'action' fields",
|
|
1444
1501
|
"Check skipped_details for specific errors",
|
{ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/smart_search/_base.py
RENAMED
|
@@ -40,9 +40,14 @@ class _SearchBase:
|
|
|
40
40
|
logger.debug(f"Could not fetch {label}: {result}")
|
|
41
41
|
cause = str(result) or type(result).__name__
|
|
42
42
|
elif isinstance(result, dict) and result.get("success"):
|
|
43
|
-
|
|
43
|
+
# No default for a missing key: a legitimate empty registry is
|
|
44
|
+
# "result": [], so an absent result is a malformed envelope and
|
|
45
|
+
# must degrade loudly, not read as available-and-empty.
|
|
46
|
+
payload = result.get("result")
|
|
44
47
|
if isinstance(payload, list):
|
|
45
48
|
registry = payload
|
|
49
|
+
elif payload is None and "result" not in result:
|
|
50
|
+
cause = "malformed result payload: result key missing"
|
|
46
51
|
else:
|
|
47
52
|
cause = (
|
|
48
53
|
"malformed result payload: expected list, "
|
{ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/smart_search/_entities.py
RENAMED
|
@@ -473,13 +473,15 @@ class EntitySearchMixin(_SearchBase):
|
|
|
473
473
|
area_registry = self._parse_area_registry(results[1], registry_warnings)
|
|
474
474
|
entity_reg_map = self._parse_entity_reg_map(results[2], registry_warnings)
|
|
475
475
|
device_area_map = self._parse_device_area_map(results[3], registry_warnings)
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
floor_registry = self._parse_floor_registry(
|
|
476
|
+
# Availability is read back OUT of the parse rather than re-derived
|
|
477
|
+
# from the raw payload: the parser already decides what counts as
|
|
478
|
+
# usable floor data, and a second predicate here drifted from it
|
|
479
|
+
# once. An empty warnings list means the parser accepted the
|
|
480
|
+
# payload; anything else disables the fuzzy floor/area fallback.
|
|
481
|
+
floor_warnings: list[str] = []
|
|
482
|
+
floor_registry = self._parse_floor_registry(results[4], floor_warnings)
|
|
483
|
+
floor_registry_available = not floor_warnings
|
|
484
|
+
registry_warnings.extend(floor_warnings)
|
|
483
485
|
degraded_warnings = registry_warnings + visibility_warnings
|
|
484
486
|
|
|
485
487
|
matched_area_ids, resolution_warnings = self._resolve_area_query(
|
|
@@ -657,8 +659,15 @@ class EntitySearchMixin(_SearchBase):
|
|
|
657
659
|
|
|
658
660
|
if not floor_registry_available:
|
|
659
661
|
# Without the floor registry, fuzzy area matching could silently
|
|
660
|
-
# recreate the original floor->partial-area misresolution.
|
|
661
|
-
|
|
662
|
+
# recreate the original floor->partial-area misresolution. The
|
|
663
|
+
# fetch warning names the outage; this one names what the outage
|
|
664
|
+
# cost the caller, so "no match" is not read as "no such area".
|
|
665
|
+
return set(), [
|
|
666
|
+
f"Floor data is unavailable, so '{area_query}' was matched only "
|
|
667
|
+
"against exact area IDs, names, and aliases; close-spelling "
|
|
668
|
+
"matching was skipped. Retry, or pass an exact area_id from "
|
|
669
|
+
"ha_list_floors_areas."
|
|
670
|
+
]
|
|
662
671
|
|
|
663
672
|
fuzzy_floor_ids, floor_score = cls._match_fuzzy_registry_ids(
|
|
664
673
|
floor_registry, "floor_id", query_lower
|
|
@@ -768,7 +777,10 @@ class EntitySearchMixin(_SearchBase):
|
|
|
768
777
|
"""Resolve the query to area IDs: exact id/name/alias, then fuzzy.
|
|
769
778
|
|
|
770
779
|
Exact matches delegate to the shared registry matcher so area and floor
|
|
771
|
-
precedence use identical case-insensitive semantics.
|
|
780
|
+
precedence use identical case-insensitive semantics. Exact must win
|
|
781
|
+
outright: a query like "bedroom_kids" partial-matches its parent
|
|
782
|
+
"bedroom" at ratio 100, so falling through to the fuzzy pass would
|
|
783
|
+
aggregate every sibling area's entities under an exact hit.
|
|
772
784
|
"""
|
|
773
785
|
exact_area_ids = cls._match_exact_registry_ids(
|
|
774
786
|
area_registry, "area_id", area_query_lower
|
|
@@ -321,12 +321,11 @@ def _finalize_partial_state(
|
|
|
321
321
|
if partial_local:
|
|
322
322
|
response["partial"] = True
|
|
323
323
|
response["errors"].extend(errors_local)
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
f"{existing_reason}; {local_reason}" if existing_reason else local_reason
|
|
324
|
+
_merge_partial_reason(
|
|
325
|
+
response,
|
|
326
|
+
"; ".join(
|
|
327
|
+
f"{error['surface']}: {error['error']}" for error in errors_local
|
|
328
|
+
),
|
|
330
329
|
)
|
|
331
330
|
|
|
332
331
|
|
|
@@ -2217,7 +2216,11 @@ class SearchTools:
|
|
|
2217
2216
|
raise outcome
|
|
2218
2217
|
if isinstance(outcome, Exception):
|
|
2219
2218
|
partial = True
|
|
2220
|
-
|
|
2219
|
+
# ``str(asyncio.TimeoutError())`` is "" — fall back to the type
|
|
2220
|
+
# name so partial_reason never reads "entities: ".
|
|
2221
|
+
errors.append(
|
|
2222
|
+
{"surface": label, "error": str(outcome) or type(outcome).__name__}
|
|
2223
|
+
)
|
|
2221
2224
|
logger.warning("ha_search %s branch failed: %r", label, outcome)
|
|
2222
2225
|
continue
|
|
2223
2226
|
_apply_search_outcome(response, label, outcome)
|
|
@@ -83,7 +83,10 @@ class BulkControlOperation(TypedDict):
|
|
|
83
83
|
Field(
|
|
84
84
|
description=(
|
|
85
85
|
"Optional action parameters, e.g. {'brightness_pct': 30} "
|
|
86
|
-
"when action='on'."
|
|
86
|
+
"when action='on'. Each domain has a fixed allowlist of "
|
|
87
|
+
"supported keys; keys outside it are ignored rather than "
|
|
88
|
+
"rejected. Use ha_call_service for parameters this tool "
|
|
89
|
+
"does not carry."
|
|
87
90
|
)
|
|
88
91
|
),
|
|
89
92
|
]
|
|
@@ -94,6 +97,10 @@ class BulkControlOperation(TypedDict):
|
|
|
94
97
|
Field(
|
|
95
98
|
ge=0,
|
|
96
99
|
allow_inf_nan=False,
|
|
100
|
+
# ``strict`` for the same reason validate_first carries it:
|
|
101
|
+
# lax mode coerces ``true`` to 1.0, so a bool would land
|
|
102
|
+
# downstream as a silent one-second timeout.
|
|
103
|
+
strict=True,
|
|
97
104
|
description=(
|
|
98
105
|
"Optional confirmation timeout. On the component path, all "
|
|
99
106
|
"operations share the maximum requested wait (default 10s, "
|
|
@@ -108,8 +115,10 @@ class BulkControlOperation(TypedDict):
|
|
|
108
115
|
Field(
|
|
109
116
|
strict=True,
|
|
110
117
|
description=(
|
|
111
|
-
"
|
|
112
|
-
"
|
|
118
|
+
"Report an ENTITY_NOT_FOUND failure when the target entity "
|
|
119
|
+
"does not exist; default true. On the component batch path "
|
|
120
|
+
"this is detected from the captured pre-state rather than by "
|
|
121
|
+
"preventing dispatch. The action is always validated."
|
|
113
122
|
),
|
|
114
123
|
),
|
|
115
124
|
]
|
|
@@ -1531,7 +1540,8 @@ class ServiceTools:
|
|
|
1531
1540
|
|
|
1532
1541
|
Caveats: put every target in ``operations`` and call the tool once. Parallel
|
|
1533
1542
|
execution is the default, and invalid items are reported without aborting
|
|
1534
|
-
valid operations in the same batch.
|
|
1543
|
+
valid operations in the same batch. A batch in which every item fails
|
|
1544
|
+
validation dispatches nothing and fails the call.
|
|
1535
1545
|
"""
|
|
1536
1546
|
parallel_bool = parallel
|
|
1537
1547
|
|
|
@@ -1559,14 +1569,24 @@ class ServiceTools:
|
|
|
1559
1569
|
)
|
|
1560
1570
|
|
|
1561
1571
|
operations_list: list[Any] = []
|
|
1562
|
-
for operation in parsed_operations:
|
|
1572
|
+
for index, operation in enumerate(parsed_operations):
|
|
1563
1573
|
try:
|
|
1564
1574
|
operations_list.append(
|
|
1565
1575
|
_BULK_CONTROL_OPERATION_ADAPTER.validate_python(operation)
|
|
1566
1576
|
)
|
|
1567
|
-
except ValidationError:
|
|
1577
|
+
except ValidationError as exc:
|
|
1568
1578
|
# Preserve malformed rows for the runtime batch validator, which
|
|
1569
1579
|
# reports them in skipped_details instead of rejecting the call.
|
|
1580
|
+
# The schema's reason is richer than the runtime validator's and
|
|
1581
|
+
# is the only place it survives, so log it here.
|
|
1582
|
+
# include_input=False: a malformed row can carry sensitive
|
|
1583
|
+
# values (lock or alarm codes in a mistyped field), and
|
|
1584
|
+
# str(exc) would write them to persistent server logs.
|
|
1585
|
+
logger.warning(
|
|
1586
|
+
"ha_bulk_control operation %d failed schema validation: %s",
|
|
1587
|
+
index,
|
|
1588
|
+
exc.errors(include_url=False, include_input=False),
|
|
1589
|
+
)
|
|
1570
1590
|
operations_list.append(operation)
|
|
1571
1591
|
|
|
1572
1592
|
result = await self._device_tools.bulk_device_control(
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/MANIFEST.sha256
RENAMED
|
File without changes
|
{ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/VENDORED
RENAMED
|
File without changes
|
{ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/__init__.py
RENAMED
|
File without changes
|
{ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/__main__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/client.py
RENAMED
|
File without changes
|
{ha_mcp_dev-8.3.0.dev2379 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/connection.py
RENAMED
|
File without changes
|
|
File without changes
|