ha-mcp-dev 8.2.0.dev2364__tar.gz → 8.2.0.dev2367__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.2.0.dev2364/src/ha_mcp_dev.egg-info → ha_mcp_dev-8.2.0.dev2367}/PKG-INFO +1 -1
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/pyproject.toml +1 -1
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/policy/middleware.py +11 -9
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/read_only.py +10 -8
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/settings_ui/__init__.py +19 -15
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/settings_ui/locales/en.json +4 -2
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/settings_ui/settings.html +4 -2
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/settings_ui/settings.js +2 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/stdio_settings_sidecar.py +274 -1
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/visibility/enforcement.py +113 -34
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/visibility/model.py +14 -6
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/LICENSE +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/MANIFEST.in +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/README.md +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/setup.cfg +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/__init__.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/__main__.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_pypi_marker +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_rich_compat.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/__init__.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/requirements.txt +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/LICENSE +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/MANIFEST.sha256 +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/VENDORED +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/__init__.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/__main__.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/asyncio/__init__.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/asyncio/client.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/asyncio/connection.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/asyncio/messages.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/asyncio/router.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/asyncio/server.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/auth.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/cli.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/client.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/connection.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/datastructures.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/exceptions.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/extensions/__init__.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/extensions/base.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/extensions/permessage_deflate.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/frames.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/headers.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/http11.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/imports.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/legacy/__init__.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/legacy/auth.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/legacy/client.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/legacy/exceptions.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/legacy/framing.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/legacy/handshake.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/legacy/http.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/legacy/protocol.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/legacy/server.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/protocol.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/proxy.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/py.typed +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/server.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/streams.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/sync/__init__.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/sync/client.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/sync/connection.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/sync/messages.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/sync/router.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/sync/server.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/sync/utils.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/trio/__init__.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/trio/client.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/trio/connection.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/trio/messages.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/trio/router.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/trio/server.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/trio/utils.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/typing.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/uri.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/utils.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/version.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_version.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/auth/__init__.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/auth/consent_form.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/auth/oidc_compat.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/auth/provider.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/backup_manager.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/browser_landing.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/client/__init__.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/client/rest_client.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/client/supervisor_client.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/client/websocket_client.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/client/websocket_listener.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/config.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/dashboard_screenshot/__init__.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/dashboard_screenshot/capture.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/dashboard_screenshot/content.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/dashboard_screenshot/paths.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/dashboard_screenshot/provision.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/dashboard_screenshot/theme_guard.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/errors.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/hacs_auto_refresh.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/llm_exposure.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/policy/__init__.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/policy/approval_queue.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/policy/editing.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/policy/evaluator.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/policy/handlers.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/policy/model.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/policy/persistence.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/policy/value_sources.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/py.typed +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/redaction.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/renamed_tools.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/appdaemon.md +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/blueprint-guide.md +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/scenes.md +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/yaml-only-integrations.md +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/server.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/settings_ui/_handlers_advanced.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/settings_ui/_handlers_backups.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/settings_ui/_handlers_fs.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/settings_ui/_handlers_server.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/settings_ui/_handlers_theme.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/settings_ui/_handlers_tools.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/settings_ui/_i18n.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/settings_ui/_persistence.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/settings_ui/_supervisor.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/settings_ui/_theme.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/settings_ui/_tools_meta.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/settings_ui/locales/README.md +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/settings_ui/locales/cs.json +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/settings_ui/locales/de.json +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/settings_ui/locales/eo.json +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/settings_ui/locales/es.json +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/settings_ui/locales/fr.json +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/settings_ui/locales/it.json +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/settings_ui/locales/nl.json +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/settings_ui/locales/pl.json +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/settings_ui/locales/ru.json +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/settings_ui/locales/sv.json +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/settings_ui/locales/zh-Hans.json +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/settings_ui/settings.css +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/smoke_test.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/strict_bps.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/__init__.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/auto_backup.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/backup.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/best_practice_checker.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/best_practice_result.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/best_practice_variables.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/component_api.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/component_config_entries.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/component_config_reads.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/component_devices.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/component_registries.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/component_registry_lookup.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/config_entry_flow.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/config_entry_flow_form.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/config_entry_flow_menu.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/config_entry_flow_walker.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/config_entry_identity.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/config_entry_reconfigure.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/config_entry_reload_watch.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/device_control.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/error_log_parsing.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/hacs_registration.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/helpers.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/integration_reconfigure.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/radio/__init__.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/radio/base.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/radio/matter.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/radio/thread.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/radio/zigbee.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/radio/zwave.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/reference_validator.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/registry.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/renamed_tool_middleware.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/smart_search/__init__.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/smart_search/_base.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/smart_search/_config.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/smart_search/_deep.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/smart_search/_entities.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/smart_search/_fetch.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/smart_search/_overview.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/smart_search/_scenes.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/smart_search/_scoring.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tool_search_hint_middleware.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_addons.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_areas.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_assist_pipeline.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_blueprints.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_bug_report.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_calendar.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_camera.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_categories.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_code.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_config_automations.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_dashboard_screenshot.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_dev.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_energy.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_entities.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_filesystem.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_groups.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_hacs.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_history.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_integrations.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_labels.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_radio.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_registry.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_resources.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_search.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_security_policy.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_service.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_services.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_system.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_themes.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_todo.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_traces.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_updates.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_utility.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_yaml_read.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_zones.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/util_helpers.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/validation_middleware.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/transforms/__init__.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/transforms/categorized_search.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/transport_security.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/update_check.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/utils/__init__.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/utils/config_hash.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/utils/config_write_lock.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/utils/data_paths.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/utils/domain_handlers.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/utils/fuzzy_search.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/utils/operation_manager.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/utils/python_sandbox.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/utils/skill_loader.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/utils/usage_logger.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/visibility/__init__.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/visibility/persistence.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/visibility/resolver.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/tests/__init__.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/tests/test_constants.py +0 -0
- {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/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.2.0.
|
|
7
|
+
version = "8.2.0.dev2367"
|
|
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
|
|
@@ -20,21 +20,23 @@ from .model import Policy, Rule
|
|
|
20
20
|
|
|
21
21
|
logger = logging.getLogger(__name__)
|
|
22
22
|
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
#
|
|
28
|
-
#
|
|
29
|
-
#
|
|
30
|
-
|
|
23
|
+
# Dispatching call proxies are a strict subset of the ungated tool-search
|
|
24
|
+
# meta-tools. Only these three execute their envelope's ``name``; search merely
|
|
25
|
+
# returns catalog metadata and must not be unwrapped by other middleware.
|
|
26
|
+
# Gating a call proxy directly would be wrong: rule predicates target the real
|
|
27
|
+
# tool's args (for example args.domain), while the proxy receives a wrapped
|
|
28
|
+
# {"name": "...", "arguments": {...}} envelope. Its dispatch re-enters the
|
|
29
|
+
# middleware chain with the real name and args, so the inner call is gated there.
|
|
30
|
+
CALL_PROXY_META_TOOLS = frozenset(
|
|
31
31
|
{
|
|
32
32
|
"ha_call_read_tool",
|
|
33
33
|
"ha_call_write_tool",
|
|
34
34
|
"ha_call_delete_tool",
|
|
35
|
-
"ha_search_tools",
|
|
36
35
|
}
|
|
37
36
|
)
|
|
37
|
+
# Policy itself also leaves catalog search ungated; unlike the call proxies it
|
|
38
|
+
# never dispatches a tool named in client-supplied arguments.
|
|
39
|
+
PROXY_META_TOOLS = CALL_PROXY_META_TOOLS | {"ha_search_tools"}
|
|
38
40
|
|
|
39
41
|
# ha_dev_manage_server actions that MANAGE the approval queue itself.
|
|
40
42
|
# Gating these deadlocks by construction: with a wildcard (or
|
|
@@ -39,7 +39,7 @@ from fastmcp.tools import Tool
|
|
|
39
39
|
|
|
40
40
|
from .config import get_global_settings
|
|
41
41
|
from .errors import ErrorCode, create_error_response
|
|
42
|
-
from .policy.middleware import
|
|
42
|
+
from .policy.middleware import CALL_PROXY_META_TOOLS
|
|
43
43
|
from .renamed_tools import current_tool_name
|
|
44
44
|
from .tools.helpers import raise_tool_error
|
|
45
45
|
|
|
@@ -428,7 +428,7 @@ class ReadOnlyMiddleware(Middleware):
|
|
|
428
428
|
arguments = cls._coerce_arguments(args.get("arguments"))
|
|
429
429
|
while (
|
|
430
430
|
isinstance(name, str)
|
|
431
|
-
and name in
|
|
431
|
+
and name in CALL_PROXY_META_TOOLS
|
|
432
432
|
and isinstance(arguments, dict)
|
|
433
433
|
and isinstance(arguments.get("name"), str)
|
|
434
434
|
):
|
|
@@ -460,12 +460,14 @@ class ReadOnlyMiddleware(Middleware):
|
|
|
460
460
|
args = context.message.arguments or {}
|
|
461
461
|
|
|
462
462
|
# Call proxies: decide on the INNER call (see _unwrap_proxy_call).
|
|
463
|
-
#
|
|
464
|
-
#
|
|
465
|
-
#
|
|
466
|
-
#
|
|
467
|
-
#
|
|
468
|
-
|
|
463
|
+
# Envelope-less proxy calls pass through because the proxy raises its own
|
|
464
|
+
# validation error for a missing inner name. When the inner call is
|
|
465
|
+
# allowed, proxy dispatch re-enters this middleware with the real tool name
|
|
466
|
+
# anyway (harmless re-check, same verdict). ``ha_search_tools`` is not a
|
|
467
|
+
# dispatch proxy; it reaches generic classification below as an unknown
|
|
468
|
+
# synthetic tool, which passes through to the search transform (or the
|
|
469
|
+
# stale-tool hint path when search is disabled).
|
|
470
|
+
if name in CALL_PROXY_META_TOOLS:
|
|
469
471
|
unwrapped = self._unwrap_proxy_call(args)
|
|
470
472
|
if unwrapped is None:
|
|
471
473
|
return await call_next(context)
|
|
@@ -312,21 +312,25 @@ def _build_visibility_handlers(*, data_dir: Path) -> dict[str, Any]:
|
|
|
312
312
|
new_config = VisibilityConfig.model_validate(await request.json())
|
|
313
313
|
except (ValidationError, ValueError) as e:
|
|
314
314
|
return JSONResponse({"error": str(e)}, status_code=400)
|
|
315
|
-
#
|
|
316
|
-
#
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
315
|
+
# The guard makes the version check a real cross-process
|
|
316
|
+
# compare-and-swap: the stdio settings sidecar and main MCP process can
|
|
317
|
+
# both write this file.
|
|
318
|
+
from ..utils.config_write_lock import config_write_guard
|
|
319
|
+
|
|
320
|
+
async with config_write_guard():
|
|
321
|
+
current = load_visibility_config(data_dir)
|
|
322
|
+
if new_config.version != current.version:
|
|
323
|
+
return JSONResponse(
|
|
324
|
+
{
|
|
325
|
+
"error": (
|
|
326
|
+
"Visibility config version mismatch. Reload before saving."
|
|
327
|
+
),
|
|
328
|
+
"current_version": current.version,
|
|
329
|
+
"current_config": current.model_dump(mode="json"),
|
|
330
|
+
},
|
|
331
|
+
status_code=409,
|
|
332
|
+
)
|
|
333
|
+
save_visibility_config(data_dir, new_config)
|
|
330
334
|
return JSONResponse({"saved": True, "version": new_config.version + 1})
|
|
331
335
|
|
|
332
336
|
return {
|
{ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/settings_ui/locales/en.json
RENAMED
|
@@ -96,8 +96,10 @@
|
|
|
96
96
|
"visibility.boundary": "Without enforce mode (below), this is noise reduction, not access control: a hidden entity is still returned by a direct read of its entity_id and still appears in automation, dashboard, and template content. Do not rely on the filter alone as a security boundary.",
|
|
97
97
|
"visibility.enabled": "Enable entity visibility filter",
|
|
98
98
|
"visibility.enforce.title": "Enforce mode",
|
|
99
|
-
"visibility.enforce.help": "Extends the filter from decluttering search and overview to every tool read. A direct read of a hidden entity returns “not found”, and a content read
|
|
100
|
-
"visibility.enforce.only_enforced": "Make hidden entities unreadable across
|
|
99
|
+
"visibility.enforce.help": "Extends the filter from decluttering search and overview to every tool read except ha_report_issue by default. A direct read of a hidden entity returns “not found”, and a content read that would surface one is refused. The report-tool exception is controlled below. Best-effort concealment against incidental exposure, not a hardened security boundary.",
|
|
100
|
+
"visibility.enforce.only_enforced": "Make hidden entities unreadable across tool reads (except issue reports by default)",
|
|
101
|
+
"visibility.enforce.report_issue_help": "While this is off, ha_report_issue and all of its diagnostic output—including logs—stay outside enforce mode on every call, even when visibility data is healthy or unavailable. Turn it on to scan and refuse that tool like other reads.",
|
|
102
|
+
"visibility.enforce.restrict_report_issue": "Apply enforce mode to ha_report_issue",
|
|
101
103
|
"visibility.categories.title": "Hide by category",
|
|
102
104
|
"visibility.categories.help": "Home Assistant tags auto-generated entities as diagnostic or config.",
|
|
103
105
|
"visibility.categories.legend": "Entity categories to hide",
|
|
@@ -485,8 +485,10 @@
|
|
|
485
485
|
|
|
486
486
|
<section class="a11y-section">
|
|
487
487
|
<h2 class="a11y-section-title" data-i18n="visibility.enforce.title">Enforce mode</h2>
|
|
488
|
-
<p class="a11y-section-help" data-i18n="visibility.enforce.help">Extends the filter from decluttering search and overview to every tool read. A direct read of a hidden entity returns “not found”, and a content read
|
|
489
|
-
<label class="a11y-option"><input type="checkbox" id="visibility-enforce"> <span data-i18n="visibility.enforce.only_enforced">Make hidden entities unreadable across
|
|
488
|
+
<p class="a11y-section-help" data-i18n="visibility.enforce.help">Extends the filter from decluttering search and overview to every tool read except ha_report_issue by default. A direct read of a hidden entity returns “not found”, and a content read that would surface one is refused. The report-tool exception is controlled below. Best-effort concealment against incidental exposure, not a hardened security boundary.</p>
|
|
489
|
+
<label class="a11y-option"><input type="checkbox" id="visibility-enforce"> <span data-i18n="visibility.enforce.only_enforced">Make hidden entities unreadable across tool reads (except issue reports by default)</span></label>
|
|
490
|
+
<p class="a11y-section-help" data-i18n="visibility.enforce.report_issue_help">While this is off, ha_report_issue and all of its diagnostic output—including logs—stay outside enforce mode on every call, even when visibility data is healthy or unavailable. Turn it on to scan and refuse that tool like other reads.</p>
|
|
491
|
+
<label class="a11y-option"><input type="checkbox" id="visibility-restrict-report-issue"> <span data-i18n="visibility.enforce.restrict_report_issue">Apply enforce mode to ha_report_issue</span></label>
|
|
490
492
|
</section>
|
|
491
493
|
|
|
492
494
|
<section class="a11y-section">
|
|
@@ -4488,6 +4488,7 @@ async function visibilityLoadConfig() {
|
|
|
4488
4488
|
const cats = c.exclude_categories || [];
|
|
4489
4489
|
document.getElementById('visibility-enabled').checked = !!c.enabled;
|
|
4490
4490
|
document.getElementById('visibility-enforce').checked = !!c.enforce;
|
|
4491
|
+
document.getElementById('visibility-restrict-report-issue').checked = !!c.restrict_report_issue;
|
|
4491
4492
|
document.getElementById('visibility-cat-diagnostic').checked = cats.includes('diagnostic');
|
|
4492
4493
|
document.getElementById('visibility-cat-config').checked = cats.includes('config');
|
|
4493
4494
|
document.getElementById('visibility-exclude-hidden').checked = !!c.exclude_hidden;
|
|
@@ -4509,6 +4510,7 @@ async function visibilitySaveConfig() {
|
|
|
4509
4510
|
version: visibilityVersion,
|
|
4510
4511
|
enabled: document.getElementById('visibility-enabled').checked,
|
|
4511
4512
|
enforce: document.getElementById('visibility-enforce').checked,
|
|
4513
|
+
restrict_report_issue: document.getElementById('visibility-restrict-report-issue').checked,
|
|
4512
4514
|
exclude_categories: cats,
|
|
4513
4515
|
exclude_hidden: document.getElementById('visibility-exclude-hidden').checked,
|
|
4514
4516
|
deny_entity_ids: _visibilityParseList(document.getElementById('visibility-deny').value, '\n'),
|
|
@@ -277,7 +277,15 @@ def _no_proxy_opener() -> Any:
|
|
|
277
277
|
"""
|
|
278
278
|
import urllib.request
|
|
279
279
|
|
|
280
|
-
|
|
280
|
+
class _NoRedirectHandler(urllib.request.HTTPRedirectHandler):
|
|
281
|
+
"""Keep the loopback-only shutdown request on its original target."""
|
|
282
|
+
|
|
283
|
+
def redirect_request(self, *_args: Any, **_kwargs: Any) -> None:
|
|
284
|
+
return None
|
|
285
|
+
|
|
286
|
+
return urllib.request.build_opener(
|
|
287
|
+
urllib.request.ProxyHandler({}), _NoRedirectHandler()
|
|
288
|
+
)
|
|
281
289
|
|
|
282
290
|
|
|
283
291
|
# Shape run_main() writes into ui.url. Doubles as the legacy-migration
|
|
@@ -287,6 +295,15 @@ _SIDECAR_URL_RE = re.compile(
|
|
|
287
295
|
)
|
|
288
296
|
|
|
289
297
|
|
|
298
|
+
def _is_trusted_sidecar_url(url: str) -> bool:
|
|
299
|
+
"""Return whether a URL matches the loopback sidecar producer contract."""
|
|
300
|
+
match = _SIDECAR_URL_RE.fullmatch(url.strip())
|
|
301
|
+
if match is None:
|
|
302
|
+
return False
|
|
303
|
+
port = int(match.group(1))
|
|
304
|
+
return 1024 <= port <= 65535
|
|
305
|
+
|
|
306
|
+
|
|
290
307
|
def _seed_state_from_url(url: str) -> None:
|
|
291
308
|
"""Migrate a pre-``ui.state`` install's stable URL into the state file.
|
|
292
309
|
|
|
@@ -335,6 +352,13 @@ def _post_shutdown(url: str) -> tuple[bool, bool | None]:
|
|
|
335
352
|
"""
|
|
336
353
|
import urllib.error
|
|
337
354
|
|
|
355
|
+
if not _is_trusted_sidecar_url(url):
|
|
356
|
+
logger.warning(
|
|
357
|
+
"Recorded sidecar URL is invalid; refusing the shutdown request and "
|
|
358
|
+
"replacing state files only."
|
|
359
|
+
)
|
|
360
|
+
return False, None
|
|
361
|
+
|
|
338
362
|
try:
|
|
339
363
|
import urllib.request
|
|
340
364
|
|
|
@@ -384,6 +408,185 @@ def _post_shutdown(url: str) -> tuple[bool, bool | None]:
|
|
|
384
408
|
return False, None
|
|
385
409
|
|
|
386
410
|
|
|
411
|
+
def _read_external_retirement_url(config_dir: Path) -> str | None:
|
|
412
|
+
"""Read the sidecar URL used by an external-process cleanup."""
|
|
413
|
+
url_path = config_dir / "ui.url"
|
|
414
|
+
try:
|
|
415
|
+
url = url_path.read_text(encoding="utf-8").strip()
|
|
416
|
+
except FileNotFoundError:
|
|
417
|
+
logger.debug("Sidecar URL file %s not found; nothing to retire.", url_path)
|
|
418
|
+
return None
|
|
419
|
+
except PermissionError:
|
|
420
|
+
logger.warning(
|
|
421
|
+
"Cannot read sidecar URL file %s; a detached listener may still be "
|
|
422
|
+
"running for config dir %s.",
|
|
423
|
+
url_path,
|
|
424
|
+
config_dir,
|
|
425
|
+
exc_info=True,
|
|
426
|
+
)
|
|
427
|
+
return None
|
|
428
|
+
except OSError:
|
|
429
|
+
logger.warning(
|
|
430
|
+
"Failed to read sidecar URL file %s; a detached listener may still be "
|
|
431
|
+
"running for config dir %s.",
|
|
432
|
+
url_path,
|
|
433
|
+
config_dir,
|
|
434
|
+
exc_info=True,
|
|
435
|
+
)
|
|
436
|
+
return None
|
|
437
|
+
if not url:
|
|
438
|
+
logger.warning(
|
|
439
|
+
"Sidecar URL file %s is empty; a detached listener may still be running "
|
|
440
|
+
"for config dir %s.",
|
|
441
|
+
url_path,
|
|
442
|
+
config_dir,
|
|
443
|
+
)
|
|
444
|
+
return None
|
|
445
|
+
return url
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
def _read_external_retirement_pid(config_dir: Path) -> int | None:
|
|
449
|
+
"""Read the recorded PID so an acknowledged retirement can await exit."""
|
|
450
|
+
pid_path = config_dir / "ui.pid"
|
|
451
|
+
try:
|
|
452
|
+
return int(pid_path.read_text(encoding="utf-8").strip())
|
|
453
|
+
except FileNotFoundError:
|
|
454
|
+
logger.debug("Sidecar PID file %s not found; exit cannot be polled.", pid_path)
|
|
455
|
+
except PermissionError:
|
|
456
|
+
logger.warning(
|
|
457
|
+
"Cannot read sidecar PID file %s; exit cannot be polled for config dir %s.",
|
|
458
|
+
pid_path,
|
|
459
|
+
config_dir,
|
|
460
|
+
exc_info=True,
|
|
461
|
+
)
|
|
462
|
+
except OSError:
|
|
463
|
+
logger.warning(
|
|
464
|
+
"Failed to read sidecar PID file %s; exit cannot be polled for config "
|
|
465
|
+
"dir %s.",
|
|
466
|
+
pid_path,
|
|
467
|
+
config_dir,
|
|
468
|
+
exc_info=True,
|
|
469
|
+
)
|
|
470
|
+
except ValueError:
|
|
471
|
+
logger.warning(
|
|
472
|
+
"Sidecar PID file %s is invalid; exit cannot be polled for config dir %s.",
|
|
473
|
+
pid_path,
|
|
474
|
+
config_dir,
|
|
475
|
+
)
|
|
476
|
+
return None
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
def _external_retirement_sentinel_preexisting(sentinel_path: Path) -> bool:
|
|
480
|
+
"""Snapshot whether the user already disabled the settings sidecar."""
|
|
481
|
+
try:
|
|
482
|
+
return sentinel_path.exists()
|
|
483
|
+
except OSError:
|
|
484
|
+
logger.warning(
|
|
485
|
+
"Cannot inspect sidecar disable sentinel %s; preserving it.",
|
|
486
|
+
sentinel_path,
|
|
487
|
+
exc_info=True,
|
|
488
|
+
)
|
|
489
|
+
return True
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
def _clear_external_retirement_sentinel(
|
|
493
|
+
sentinel_path: Path,
|
|
494
|
+
*,
|
|
495
|
+
url: str,
|
|
496
|
+
sentinel_preexisting: bool,
|
|
497
|
+
sentinel_created: bool | None,
|
|
498
|
+
) -> None:
|
|
499
|
+
"""Clear only a legacy disable sentinel attributable to this retirement."""
|
|
500
|
+
should_clear = (
|
|
501
|
+
sentinel_created if sentinel_created is not None else not sentinel_preexisting
|
|
502
|
+
)
|
|
503
|
+
if not should_clear:
|
|
504
|
+
return
|
|
505
|
+
try:
|
|
506
|
+
sentinel_path.unlink()
|
|
507
|
+
except FileNotFoundError:
|
|
508
|
+
logger.debug(
|
|
509
|
+
"Sidecar disable sentinel %s was already absent after retiring %s.",
|
|
510
|
+
sentinel_path,
|
|
511
|
+
url,
|
|
512
|
+
)
|
|
513
|
+
except OSError:
|
|
514
|
+
logger.warning(
|
|
515
|
+
"Retired sidecar at %s but could not remove its disable sentinel %s.",
|
|
516
|
+
url,
|
|
517
|
+
sentinel_path,
|
|
518
|
+
exc_info=True,
|
|
519
|
+
)
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
def _wait_for_external_retired_pid(
|
|
523
|
+
pid: int | None,
|
|
524
|
+
*,
|
|
525
|
+
url: str,
|
|
526
|
+
config_dir: Path,
|
|
527
|
+
) -> None:
|
|
528
|
+
"""Wait a bounded interval for an acknowledged detached sidecar to exit."""
|
|
529
|
+
if pid is None:
|
|
530
|
+
return
|
|
531
|
+
import time
|
|
532
|
+
|
|
533
|
+
deadline = time.monotonic() + _OLD_SIDECAR_EXIT_WAIT
|
|
534
|
+
while _pid_alive(pid):
|
|
535
|
+
if time.monotonic() >= deadline:
|
|
536
|
+
logger.warning(
|
|
537
|
+
"Retired sidecar pid %s at %s is still alive after %.0fs for "
|
|
538
|
+
"config dir %s.",
|
|
539
|
+
pid,
|
|
540
|
+
url,
|
|
541
|
+
_OLD_SIDECAR_EXIT_WAIT,
|
|
542
|
+
config_dir,
|
|
543
|
+
)
|
|
544
|
+
return
|
|
545
|
+
time.sleep(_OLD_SIDECAR_EXIT_POLL)
|
|
546
|
+
|
|
547
|
+
|
|
548
|
+
def retire_sidecar(config_dir: Path) -> bool:
|
|
549
|
+
"""Best-effort retirement for an externally managed stdio test process.
|
|
550
|
+
|
|
551
|
+
This public helper intentionally crosses the process boundary: callers that
|
|
552
|
+
spawn a real ``ha-mcp`` stdio server otherwise have no handle to its detached
|
|
553
|
+
settings listener. It uses discovery files in ``config_dir``, reports each
|
|
554
|
+
failure mode, and returns whether the sidecar acknowledged shutdown.
|
|
555
|
+
|
|
556
|
+
The replacement path in ``_shutdown_existing_sidecar`` deliberately remains
|
|
557
|
+
separate because it seeds global sidecar state and immediately spawns a successor.
|
|
558
|
+
"""
|
|
559
|
+
url = _read_external_retirement_url(config_dir)
|
|
560
|
+
if url is None:
|
|
561
|
+
return False
|
|
562
|
+
# Capture both before the POST: an acknowledged sidecar may remove its PID,
|
|
563
|
+
# and the sentinel snapshot distinguishes a legacy endpoint's write from a
|
|
564
|
+
# user's preexisting disable choice.
|
|
565
|
+
pid = _read_external_retirement_pid(config_dir)
|
|
566
|
+
sentinel_path = config_dir / "settings_ui_disabled"
|
|
567
|
+
sentinel_preexisting = _external_retirement_sentinel_preexisting(sentinel_path)
|
|
568
|
+
|
|
569
|
+
acked, sentinel_created = _post_shutdown(url)
|
|
570
|
+
if not acked:
|
|
571
|
+
logger.warning(
|
|
572
|
+
"Sidecar at %s did not acknowledge retirement for config dir %s; "
|
|
573
|
+
"the detached listener may still be running.",
|
|
574
|
+
url,
|
|
575
|
+
config_dir,
|
|
576
|
+
)
|
|
577
|
+
return False
|
|
578
|
+
|
|
579
|
+
_clear_external_retirement_sentinel(
|
|
580
|
+
sentinel_path,
|
|
581
|
+
url=url,
|
|
582
|
+
sentinel_preexisting=sentinel_preexisting,
|
|
583
|
+
sentinel_created=sentinel_created,
|
|
584
|
+
)
|
|
585
|
+
logger.info("Retired settings UI sidecar at %s for config dir %s", url, config_dir)
|
|
586
|
+
_wait_for_external_retired_pid(pid, url=url, config_dir=config_dir)
|
|
587
|
+
return True
|
|
588
|
+
|
|
589
|
+
|
|
387
590
|
def _log_shutdown_timeout(exc: BaseException) -> None:
|
|
388
591
|
logger.warning(
|
|
389
592
|
"Old sidecar did not answer /shutdown within %.1fs (%s); "
|
|
@@ -420,6 +623,9 @@ def _shutdown_existing_sidecar() -> None:
|
|
|
420
623
|
replacement, so nothing hands out the old URL anymore (if the
|
|
421
624
|
replacement loses the port to that lingering process, it serves on
|
|
422
625
|
an ephemeral fallback instead).
|
|
626
|
+
|
|
627
|
+
External test-process cleanup uses ``retire_sidecar``; the sequences stay
|
|
628
|
+
separate because only this path seeds state and immediately replaces the listener.
|
|
423
629
|
"""
|
|
424
630
|
url = read_sidecar_url()
|
|
425
631
|
if url is None:
|
|
@@ -1163,6 +1369,60 @@ def _build_app(
|
|
|
1163
1369
|
handlers["save_theme_prefs"],
|
|
1164
1370
|
methods=["POST"],
|
|
1165
1371
|
),
|
|
1372
|
+
# Advanced settings. The stdio-only sidecar-port control is rendered
|
|
1373
|
+
# from this handler, so these routes are required in sidecar mode.
|
|
1374
|
+
Route(
|
|
1375
|
+
f"{secret_prefix}/api/settings/advanced",
|
|
1376
|
+
handlers["get_advanced_settings"],
|
|
1377
|
+
methods=["GET"],
|
|
1378
|
+
),
|
|
1379
|
+
Route(
|
|
1380
|
+
f"{secret_prefix}/api/settings/advanced",
|
|
1381
|
+
handlers["save_advanced_settings"],
|
|
1382
|
+
methods=["POST"],
|
|
1383
|
+
),
|
|
1384
|
+
# Auto-backup endpoints (#1288). The Backups tab is available in the
|
|
1385
|
+
# shared settings page and must use the same handlers in stdio mode.
|
|
1386
|
+
Route(
|
|
1387
|
+
f"{secret_prefix}/api/settings/backups",
|
|
1388
|
+
handlers["list_backups"],
|
|
1389
|
+
methods=["GET"],
|
|
1390
|
+
),
|
|
1391
|
+
Route(
|
|
1392
|
+
f"{secret_prefix}/api/settings/backups",
|
|
1393
|
+
handlers["delete_backups_bulk"],
|
|
1394
|
+
methods=["DELETE"],
|
|
1395
|
+
),
|
|
1396
|
+
Route(
|
|
1397
|
+
f"{secret_prefix}/api/settings/backups/{{name}}",
|
|
1398
|
+
handlers["view_backup"],
|
|
1399
|
+
methods=["GET"],
|
|
1400
|
+
),
|
|
1401
|
+
Route(
|
|
1402
|
+
f"{secret_prefix}/api/settings/backups/{{name}}/diff",
|
|
1403
|
+
handlers["diff_backup"],
|
|
1404
|
+
methods=["GET"],
|
|
1405
|
+
),
|
|
1406
|
+
Route(
|
|
1407
|
+
f"{secret_prefix}/api/settings/backups/{{name}}/restore",
|
|
1408
|
+
handlers["restore_backup"],
|
|
1409
|
+
methods=["POST"],
|
|
1410
|
+
),
|
|
1411
|
+
Route(
|
|
1412
|
+
f"{secret_prefix}/api/settings/backups/{{name}}",
|
|
1413
|
+
handlers["delete_backup"],
|
|
1414
|
+
methods=["DELETE"],
|
|
1415
|
+
),
|
|
1416
|
+
Route(
|
|
1417
|
+
f"{secret_prefix}/api/settings/backup-config",
|
|
1418
|
+
handlers["get_backup_config"],
|
|
1419
|
+
methods=["GET"],
|
|
1420
|
+
),
|
|
1421
|
+
Route(
|
|
1422
|
+
f"{secret_prefix}/api/settings/backup-config",
|
|
1423
|
+
handlers["save_backup_config"],
|
|
1424
|
+
methods=["POST"],
|
|
1425
|
+
),
|
|
1166
1426
|
# Custom filesystem directories (issue #1567). The sub-form is rendered
|
|
1167
1427
|
# in the features panel, so the stdio sidecar must serve these too — its
|
|
1168
1428
|
# route list is hand-maintained and does NOT derive from
|
|
@@ -1217,6 +1477,19 @@ def _build_app(
|
|
|
1217
1477
|
handlers["policy_get_value_source"],
|
|
1218
1478
|
methods=["GET"],
|
|
1219
1479
|
),
|
|
1480
|
+
# Entity visibility filter endpoints. The sidecar owns a hand-maintained
|
|
1481
|
+
# route table (it cannot reuse FastMCP custom-route registration), so
|
|
1482
|
+
# keep this pair in lockstep with settings_ui.register_settings_routes.
|
|
1483
|
+
Route(
|
|
1484
|
+
f"{secret_prefix}/api/visibility/config",
|
|
1485
|
+
handlers["visibility_get_config"],
|
|
1486
|
+
methods=["GET"],
|
|
1487
|
+
),
|
|
1488
|
+
Route(
|
|
1489
|
+
f"{secret_prefix}/api/visibility/config",
|
|
1490
|
+
handlers["visibility_put_config"],
|
|
1491
|
+
methods=["PUT"],
|
|
1492
|
+
),
|
|
1220
1493
|
]
|
|
1221
1494
|
|
|
1222
1495
|
# /shutdown — POST endpoint that drops the disable sentinel and
|