ha-mcp-dev 7.12.3.dev1934__tar.gz → 7.12.3.dev1937__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.12.3.dev1934/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.12.3.dev1937}/PKG-INFO +1 -1
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/pyproject.toml +1 -1
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/settings_ui/_tools_meta.py +10 -2
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_filesystem.py +29 -1
- ha_mcp_dev-7.12.3.dev1937/src/ha_mcp/tools/tools_yaml_read.py +336 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp_dev.egg-info/SOURCES.txt +1 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/LICENSE +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/MANIFEST.in +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/README.md +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/setup.cfg +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/__init__.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/__main__.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/_pypi_marker +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/_version.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/auth/__init__.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/auth/consent_form.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/auth/provider.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/backup_manager.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/browser_landing.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/client/__init__.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/client/rest_client.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/client/supervisor_client.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/client/websocket_client.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/client/websocket_listener.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/config.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/dashboard_screenshot/__init__.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/dashboard_screenshot/capture.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/dashboard_screenshot/content.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/dashboard_screenshot/paths.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/dashboard_screenshot/provision.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/errors.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/llm_exposure.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/policy/__init__.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/policy/approval_queue.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/policy/evaluator.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/policy/handlers.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/policy/middleware.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/policy/model.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/policy/persistence.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/policy/value_sources.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/py.typed +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/read_only.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/appdaemon.md +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/blueprint-guide.md +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/scenes.md +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/yaml-only-integrations.md +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/server.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/settings_ui/__init__.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/settings_ui/_handlers_advanced.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/settings_ui/_handlers_backups.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/settings_ui/_handlers_fs.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/settings_ui/_handlers_server.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/settings_ui/_handlers_theme.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/settings_ui/_handlers_tools.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/settings_ui/_persistence.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/settings_ui/_supervisor.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/settings_ui/_theme.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/settings_ui/settings.css +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/settings_ui/settings.html +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/settings_ui/settings.js +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/smoke_test.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/strict_bps.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/__init__.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/auto_backup.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/backup.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/best_practice_checker.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/component_api.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/config_entry_flow.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/device_control.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/enhanced.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/helpers.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/radio/__init__.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/radio/base.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/radio/matter.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/radio/thread.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/radio/zigbee.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/radio/zwave.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/reference_validator.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/registry.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/smart_search/__init__.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/smart_search/_base.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/smart_search/_config.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/smart_search/_deep.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/smart_search/_entities.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/smart_search/_fetch.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/smart_search/_overview.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/smart_search/_scenes.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/smart_search/_scoring.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tool_search_hint_middleware.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_addons.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_areas.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_blueprints.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_bug_report.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_calendar.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_camera.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_categories.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_code.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_config_automations.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_dashboard_screenshot.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_dev.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_energy.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_entities.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_groups.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_hacs.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_history.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_integrations.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_labels.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_mcp_component.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_radio.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_registry.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_resources.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_search.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_service.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_services.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_system.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_themes.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_todo.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_traces.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_updates.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_utility.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_zones.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/util_helpers.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/validation_middleware.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/transforms/__init__.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/transforms/categorized_search.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/transport_security.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/update_check.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/utils/__init__.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/utils/config_hash.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/utils/data_paths.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/utils/domain_handlers.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/utils/fuzzy_search.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/utils/operation_manager.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/utils/python_sandbox.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/utils/skill_loader.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/utils/usage_logger.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/visibility/__init__.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/visibility/model.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/visibility/persistence.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/visibility/resolver.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/tests/__init__.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/tests/test_constants.py +0 -0
- {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/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.12.3.
|
|
7
|
+
version = "7.12.3.dev1937"
|
|
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
|
{ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/settings_ui/_tools_meta.py
RENAMED
|
@@ -89,8 +89,9 @@ TRANSFORM_GENERATED_TOOLS: dict[str, ToolStub] = {}
|
|
|
89
89
|
# won't appear in local_provider._list_tools(), so we inject stub entries
|
|
90
90
|
# into the settings UI so users discover the tool exists and how to enable
|
|
91
91
|
# it. Keep this dict in sync with the ``"beta"`` tag added to each tool's
|
|
92
|
-
# source file (tools_yaml_config.py,
|
|
93
|
-
# tools_code.py) — a future rename or removal needs to
|
|
92
|
+
# source file (tools_yaml_config.py, tools_yaml_read.py, tools_filesystem.py,
|
|
93
|
+
# tools_mcp_component.py, tools_code.py) — a future rename or removal needs to
|
|
94
|
+
# land in both places.
|
|
94
95
|
FEATURE_GATED_TOOLS: dict[str, ToolStub] = {
|
|
95
96
|
"ha_config_set_yaml": {
|
|
96
97
|
"title": "Set YAML Config",
|
|
@@ -99,6 +100,13 @@ FEATURE_GATED_TOOLS: dict[str, ToolStub] = {
|
|
|
99
100
|
"disabled_by": "enable_yaml_config_editing",
|
|
100
101
|
"destructiveHint": True,
|
|
101
102
|
},
|
|
103
|
+
"ha_config_get_yaml": {
|
|
104
|
+
"title": "Read YAML Config Fragment",
|
|
105
|
+
"primary_tag": "System",
|
|
106
|
+
"description": "Read the YAML fragment under a key in a config file, or find which file defines it.",
|
|
107
|
+
"disabled_by": "enable_filesystem_tools",
|
|
108
|
+
"readOnlyHint": True,
|
|
109
|
+
},
|
|
102
110
|
"ha_manage_custom_tool": {
|
|
103
111
|
"title": "Custom Tool",
|
|
104
112
|
"primary_tag": "System",
|
{ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_filesystem.py
RENAMED
|
@@ -74,7 +74,14 @@ CALLER_TOKEN_BOOTSTRAP_SERVICE = "get_caller_token"
|
|
|
74
74
|
# ENABLE_YAML_EDIT_CONFIRM, default on); a <0.11.0 component's strict
|
|
75
75
|
# (PREVENT_EXTRA) schema rejects the unknown arg with a raw voluptuous
|
|
76
76
|
# error — the gate surfaces an actionable "update" prompt instead.
|
|
77
|
-
|
|
77
|
+
# 1.1.0: the YAML fragment read (#1788) needs two component behaviours that a
|
|
78
|
+
# <1.1.0 component reports no differently from a missing key. ``list_files``
|
|
79
|
+
# only now honours the configured packages folder, so ha_config_get_yaml's
|
|
80
|
+
# glob/discovery would otherwise come back "Path not allowed" instead of
|
|
81
|
+
# enumerating packages; and ``include_parsed`` on ``read_file`` is a new arg
|
|
82
|
+
# that an older strict (PREVENT_EXTRA) schema rejects with a raw voluptuous
|
|
83
|
+
# error. The gate turns both into the actionable "update" prompt.
|
|
84
|
+
MIN_COMPONENT_VERSION = "1.1.0"
|
|
78
85
|
|
|
79
86
|
|
|
80
87
|
def _version_tuple(version: str) -> tuple[int, ...]:
|
|
@@ -511,6 +518,19 @@ class FilesystemTools:
|
|
|
511
518
|
),
|
|
512
519
|
),
|
|
513
520
|
] = None,
|
|
521
|
+
yaml_path: Annotated[
|
|
522
|
+
str | None,
|
|
523
|
+
Field(
|
|
524
|
+
default=None,
|
|
525
|
+
description=(
|
|
526
|
+
"Dotted YAML key path (e.g. 'alert2', 'mqtt.sensor'). When "
|
|
527
|
+
"set, the response also carries 'subtree': the round-trip "
|
|
528
|
+
"text of just that key's value. To look a key up across "
|
|
529
|
+
"packages/*.yaml, or to get it as structured data, use "
|
|
530
|
+
"ha_config_get_yaml instead."
|
|
531
|
+
),
|
|
532
|
+
),
|
|
533
|
+
] = None,
|
|
514
534
|
) -> dict[str, Any]:
|
|
515
535
|
"""Read a file from the Home Assistant config directory.
|
|
516
536
|
|
|
@@ -541,6 +561,9 @@ class FilesystemTools:
|
|
|
541
561
|
- size: File size in bytes
|
|
542
562
|
- modified: Last modification timestamp
|
|
543
563
|
- path: The file path that was read
|
|
564
|
+
- subtree: Round-trip text of the `yaml_path` key, when that arg is set
|
|
565
|
+
(null when the key is absent). Comments and HA tags (`!secret`,
|
|
566
|
+
`!include`) survive as written — a `!secret` is never resolved.
|
|
544
567
|
|
|
545
568
|
**Example:**
|
|
546
569
|
```python
|
|
@@ -549,6 +572,9 @@ class FilesystemTools:
|
|
|
549
572
|
|
|
550
573
|
# Read last 100 lines of log
|
|
551
574
|
result = ha_read_file(path="home-assistant.log", tail_lines=100)
|
|
575
|
+
|
|
576
|
+
# Read just the alert2 block out of a package file
|
|
577
|
+
result = ha_read_file(path="packages/alert2.yaml", yaml_path="alert2")
|
|
552
578
|
```
|
|
553
579
|
"""
|
|
554
580
|
try:
|
|
@@ -559,6 +585,8 @@ class FilesystemTools:
|
|
|
559
585
|
service_data: dict[str, Any] = {"path": path}
|
|
560
586
|
if tail_lines is not None:
|
|
561
587
|
service_data["tail_lines"] = tail_lines
|
|
588
|
+
if yaml_path is not None:
|
|
589
|
+
service_data["yaml_path"] = yaml_path
|
|
562
590
|
|
|
563
591
|
# Call the custom component service
|
|
564
592
|
result = await call_mcp_tools_service(
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
"""Read-only YAML fragment lookup for Home Assistant config files.
|
|
2
|
+
|
|
3
|
+
The read companion to ``ha_config_set_yaml``: same ``file`` + ``yaml_path``
|
|
4
|
+
addressing, so a fragment found here can be handed straight back to the editor.
|
|
5
|
+
|
|
6
|
+
**Dependency:** Requires the ha_mcp_tools custom component (the round-trip parse
|
|
7
|
+
needs ``ruamel``, which the component carries and the MCP server does not).
|
|
8
|
+
|
|
9
|
+
Gating: behind ``enable_filesystem_tools``, NOT ENABLE_YAML_CONFIG_EDITING. The
|
|
10
|
+
editing flag gates *edits*, and reading a fragment is not one — but this returns
|
|
11
|
+
config-file contents through the same ``read_file``/``list_files`` component
|
|
12
|
+
services as ``ha_read_file``/``ha_list_files``, so it belongs behind the same
|
|
13
|
+
flag those are behind. Registering it unconditionally would hand an install that
|
|
14
|
+
deliberately turned filesystem tools off a config-file read surface through the
|
|
15
|
+
back door.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import asyncio
|
|
19
|
+
import logging
|
|
20
|
+
from typing import Annotated, Any
|
|
21
|
+
|
|
22
|
+
from fastmcp.exceptions import ToolError
|
|
23
|
+
from fastmcp.tools import tool
|
|
24
|
+
from pydantic import Field
|
|
25
|
+
|
|
26
|
+
from ..errors import ErrorCode, create_error_response
|
|
27
|
+
from .helpers import (
|
|
28
|
+
exception_to_structured_error,
|
|
29
|
+
log_tool_usage,
|
|
30
|
+
raise_tool_error,
|
|
31
|
+
register_tool_methods,
|
|
32
|
+
)
|
|
33
|
+
from .tools_filesystem import (
|
|
34
|
+
_assert_mcp_tools_available,
|
|
35
|
+
call_mcp_tools_service,
|
|
36
|
+
is_filesystem_tools_enabled,
|
|
37
|
+
)
|
|
38
|
+
from .util_helpers import unwrap_service_response
|
|
39
|
+
|
|
40
|
+
logger = logging.getLogger(__name__)
|
|
41
|
+
|
|
42
|
+
# fnmatch metacharacters. Matching itself happens component-side (list_files
|
|
43
|
+
# fnmatches on the file name); this only decides single-file vs. glob.
|
|
44
|
+
_GLOB_METACHARS = ("*", "?", "[")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _has_glob(file: str) -> bool:
|
|
48
|
+
"""True if ``file`` carries an fnmatch wildcard and must be expanded."""
|
|
49
|
+
return any(char in file for char in _GLOB_METACHARS)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _call_failed(service: str, context: dict[str, Any]) -> None:
|
|
53
|
+
"""Raise the shared 'unexpected response' error for a component service."""
|
|
54
|
+
raise_tool_error(
|
|
55
|
+
create_error_response(
|
|
56
|
+
ErrorCode.SERVICE_CALL_FAILED,
|
|
57
|
+
f"Unexpected response format from {service} service",
|
|
58
|
+
context=context,
|
|
59
|
+
)
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _unwrap_or_raise(result: Any, service: str, context: dict[str, Any]) -> Any:
|
|
64
|
+
"""Unwrap a component service response, raising on a failure payload."""
|
|
65
|
+
if not isinstance(result, dict):
|
|
66
|
+
_call_failed(service, context)
|
|
67
|
+
unwrapped = unwrap_service_response(result)
|
|
68
|
+
if not unwrapped.get("success", True):
|
|
69
|
+
raise_tool_error(unwrapped)
|
|
70
|
+
return unwrapped
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _failure_text(payload: dict[str, Any]) -> str:
|
|
74
|
+
"""Readable reason out of a failure payload, for a per-file warning.
|
|
75
|
+
|
|
76
|
+
The component answers with ``error`` as a plain string; the shared
|
|
77
|
+
``create_error_response`` shape nests a ``message`` under it instead.
|
|
78
|
+
"""
|
|
79
|
+
error = payload.get("error")
|
|
80
|
+
if isinstance(error, dict):
|
|
81
|
+
return str(error.get("message") or error)
|
|
82
|
+
return str(error or "read failed")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _evaluate_read(
|
|
86
|
+
response: Any,
|
|
87
|
+
target: str,
|
|
88
|
+
yaml_path: str,
|
|
89
|
+
*,
|
|
90
|
+
is_glob: bool,
|
|
91
|
+
include_content: bool,
|
|
92
|
+
include_parsed: bool,
|
|
93
|
+
) -> tuple[dict[str, Any] | None, str | None]:
|
|
94
|
+
"""Turn one ``read_file`` response into a match, a warning, or neither.
|
|
95
|
+
|
|
96
|
+
Three outcomes, in the order they are decided:
|
|
97
|
+
|
|
98
|
+
* **warning** — the file could not be searched at all. Under a glob that
|
|
99
|
+
must not sink the whole search and discard the matches already found:
|
|
100
|
+
the glob is not restricted to ``*.yaml``, so ``packages/*`` can turn up
|
|
101
|
+
a README the component refuses to read, and a permission-denied or
|
|
102
|
+
non-UTF-8 ``.yaml`` lands here too. A syntactically broken file is the
|
|
103
|
+
same class — reporting it as "key absent" would claim a file was
|
|
104
|
+
inspected when it never was. A single explicit target has no siblings
|
|
105
|
+
to salvage, so it raises instead.
|
|
106
|
+
* **neither** — the file parsed and simply has no such key. The real
|
|
107
|
+
non-match, and the whole point of a glob search.
|
|
108
|
+
* **match** — the key is there.
|
|
109
|
+
"""
|
|
110
|
+
# Only reachable under a glob: with a single target return_exceptions is
|
|
111
|
+
# False, so gather itself raised.
|
|
112
|
+
if isinstance(response, BaseException):
|
|
113
|
+
return None, f"{target} was not searched: {response}."
|
|
114
|
+
|
|
115
|
+
if not isinstance(response, dict):
|
|
116
|
+
if is_glob:
|
|
117
|
+
return None, f"{target} was not searched: unexpected read_file response."
|
|
118
|
+
_call_failed("read_file", {"file": target, "yaml_path": yaml_path})
|
|
119
|
+
unwrapped = unwrap_service_response(response)
|
|
120
|
+
|
|
121
|
+
if not unwrapped.get("success", True):
|
|
122
|
+
if not is_glob:
|
|
123
|
+
raise_tool_error(unwrapped)
|
|
124
|
+
return None, f"{target} was not searched: {_failure_text(unwrapped)}."
|
|
125
|
+
|
|
126
|
+
parse_error = unwrapped.get("parse_error")
|
|
127
|
+
if parse_error:
|
|
128
|
+
return None, f"{target} was not searched: {parse_error}."
|
|
129
|
+
|
|
130
|
+
if unwrapped.get("subtree") is None:
|
|
131
|
+
return None, None
|
|
132
|
+
|
|
133
|
+
match: dict[str, Any] = {"file": target, "yaml_path": yaml_path}
|
|
134
|
+
if include_content:
|
|
135
|
+
match["content"] = unwrapped["subtree"]
|
|
136
|
+
if include_parsed:
|
|
137
|
+
match["parsed"] = unwrapped.get("parsed")
|
|
138
|
+
return match, None
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
async def _resolve_target_files(client: Any, file: str) -> list[str]:
|
|
142
|
+
"""Expand ``file`` into the concrete config-relative paths to read.
|
|
143
|
+
|
|
144
|
+
A plain path is returned as-is (the read itself reports a missing file). A
|
|
145
|
+
glob is expanded through the component's ``list_files``, which matches the
|
|
146
|
+
file name only — so ``packages/*.yaml`` covers one directory level, not a
|
|
147
|
+
nested tree.
|
|
148
|
+
"""
|
|
149
|
+
if not _has_glob(file):
|
|
150
|
+
return [file]
|
|
151
|
+
|
|
152
|
+
directory, _, pattern = file.rpartition("/")
|
|
153
|
+
unwrapped = _unwrap_or_raise(
|
|
154
|
+
await call_mcp_tools_service(
|
|
155
|
+
client,
|
|
156
|
+
"list_files",
|
|
157
|
+
{"path": directory or ".", "pattern": pattern},
|
|
158
|
+
),
|
|
159
|
+
"list_files",
|
|
160
|
+
{"file": file},
|
|
161
|
+
)
|
|
162
|
+
entries = unwrapped.get("files")
|
|
163
|
+
if not isinstance(entries, list):
|
|
164
|
+
_call_failed("list_files", {"file": file})
|
|
165
|
+
# Sorted so a multi-file result is deterministic across calls.
|
|
166
|
+
return sorted(
|
|
167
|
+
str(entry["path"])
|
|
168
|
+
for entry in entries
|
|
169
|
+
if isinstance(entry, dict) and entry.get("path") and not entry.get("is_dir")
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class YamlReadTools:
|
|
174
|
+
def __init__(self, client: Any) -> None:
|
|
175
|
+
self._client = client
|
|
176
|
+
|
|
177
|
+
@tool(
|
|
178
|
+
name="ha_config_get_yaml",
|
|
179
|
+
tags={"System", "beta"},
|
|
180
|
+
annotations={
|
|
181
|
+
"readOnlyHint": True,
|
|
182
|
+
"openWorldHint": False,
|
|
183
|
+
"title": "Read YAML Config Fragment",
|
|
184
|
+
},
|
|
185
|
+
)
|
|
186
|
+
@log_tool_usage
|
|
187
|
+
async def ha_config_get_yaml(
|
|
188
|
+
self,
|
|
189
|
+
yaml_path: Annotated[
|
|
190
|
+
str,
|
|
191
|
+
Field(
|
|
192
|
+
description=(
|
|
193
|
+
"Dotted YAML key path to look up, e.g. 'alert2', 'mqtt', "
|
|
194
|
+
"'template'. Any key is readable — unlike ha_config_set_yaml, "
|
|
195
|
+
"which only writes an allowlisted set."
|
|
196
|
+
),
|
|
197
|
+
),
|
|
198
|
+
],
|
|
199
|
+
file: Annotated[
|
|
200
|
+
str,
|
|
201
|
+
Field(
|
|
202
|
+
default="configuration.yaml",
|
|
203
|
+
description=(
|
|
204
|
+
"Config-relative file to read. Accepts an fnmatch glob to "
|
|
205
|
+
"search several files at once — 'packages/*.yaml' matches one "
|
|
206
|
+
"directory level, not a nested tree. Use the glob to find "
|
|
207
|
+
"which file defines a key."
|
|
208
|
+
),
|
|
209
|
+
),
|
|
210
|
+
] = "configuration.yaml",
|
|
211
|
+
include_content: Annotated[
|
|
212
|
+
bool,
|
|
213
|
+
Field(
|
|
214
|
+
default=True,
|
|
215
|
+
description=(
|
|
216
|
+
"Return the round-trip YAML text of each match. Set False to "
|
|
217
|
+
"discover only which files define the key."
|
|
218
|
+
),
|
|
219
|
+
),
|
|
220
|
+
] = True,
|
|
221
|
+
include_parsed: Annotated[
|
|
222
|
+
bool,
|
|
223
|
+
Field(
|
|
224
|
+
default=False,
|
|
225
|
+
description=(
|
|
226
|
+
"Additionally return each match as structured data. HA tags "
|
|
227
|
+
"stay in source form ('!secret api_key'), never resolved."
|
|
228
|
+
),
|
|
229
|
+
),
|
|
230
|
+
] = False,
|
|
231
|
+
) -> dict[str, Any]:
|
|
232
|
+
"""Get the current YAML fragment under a key, from one config file or across a glob.
|
|
233
|
+
|
|
234
|
+
Use before ha_config_set_yaml to inspect what a key currently holds, and
|
|
235
|
+
to find which file defines it — the returned ``file`` + ``yaml_path`` are
|
|
236
|
+
exactly the arguments that address the same fragment for an edit.
|
|
237
|
+
|
|
238
|
+
Not for storage-mode items: automations, scripts and scenes created
|
|
239
|
+
through the UI live outside YAML — read those with ha_config_get_automation,
|
|
240
|
+
ha_config_get_script and ha_config_get_scene. This tool sees only what is
|
|
241
|
+
written in the config files themselves.
|
|
242
|
+
|
|
243
|
+
Reads the file, so it reflects what is on disk rather than what Home
|
|
244
|
+
Assistant currently has loaded; a fragment edited but not yet reloaded
|
|
245
|
+
differs from the running config. Comments and HA tags survive as written
|
|
246
|
+
and a ``!secret`` is never resolved to its value, so ``content`` can be
|
|
247
|
+
handed back to ha_config_set_yaml unchanged. Files outside the read
|
|
248
|
+
allowlist stay unreadable, and secrets.yaml reads back with its values
|
|
249
|
+
masked.
|
|
250
|
+
"""
|
|
251
|
+
try:
|
|
252
|
+
await _assert_mcp_tools_available(self._client)
|
|
253
|
+
|
|
254
|
+
is_glob = _has_glob(file)
|
|
255
|
+
targets = await _resolve_target_files(self._client, file)
|
|
256
|
+
|
|
257
|
+
extra: dict[str, Any] = {"include_parsed": True} if include_parsed else {}
|
|
258
|
+
# The reads are independent, so fan them out rather than paying N
|
|
259
|
+
# sequential round-trips to HA — a packages glob is routinely 10+
|
|
260
|
+
# files. gather preserves order, so matches stay sorted by file.
|
|
261
|
+
# Under a glob a single read blowing up must not sink its siblings,
|
|
262
|
+
# so exceptions come back as values to be warned about below.
|
|
263
|
+
responses = await asyncio.gather(
|
|
264
|
+
*(
|
|
265
|
+
call_mcp_tools_service(
|
|
266
|
+
self._client,
|
|
267
|
+
"read_file",
|
|
268
|
+
{"path": target, "yaml_path": yaml_path, **extra},
|
|
269
|
+
)
|
|
270
|
+
for target in targets
|
|
271
|
+
),
|
|
272
|
+
return_exceptions=is_glob,
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
matches: list[dict[str, Any]] = []
|
|
276
|
+
warnings: list[str] = []
|
|
277
|
+
for target, response in zip(targets, responses, strict=True):
|
|
278
|
+
match, warning = _evaluate_read(
|
|
279
|
+
response,
|
|
280
|
+
target,
|
|
281
|
+
yaml_path,
|
|
282
|
+
is_glob=is_glob,
|
|
283
|
+
include_content=include_content,
|
|
284
|
+
include_parsed=include_parsed,
|
|
285
|
+
)
|
|
286
|
+
if warning:
|
|
287
|
+
warnings.append(warning)
|
|
288
|
+
elif match:
|
|
289
|
+
matches.append(match)
|
|
290
|
+
|
|
291
|
+
result: dict[str, Any] = {
|
|
292
|
+
"success": True,
|
|
293
|
+
"yaml_path": yaml_path,
|
|
294
|
+
"matches": matches,
|
|
295
|
+
"count": len(matches),
|
|
296
|
+
# Separates "the glob matched no files" from "no file defines
|
|
297
|
+
# the key" — same empty matches[], different fix.
|
|
298
|
+
"files_searched": len(targets),
|
|
299
|
+
}
|
|
300
|
+
if warnings:
|
|
301
|
+
result["warnings"] = warnings
|
|
302
|
+
return result
|
|
303
|
+
|
|
304
|
+
except ToolError:
|
|
305
|
+
raise
|
|
306
|
+
except Exception as e:
|
|
307
|
+
exception_to_structured_error(
|
|
308
|
+
e,
|
|
309
|
+
context={
|
|
310
|
+
"tool": "ha_config_get_yaml",
|
|
311
|
+
"file": file,
|
|
312
|
+
"yaml_path": yaml_path,
|
|
313
|
+
},
|
|
314
|
+
)
|
|
315
|
+
# Unreachable (the call is typed NoReturn) but explicit, because
|
|
316
|
+
# CodeQL cannot see the NoReturn and would otherwise read this
|
|
317
|
+
# handler as falling through to an implicit None (py/mixed-returns).
|
|
318
|
+
# The sibling tools additionally carry a `return None` AFTER the
|
|
319
|
+
# try/except; that shape only works because their try ends in a
|
|
320
|
+
# raise_tool_error() call CodeQL treats as returning. This try ends
|
|
321
|
+
# in a real return, so the same trailer is provably dead there
|
|
322
|
+
# (py/unreachable-statement).
|
|
323
|
+
return None
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def register_yaml_read_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
|
|
327
|
+
"""Register the read-only YAML lookup tool.
|
|
328
|
+
|
|
329
|
+
Behind ``enable_filesystem_tools`` (see module docstring): this returns
|
|
330
|
+
config-file contents, so it registers with the same flag as the other
|
|
331
|
+
file-read tools rather than with the YAML *editing* flag.
|
|
332
|
+
"""
|
|
333
|
+
if not is_filesystem_tools_enabled():
|
|
334
|
+
logger.debug("YAML read tool disabled (set HAMCP_ENABLE_FILESYSTEM_TOOLS=true)")
|
|
335
|
+
return
|
|
336
|
+
register_tool_methods(mcp, YamlReadTools(client))
|
|
@@ -129,6 +129,7 @@ src/ha_mcp/tools/tools_updates.py
|
|
|
129
129
|
src/ha_mcp/tools/tools_utility.py
|
|
130
130
|
src/ha_mcp/tools/tools_voice_assistant.py
|
|
131
131
|
src/ha_mcp/tools/tools_yaml_config.py
|
|
132
|
+
src/ha_mcp/tools/tools_yaml_read.py
|
|
132
133
|
src/ha_mcp/tools/tools_zones.py
|
|
133
134
|
src/ha_mcp/tools/util_helpers.py
|
|
134
135
|
src/ha_mcp/tools/validation_middleware.py
|
|
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.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/client/supervisor_client.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/client/websocket_client.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/client/websocket_listener.py
RENAMED
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/dashboard_screenshot/__init__.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/dashboard_screenshot/capture.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/dashboard_screenshot/content.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/dashboard_screenshot/paths.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/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
|
{ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/resources/skills-vendor/AGENTS.md
RENAMED
|
File without changes
|
{ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/resources/skills-vendor/CLAUDE.md
RENAMED
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/resources/skills-vendor/LICENSE
RENAMED
|
File without changes
|
{ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/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.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/settings_ui/_handlers_advanced.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/settings_ui/_handlers_backups.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/settings_ui/_handlers_fs.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/settings_ui/_handlers_server.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/settings_ui/_handlers_theme.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/settings_ui/_handlers_tools.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/settings_ui/_persistence.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/settings_ui/_supervisor.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/settings_ui/settings.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/stdio_settings_sidecar.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/best_practice_checker.py
RENAMED
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/config_entry_flow.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
|
{ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/reference_validator.py
RENAMED
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/smart_search/__init__.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/smart_search/_base.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/smart_search/_config.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/smart_search/_deep.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/smart_search/_entities.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/smart_search/_fetch.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/smart_search/_overview.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/smart_search/_scenes.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/smart_search/_scoring.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_blueprints.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_bug_report.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_categories.py
RENAMED
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_config_automations.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_config_dashboards.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_config_helpers.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_config_scenes.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_config_scripts.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
|
{ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_integrations.py
RENAMED
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_mcp_component.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
|
{ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_voice_assistant.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_yaml_config.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/validation_middleware.py
RENAMED
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/transforms/categorized_search.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/transforms/lite_docstrings.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
|
{ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/utils/kill_signal_diagnostics.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/utils/operation_manager.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/visibility/persistence.py
RENAMED
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp_dev.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp_dev.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp_dev.egg-info/requires.txt
RENAMED
|
File without changes
|
{ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp_dev.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|