ha-mcp-dev 7.14.2.dev2113__tar.gz → 7.14.2.dev2117__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ha_mcp_dev-7.14.2.dev2113/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.14.2.dev2117}/PKG-INFO +1 -1
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/pyproject.toml +1 -1
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/auth/provider.py +83 -10
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/smart_search/_deep.py +160 -3
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/tools_config_dashboards.py +8 -5
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/tools_search.py +35 -2
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/LICENSE +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/MANIFEST.in +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/README.md +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/setup.cfg +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/__main__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/_pypi_marker +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/_version.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/auth/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/auth/consent_form.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/backup_manager.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/browser_landing.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/client/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/client/rest_client.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/client/supervisor_client.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/client/websocket_client.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/client/websocket_listener.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/config.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/dashboard_screenshot/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/dashboard_screenshot/capture.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/dashboard_screenshot/content.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/dashboard_screenshot/paths.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/dashboard_screenshot/provision.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/dashboard_screenshot/theme_guard.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/errors.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/llm_exposure.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/policy/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/policy/approval_queue.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/policy/evaluator.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/policy/handlers.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/policy/middleware.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/policy/model.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/policy/persistence.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/policy/value_sources.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/py.typed +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/read_only.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/appdaemon.md +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/blueprint-guide.md +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/scenes.md +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/yaml-only-integrations.md +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/server.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/settings_ui/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/settings_ui/_handlers_advanced.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/settings_ui/_handlers_backups.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/settings_ui/_handlers_fs.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/settings_ui/_handlers_server.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/settings_ui/_handlers_theme.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/settings_ui/_handlers_tools.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/settings_ui/_i18n.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/settings_ui/_persistence.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/settings_ui/_supervisor.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/settings_ui/_theme.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/settings_ui/_tools_meta.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/settings_ui/locales/README.md +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/settings_ui/locales/de.json +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/settings_ui/locales/en.json +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/settings_ui/locales/ru.json +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/settings_ui/locales/zh-Hans.json +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/settings_ui/settings.css +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/settings_ui/settings.html +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/settings_ui/settings.js +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/smoke_test.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/strict_bps.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/auto_backup.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/backup.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/best_practice_checker.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/component_api.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/component_config_reads.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/component_devices.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/component_registries.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/component_registry_lookup.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/config_entry_flow.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/device_control.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/enhanced.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/helpers.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/radio/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/radio/base.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/radio/matter.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/radio/thread.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/radio/zigbee.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/radio/zwave.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/reference_validator.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/registry.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/smart_search/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/smart_search/_base.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/smart_search/_config.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/smart_search/_entities.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/smart_search/_fetch.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/smart_search/_overview.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/smart_search/_scenes.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/smart_search/_scoring.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/tool_search_hint_middleware.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/tools_addons.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/tools_areas.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/tools_blueprints.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/tools_bug_report.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/tools_calendar.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/tools_camera.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/tools_categories.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/tools_code.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/tools_config_automations.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/tools_dashboard_screenshot.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/tools_dev.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/tools_energy.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/tools_entities.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/tools_filesystem.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/tools_groups.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/tools_hacs.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/tools_history.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/tools_integrations.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/tools_labels.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/tools_radio.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/tools_registry.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/tools_resources.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/tools_service.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/tools_services.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/tools_system.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/tools_themes.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/tools_todo.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/tools_traces.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/tools_updates.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/tools_utility.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/tools_yaml_read.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/tools_zones.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/util_helpers.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/validation_middleware.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/transforms/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/transforms/categorized_search.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/transport_security.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/update_check.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/utils/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/utils/config_hash.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/utils/config_write_lock.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/utils/data_paths.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/utils/domain_handlers.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/utils/fuzzy_search.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/utils/operation_manager.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/utils/python_sandbox.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/utils/skill_loader.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/utils/usage_logger.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/visibility/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/visibility/enforcement.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/visibility/model.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/visibility/persistence.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/visibility/resolver.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/tests/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/tests/test_constants.py +0 -0
- {ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/tests/test_env_manager.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ha-mcp-dev"
|
|
7
|
-
version = "7.14.2.
|
|
7
|
+
version = "7.14.2.dev2117"
|
|
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
|
|
@@ -16,9 +16,10 @@ import os
|
|
|
16
16
|
import secrets
|
|
17
17
|
import time
|
|
18
18
|
from base64 import urlsafe_b64decode, urlsafe_b64encode
|
|
19
|
+
from collections.abc import Awaitable, Callable
|
|
19
20
|
from pathlib import Path
|
|
20
21
|
from typing import Any
|
|
21
|
-
from urllib.parse import urlencode
|
|
22
|
+
from urllib.parse import parse_qs, urlencode, urlparse, urlunparse
|
|
22
23
|
|
|
23
24
|
from fastmcp.server.auth.auth import (
|
|
24
25
|
AccessToken, # FastMCP version has claims field
|
|
@@ -370,6 +371,58 @@ class HomeAssistantOAuthProvider(OAuthProvider):
|
|
|
370
371
|
logger.debug(f"Failed to decode token: {e}")
|
|
371
372
|
return None
|
|
372
373
|
|
|
374
|
+
def _issuer(self) -> str:
|
|
375
|
+
"""The issuer identifier this authorization server advertises.
|
|
376
|
+
|
|
377
|
+
Single source for the ``issuer`` field of the metadata document served
|
|
378
|
+
below and for the RFC 9207 ``iss`` authorization-response parameter,
|
|
379
|
+
which RFC 9207 §2 requires to equal the advertised issuer exactly.
|
|
380
|
+
"""
|
|
381
|
+
return str(AnyHttpUrl(str(self.base_url).rstrip("/")))
|
|
382
|
+
|
|
383
|
+
def _ensure_single_iss(self, url: str) -> str:
|
|
384
|
+
"""Return ``url`` carrying the RFC 9207 ``iss`` parameter exactly once.
|
|
385
|
+
|
|
386
|
+
A registered redirect URI may itself carry an ``iss`` query parameter;
|
|
387
|
+
blindly appending ours would produce an ambiguous double-``iss``
|
|
388
|
+
response — and common parsers take the FIRST value, which is precisely
|
|
389
|
+
the issuer mix-up RFC 9207 exists to prevent. Any pre-existing ``iss``
|
|
390
|
+
is dropped so the one this server stamps is authoritative.
|
|
391
|
+
"""
|
|
392
|
+
parsed = urlparse(url)
|
|
393
|
+
params = [
|
|
394
|
+
(k, v) for k, vs in parse_qs(parsed.query).items() for v in vs if k != "iss"
|
|
395
|
+
]
|
|
396
|
+
params.append(("iss", self._issuer()))
|
|
397
|
+
return urlunparse(parsed._replace(query=urlencode(params)))
|
|
398
|
+
|
|
399
|
+
def _wrap_authorize_with_iss(
|
|
400
|
+
self, endpoint: "Callable[[Request], Awaitable[Response]]"
|
|
401
|
+
) -> "Callable[[Request], Awaitable[Response]]":
|
|
402
|
+
"""Wrap the SDK's ``/authorize`` endpoint to stamp ``iss`` on its
|
|
403
|
+
error redirects.
|
|
404
|
+
|
|
405
|
+
The SDK's ``AuthorizationHandler`` builds OAuth error authorization
|
|
406
|
+
responses (invalid scope, ``authorize()`` raising after redirect-URI
|
|
407
|
+
validation) itself, without the RFC 9207 ``iss`` parameter — only our
|
|
408
|
+
consent-form success path carries it otherwise. Success on this route
|
|
409
|
+
is a same-origin redirect to the consent form (no ``error``), which is
|
|
410
|
+
not an authorization response and stays untouched.
|
|
411
|
+
"""
|
|
412
|
+
|
|
413
|
+
async def authorize_with_iss(request: Request) -> Response:
|
|
414
|
+
response: Response = await endpoint(request)
|
|
415
|
+
location = response.headers.get("location", "")
|
|
416
|
+
if (
|
|
417
|
+
response.status_code in (302, 303, 307)
|
|
418
|
+
and location
|
|
419
|
+
and "error" in parse_qs(urlparse(location).query)
|
|
420
|
+
):
|
|
421
|
+
response.headers["location"] = self._ensure_single_iss(location)
|
|
422
|
+
return response
|
|
423
|
+
|
|
424
|
+
return authorize_with_iss
|
|
425
|
+
|
|
373
426
|
def get_routes(self, mcp_path: str | None = None) -> list[Route]:
|
|
374
427
|
"""
|
|
375
428
|
Get OAuth routes including custom consent form routes.
|
|
@@ -391,12 +444,9 @@ class HomeAssistantOAuthProvider(OAuthProvider):
|
|
|
391
444
|
"""Enhanced OAuth metadata handler with Claude.ai compatibility."""
|
|
392
445
|
from mcp.server.auth.routes import build_metadata
|
|
393
446
|
|
|
394
|
-
# Get base URL
|
|
395
|
-
base = str(self.base_url).rstrip("/")
|
|
396
|
-
|
|
397
447
|
# Get base metadata from MCP SDK
|
|
398
448
|
metadata = build_metadata(
|
|
399
|
-
issuer_url=AnyHttpUrl(
|
|
449
|
+
issuer_url=AnyHttpUrl(self._issuer()),
|
|
400
450
|
service_documentation_url=AnyHttpUrl(
|
|
401
451
|
"https://github.com/homeassistant-ai/ha-mcp"
|
|
402
452
|
),
|
|
@@ -411,6 +461,11 @@ class HomeAssistantOAuthProvider(OAuthProvider):
|
|
|
411
461
|
# Add response_modes_supported (required by some OAuth clients)
|
|
412
462
|
metadata_dict["response_modes_supported"] = ["query"]
|
|
413
463
|
|
|
464
|
+
# RFC 9207 §3: advertise that authorization responses carry the
|
|
465
|
+
# ``iss`` parameter — omission means "not supported" to
|
|
466
|
+
# discovery-driven clients even though redirects now include it.
|
|
467
|
+
metadata_dict["authorization_response_iss_parameter_supported"] = True
|
|
468
|
+
|
|
414
469
|
# Add "none" auth method for public clients with PKCE (used by Claude.ai)
|
|
415
470
|
if "token_endpoint_auth_methods_supported" in metadata_dict:
|
|
416
471
|
if "none" not in metadata_dict["token_endpoint_auth_methods_supported"]:
|
|
@@ -448,6 +503,16 @@ class HomeAssistantOAuthProvider(OAuthProvider):
|
|
|
448
503
|
methods=["GET", "OPTIONS"],
|
|
449
504
|
)
|
|
450
505
|
)
|
|
506
|
+
elif isinstance(route, Route) and route.path == "/authorize":
|
|
507
|
+
# RFC 9207: the SDK handler's own error redirects must carry
|
|
508
|
+
# ``iss`` too — see _wrap_authorize_with_iss.
|
|
509
|
+
enhanced_routes.append(
|
|
510
|
+
Route(
|
|
511
|
+
path=route.path,
|
|
512
|
+
endpoint=self._wrap_authorize_with_iss(route.endpoint),
|
|
513
|
+
methods=list(route.methods or []),
|
|
514
|
+
)
|
|
515
|
+
)
|
|
451
516
|
else:
|
|
452
517
|
enhanced_routes.append(route)
|
|
453
518
|
|
|
@@ -697,11 +762,19 @@ class HomeAssistantOAuthProvider(OAuthProvider):
|
|
|
697
762
|
# Clean up pending authorization
|
|
698
763
|
del self.pending_authorizations[str(txn_id)]
|
|
699
764
|
|
|
700
|
-
# Redirect back to client with auth code
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
765
|
+
# Redirect back to client with auth code. RFC 9207: the authorization
|
|
766
|
+
# response carries the issuer that produced it, so a client registered
|
|
767
|
+
# with several authorization servers cannot be fed a code minted by a
|
|
768
|
+
# different one. _ensure_single_iss (rather than an ``iss=`` kwarg
|
|
769
|
+
# here) collapses any ``iss`` baked into the registered redirect URI —
|
|
770
|
+
# construct_redirect_uri appends blindly, and a double ``iss`` is
|
|
771
|
+
# ambiguous to strict clients.
|
|
772
|
+
redirect_uri = self._ensure_single_iss(
|
|
773
|
+
construct_redirect_uri(
|
|
774
|
+
pending["redirect_uri"],
|
|
775
|
+
code=auth_code_value,
|
|
776
|
+
state=pending.get("state"),
|
|
777
|
+
)
|
|
705
778
|
)
|
|
706
779
|
|
|
707
780
|
logger.info(f"Authorization successful for client {client_id}")
|
{ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/smart_search/_deep.py
RENAMED
|
@@ -9,9 +9,14 @@ from fastmcp import Context
|
|
|
9
9
|
from fastmcp.exceptions import ToolError
|
|
10
10
|
|
|
11
11
|
from ...client.rest_client import HomeAssistantAPIError
|
|
12
|
+
from ...errors import get_error_code, get_error_message
|
|
13
|
+
from ..component_api import component_supports, get_component_caps
|
|
12
14
|
from ..config_entry_flow import FLOW_HELPER_TYPES
|
|
13
15
|
from ..helpers import exception_to_structured_error, safe_info, safe_progress
|
|
14
|
-
from ..tools_config_dashboards import
|
|
16
|
+
from ..tools_config_dashboards import (
|
|
17
|
+
_dashboards_via_component,
|
|
18
|
+
fetch_dashboards_list,
|
|
19
|
+
)
|
|
15
20
|
from ..tools_integrations import fetch_entry_options_with_status
|
|
16
21
|
from ._config import (
|
|
17
22
|
AUTOMATION_CONFIG_TIME_BUDGET,
|
|
@@ -55,6 +60,40 @@ async def _scrub_results_for_enforce(
|
|
|
55
60
|
results[category] = scrub_records(items, enforce_regex)
|
|
56
61
|
|
|
57
62
|
|
|
63
|
+
# The ``lovelace/config`` messages meaning "this dashboard has no stored
|
|
64
|
+
# config" — bare (nested error shape) and as ``send_websocket_message``'s
|
|
65
|
+
# flat envelope prefixes it. Mirrors ``dashboard_screenshot/paths.py``.
|
|
66
|
+
_NO_STORED_CONFIG_MESSAGES = frozenset(
|
|
67
|
+
{"No config found.", "Command failed: No config found."}
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _is_no_stored_config(resp: dict[str, Any]) -> bool:
|
|
72
|
+
"""True when a failed ``lovelace/config`` envelope means the dashboard
|
|
73
|
+
has no stored config — an auto-generated dashboard that was never taken
|
|
74
|
+
control of — which is a clean no-match for a config scan rather than a
|
|
75
|
+
backend failure (issue #2008).
|
|
76
|
+
|
|
77
|
+
HA reports the ``config_not_found`` code for two distinct causes
|
|
78
|
+
(``homeassistant/components/lovelace/websocket.py``, ``_handle_errors``):
|
|
79
|
+
no stored config ("No config found.") and an unresolvable url_path
|
|
80
|
+
("Unknown config specified: ..."), e.g. a dashboard deleted between the
|
|
81
|
+
registry-list snapshot and this fetch. Only the former is a clean skip,
|
|
82
|
+
so the message is matched too; the latter stays a counted failure.
|
|
83
|
+
|
|
84
|
+
``send_websocket_message`` normalizes command failures to a flat envelope
|
|
85
|
+
(code top-level as ``error_code``, message as the ``error`` string); the
|
|
86
|
+
nested ``error.code``/``error.message`` shape is accepted defensively via
|
|
87
|
+
the canonical :func:`get_error_code` / :func:`get_error_message`
|
|
88
|
+
extractors, matching ``dashboard_screenshot/paths.py``'s detection of the
|
|
89
|
+
same HA error.
|
|
90
|
+
"""
|
|
91
|
+
code = resp.get("error_code") or get_error_code(resp)
|
|
92
|
+
if code != "config_not_found":
|
|
93
|
+
return False
|
|
94
|
+
return get_error_message(resp) in _NO_STORED_CONFIG_MESSAGES
|
|
95
|
+
|
|
96
|
+
|
|
58
97
|
class DeepSearchMixin(SceneSearchMixin):
|
|
59
98
|
"""deep_search orchestration + per-type automation/script/helper/dashboard/flow search."""
|
|
60
99
|
|
|
@@ -279,8 +318,11 @@ class DeepSearchMixin(SceneSearchMixin):
|
|
|
279
318
|
(
|
|
280
319
|
results["dashboards"],
|
|
281
320
|
dashboard_failed,
|
|
282
|
-
) = await self.
|
|
283
|
-
query_lower,
|
|
321
|
+
) = await self._search_dashboards_surface(
|
|
322
|
+
query_lower,
|
|
323
|
+
exact_match,
|
|
324
|
+
semaphore,
|
|
325
|
+
include_config=include_config,
|
|
284
326
|
)
|
|
285
327
|
phase_done += 1
|
|
286
328
|
await safe_progress(
|
|
@@ -896,6 +938,106 @@ class DeepSearchMixin(SceneSearchMixin):
|
|
|
896
938
|
failed_type_count += flow_failed_count
|
|
897
939
|
return results, failed_type_count
|
|
898
940
|
|
|
941
|
+
async def _search_dashboards_surface(
|
|
942
|
+
self,
|
|
943
|
+
query_lower: str,
|
|
944
|
+
exact_match: bool,
|
|
945
|
+
semaphore: asyncio.Semaphore,
|
|
946
|
+
*,
|
|
947
|
+
include_config: bool,
|
|
948
|
+
) -> tuple[list[dict[str, Any]], int]:
|
|
949
|
+
"""The dashboard bucket: component-first, legacy fallback.
|
|
950
|
+
|
|
951
|
+
Returns ``(records, failed_count)``. The component's in-process
|
|
952
|
+
``search`` frame (issue #2008) serves the exact-match, no-body shape
|
|
953
|
+
— the default ``ha_search`` call — mirroring the
|
|
954
|
+
``ha_config_get_dashboard(mode="search")`` routing; fuzzy scoring and
|
|
955
|
+
``include_config`` bodies are legacy-only (the component walk is
|
|
956
|
+
substring and its matches carry no bodies), as is every ``None``
|
|
957
|
+
fallback case of :meth:`_component_dashboard_search` — including a
|
|
958
|
+
YAML-bearing install, so component-vs-legacy never changes which
|
|
959
|
+
dashboards a search covers.
|
|
960
|
+
"""
|
|
961
|
+
if exact_match and not include_config:
|
|
962
|
+
component = await self._component_dashboard_search(query_lower)
|
|
963
|
+
if component is not None:
|
|
964
|
+
return component
|
|
965
|
+
return await self._deep_search_dashboards(query_lower, exact_match, semaphore)
|
|
966
|
+
|
|
967
|
+
async def _component_dashboard_search(
|
|
968
|
+
self, query_lower: str
|
|
969
|
+
) -> tuple[list[dict[str, Any]], int] | None:
|
|
970
|
+
"""The dashboard bucket via the component's in-process ``search`` frame.
|
|
971
|
+
|
|
972
|
+
Returns ``(records, failed_count)`` — one legacy-shaped record per
|
|
973
|
+
matching dashboard from the component's ``document_matches`` (a
|
|
974
|
+
whole-document substring verdict per storage dashboard, ported from
|
|
975
|
+
``_search_in_dict`` leaf for leaf, so view titles and dashboard-level
|
|
976
|
+
keys match exactly as the legacy walk matches them); ``failed_count``
|
|
977
|
+
is the component's ``load_failed`` (storage dashboards whose config
|
|
978
|
+
load raised — surfaced as the same ``dashboard(s) not scanned``
|
|
979
|
+
partial the legacy scan reports). A config-less auto-generated
|
|
980
|
+
dashboard is skipped in-process (nothing to scan), consistent with
|
|
981
|
+
:func:`_is_no_stored_config`.
|
|
982
|
+
|
|
983
|
+
``None`` ⇒ run the legacy per-dashboard walk instead: the component
|
|
984
|
+
lacks the ``dashboards_doc_search`` capability (older components
|
|
985
|
+
emit only the card-scoped ``matches``, which would silently narrow
|
|
986
|
+
coverage), any ``_dashboards_via_component`` fallback (capability
|
|
987
|
+
miss / command error / lovelace unavailable), an empty query, a
|
|
988
|
+
malformed ``document_matches`` — or a non-zero ``yaml_skipped``.
|
|
989
|
+
The component never reads YAML-mode dashboard bodies, but the legacy
|
|
990
|
+
walk this path replaces does (``lovelace/config`` loads them, and the
|
|
991
|
+
fuzzy / ``include_config`` routes still read the same resolved
|
|
992
|
+
configs), so serving the component result on a YAML-bearing install
|
|
993
|
+
would make the DEFAULT call shape permanently narrower and
|
|
994
|
+
permanently ``partial``. Falling back keeps coverage identical to
|
|
995
|
+
the pre-component behaviour; storage-only installs — where the
|
|
996
|
+
exclusion costs nothing — keep the single-frame win (review
|
|
997
|
+
follow-up on issue #2008).
|
|
998
|
+
"""
|
|
999
|
+
if not query_lower:
|
|
1000
|
+
return None
|
|
1001
|
+
caps = await get_component_caps(self.client)
|
|
1002
|
+
if not component_supports(caps, "dashboards_doc_search"):
|
|
1003
|
+
return None
|
|
1004
|
+
result = await _dashboards_via_component(
|
|
1005
|
+
self.client, "search", query=query_lower
|
|
1006
|
+
)
|
|
1007
|
+
if result is None:
|
|
1008
|
+
return None
|
|
1009
|
+
document_matches = result.get("document_matches")
|
|
1010
|
+
if not isinstance(document_matches, list):
|
|
1011
|
+
return None
|
|
1012
|
+
if int(result.get("yaml_skipped", 0) or 0) > 0:
|
|
1013
|
+
return None
|
|
1014
|
+
failed = int(result.get("load_failed", 0) or 0)
|
|
1015
|
+
|
|
1016
|
+
records: dict[str, dict[str, Any]] = {}
|
|
1017
|
+
for match in document_matches:
|
|
1018
|
+
if not isinstance(match, dict):
|
|
1019
|
+
continue
|
|
1020
|
+
raw_url_path = match.get("url_path")
|
|
1021
|
+
url_path = raw_url_path or "default"
|
|
1022
|
+
if url_path in records:
|
|
1023
|
+
continue
|
|
1024
|
+
# The default dashboard: the legacy walk always labels it
|
|
1025
|
+
# ("default", "Default Dashboard") — a taken-control default
|
|
1026
|
+
# may carry its own body title, but emitting it would change
|
|
1027
|
+
# the record shape between the component and legacy paths.
|
|
1028
|
+
title = (
|
|
1029
|
+
"Default Dashboard"
|
|
1030
|
+
if not raw_url_path
|
|
1031
|
+
else (match.get("title") or url_path)
|
|
1032
|
+
)
|
|
1033
|
+
records[url_path] = {
|
|
1034
|
+
"dashboard_url": url_path,
|
|
1035
|
+
"dashboard_title": title,
|
|
1036
|
+
"score": 100,
|
|
1037
|
+
"match_in_config": True,
|
|
1038
|
+
}
|
|
1039
|
+
return list(records.values()), failed
|
|
1040
|
+
|
|
899
1041
|
async def _search_one_dashboard(
|
|
900
1042
|
self,
|
|
901
1043
|
url_path: str,
|
|
@@ -912,6 +1054,15 @@ class DeepSearchMixin(SceneSearchMixin):
|
|
|
912
1054
|
distinct from a successful no-match. Surfacing it lets
|
|
913
1055
|
``ha_search(search_types=["dashboard"])`` report ``partial`` instead
|
|
914
1056
|
of a complete-looking empty result.
|
|
1057
|
+
|
|
1058
|
+
The no-stored-config envelope (``config_not_found`` + "No config
|
|
1059
|
+
found.", see :func:`_is_no_stored_config`) is the deliberate
|
|
1060
|
+
exception: an auto-generated dashboard (strategy-backed, never taken
|
|
1061
|
+
control of) has nothing to scan, so it reads as a clean no-match.
|
|
1062
|
+
Counting it as failed made every dashboard search on a stock install
|
|
1063
|
+
report ``partial`` (issue #2008). The code's other cause — "Unknown
|
|
1064
|
+
config specified", a dashboard deleted since the registry-list
|
|
1065
|
+
snapshot — stays a counted failure.
|
|
915
1066
|
"""
|
|
916
1067
|
async with semaphore:
|
|
917
1068
|
try:
|
|
@@ -932,6 +1083,12 @@ class DeepSearchMixin(SceneSearchMixin):
|
|
|
932
1083
|
# shape explicitly (``success is False``) so a missing-success
|
|
933
1084
|
# raw response still falls through to the ``result`` fallback.
|
|
934
1085
|
if isinstance(resp, dict) and resp.get("success") is False:
|
|
1086
|
+
if _is_no_stored_config(resp):
|
|
1087
|
+
logger.debug(
|
|
1088
|
+
f"Dashboard has no stored config ({url_path}); "
|
|
1089
|
+
"auto-generated — nothing to scan"
|
|
1090
|
+
)
|
|
1091
|
+
return [], False
|
|
935
1092
|
logger.debug(
|
|
936
1093
|
f"Dashboard config returned non-success ({url_path}): {resp!r}"
|
|
937
1094
|
)
|
{ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/tools/tools_config_dashboards.py
RENAMED
|
@@ -813,10 +813,12 @@ def _card_matches(
|
|
|
813
813
|
#
|
|
814
814
|
# Source: homeassistant/components/lovelace/websocket.py, _handle_errors —
|
|
815
815
|
# emits f"Unknown config specified: {url_path}" paired with structured
|
|
816
|
-
# error.code "config_not_found". The
|
|
817
|
-
#
|
|
818
|
-
#
|
|
819
|
-
#
|
|
816
|
+
# error.code "config_not_found". The client envelope does surface that code
|
|
817
|
+
# top-level (``error_code``), but HA reuses it for the no-stored-config case
|
|
818
|
+
# ("No config found." — an auto-generated dashboard), so the code alone cannot
|
|
819
|
+
# identify an unresolved identifier; the message substring remains the only
|
|
820
|
+
# discriminating signal. If HA reformats this string, the lazy fallback
|
|
821
|
+
# regresses silently to never firing — re-verify with major HA upgrades.
|
|
820
822
|
_LAZY_RESOLVE_TRIGGER = "Unknown config specified"
|
|
821
823
|
|
|
822
824
|
|
|
@@ -1283,7 +1285,8 @@ async def fetch_dashboards_list(
|
|
|
1283
1285
|
every fetch site rather than silently degrading.
|
|
1284
1286
|
|
|
1285
1287
|
When the ``ha_mcp_tools`` component advertises the ``dashboards`` capability
|
|
1286
|
-
the
|
|
1288
|
+
the rows — YAML rows included, tagged with ``mode`` (see
|
|
1289
|
+
``_component_dashboard_rows``) — are served from one in-process ``list`` frame (the
|
|
1287
1290
|
``_resolve_dashboard`` / ``_lookup_existing_dashboards`` / list-mode callers
|
|
1288
1291
|
all funnel here); otherwise the legacy ``lovelace/dashboards/list`` WS read
|
|
1289
1292
|
runs unchanged.
|
|
@@ -917,6 +917,33 @@ def _normalize_component_config_record(
|
|
|
917
917
|
return out
|
|
918
918
|
|
|
919
919
|
|
|
920
|
+
# Body surfaces the ``ha_mcp_tools`` component's ``search`` command accepts
|
|
921
|
+
# (its voluptuous allowlist also has ``entity``, appended separately by
|
|
922
|
+
# ``_build_component_search_request``). ``dashboard`` is deliberately absent:
|
|
923
|
+
# the component has no dashboard scanner, so a request naming it must stay on
|
|
924
|
+
# the legacy path — forwarding it just bounced off the component schema into a
|
|
925
|
+
# warning-laden fallback on every call (issue #2008).
|
|
926
|
+
_COMPONENT_BODY_SEARCH_TYPES: frozenset[str] = frozenset(
|
|
927
|
+
{"automation", "script", "scene", "helper"}
|
|
928
|
+
)
|
|
929
|
+
|
|
930
|
+
|
|
931
|
+
def _component_serves_search_types(req: _ResolvedSearch) -> bool:
|
|
932
|
+
"""True when the component's search command accepts every requested surface.
|
|
933
|
+
|
|
934
|
+
Only an explicit ``search_types`` list can name an unsupported surface, and
|
|
935
|
+
only the body-eligible branch forwards it to the component — a
|
|
936
|
+
body-ineligible request sends the entity surface alone, which the component
|
|
937
|
+
always accepts. Routing is all-or-nothing per command, so one unsupported
|
|
938
|
+
surface sends the whole request to the legacy path, silently — the same
|
|
939
|
+
treatment as the other route-ineligible modes, not the warning-emitting
|
|
940
|
+
failure fallback.
|
|
941
|
+
"""
|
|
942
|
+
if not req.body_eligible or req.parsed_search_types is None:
|
|
943
|
+
return True
|
|
944
|
+
return all(t in _COMPONENT_BODY_SEARCH_TYPES for t in req.parsed_search_types)
|
|
945
|
+
|
|
946
|
+
|
|
920
947
|
def _build_component_search_request(req: _ResolvedSearch) -> dict[str, Any]:
|
|
921
948
|
"""Translate resolved ha_search inputs into an ``ha_mcp_tools/search`` request.
|
|
922
949
|
|
|
@@ -1958,7 +1985,9 @@ class SearchTools:
|
|
|
1958
1985
|
# response keys) — keep the legacy path: their response contracts
|
|
1959
1986
|
# differ per mode, and after the request-dedup work they are cheap
|
|
1960
1987
|
# registry-only calls, so the component round-trip buys nothing worth
|
|
1961
|
-
# the shape risk.
|
|
1988
|
+
# the shape risk. A ``search_types`` naming a surface the component
|
|
1989
|
+
# lacks (``dashboard``) also stays legacy — see
|
|
1990
|
+
# ``_component_serves_search_types`` (issue #2008).
|
|
1962
1991
|
#
|
|
1963
1992
|
# Entity-visibility gate. A plain ``search`` component applies no
|
|
1964
1993
|
# filtering, so an install with an ACTIVE visibility filter would leak
|
|
@@ -1973,7 +2002,11 @@ class SearchTools:
|
|
|
1973
2002
|
# needs no analogous gate — it re-applies the filter server-side over the
|
|
1974
2003
|
# component's raw slices. Checked only when the component would otherwise
|
|
1975
2004
|
# serve, so the common (no-component / filter-off) install pays nothing.
|
|
1976
|
-
if
|
|
2005
|
+
if (
|
|
2006
|
+
req.query_text
|
|
2007
|
+
and not (req.area_filter or "").strip()
|
|
2008
|
+
and _component_serves_search_types(req)
|
|
2009
|
+
):
|
|
1977
2010
|
caps = await get_component_caps(self._client)
|
|
1978
2011
|
if component_supports(caps, "search"):
|
|
1979
2012
|
(
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/client/supervisor_client.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/client/websocket_client.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/client/websocket_listener.py
RENAMED
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/dashboard_screenshot/__init__.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/dashboard_screenshot/capture.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/dashboard_screenshot/content.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/dashboard_screenshot/paths.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/dashboard_screenshot/provision.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/resources/skills-vendor/AGENTS.md
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/resources/skills-vendor/CLAUDE.md
RENAMED
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/resources/skills-vendor/LICENSE
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/resources/skills-vendor/README.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/settings_ui/_handlers_advanced.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/settings_ui/_handlers_backups.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/settings_ui/_handlers_fs.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/settings_ui/_handlers_server.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/settings_ui/_handlers_theme.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.14.2.dev2113 → ha_mcp_dev-7.14.2.dev2117}/src/ha_mcp/settings_ui/_handlers_tools.py
RENAMED
|
File without changes
|