ha-mcp-dev 7.14.2.dev2162__tar.gz → 7.14.2.dev2165__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.dev2162/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.14.2.dev2165}/PKG-INFO +2 -2
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/README.md +1 -1
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/pyproject.toml +1 -1
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/__init__.py +1 -1
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/client/rest_client.py +0 -77
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/client/websocket_listener.py +0 -123
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/config.py +2 -32
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/policy/value_sources.py +0 -4
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/server.py +4 -54
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/settings_ui/locales/de.json +0 -2
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/settings_ui/locales/en.json +0 -2
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/settings_ui/locales/fr.json +0 -2
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/settings_ui/locales/ru.json +0 -2
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/settings_ui/locales/zh-Hans.json +0 -2
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/settings_ui/settings.js +4 -5
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/smart_search/_deep.py +2 -2
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/tools_config_dashboards.py +1 -51
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/tools_search.py +8 -8
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/tools_utility.py +2 -2
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/utils/domain_handlers.py +0 -172
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/utils/fuzzy_search.py +0 -40
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/utils/operation_manager.py +0 -60
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165/src/ha_mcp_dev.egg-info}/PKG-INFO +2 -2
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -1
- ha_mcp_dev-7.14.2.dev2162/src/ha_mcp/tools/enhanced.py +0 -190
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/LICENSE +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/MANIFEST.in +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/setup.cfg +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/__main__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/_pypi_marker +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/_version.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/auth/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/auth/consent_form.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/auth/provider.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/backup_manager.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/browser_landing.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/client/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/client/supervisor_client.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/client/websocket_client.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/dashboard_screenshot/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/dashboard_screenshot/capture.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/dashboard_screenshot/content.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/dashboard_screenshot/paths.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/dashboard_screenshot/provision.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/dashboard_screenshot/theme_guard.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/errors.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/llm_exposure.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/policy/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/policy/approval_queue.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/policy/evaluator.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/policy/handlers.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/policy/middleware.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/policy/model.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/policy/persistence.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/py.typed +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/read_only.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/appdaemon.md +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/blueprint-guide.md +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/scenes.md +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/yaml-only-integrations.md +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/settings_ui/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/settings_ui/_handlers_advanced.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/settings_ui/_handlers_backups.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/settings_ui/_handlers_fs.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/settings_ui/_handlers_server.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/settings_ui/_handlers_theme.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/settings_ui/_handlers_tools.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/settings_ui/_i18n.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/settings_ui/_persistence.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/settings_ui/_supervisor.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/settings_ui/_theme.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/settings_ui/_tools_meta.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/settings_ui/locales/README.md +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/settings_ui/locales/es.json +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/settings_ui/settings.css +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/settings_ui/settings.html +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/smoke_test.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/strict_bps.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/auto_backup.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/backup.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/best_practice_checker.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/component_api.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/component_config_reads.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/component_devices.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/component_registries.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/component_registry_lookup.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/config_entry_flow.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/device_control.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/helpers.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/radio/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/radio/base.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/radio/matter.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/radio/thread.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/radio/zigbee.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/radio/zwave.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/reference_validator.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/registry.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/smart_search/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/smart_search/_base.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/smart_search/_config.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/smart_search/_entities.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/smart_search/_fetch.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/smart_search/_overview.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/smart_search/_scenes.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/smart_search/_scoring.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/tool_search_hint_middleware.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/tools_addons.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/tools_areas.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/tools_blueprints.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/tools_bug_report.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/tools_calendar.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/tools_camera.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/tools_categories.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/tools_code.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/tools_config_automations.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/tools_dashboard_screenshot.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/tools_dev.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/tools_energy.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/tools_entities.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/tools_filesystem.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/tools_groups.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/tools_hacs.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/tools_history.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/tools_integrations.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/tools_labels.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/tools_radio.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/tools_registry.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/tools_resources.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/tools_service.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/tools_services.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/tools_system.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/tools_themes.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/tools_todo.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/tools_traces.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/tools_updates.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/tools_yaml_read.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/tools_zones.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/util_helpers.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/validation_middleware.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/transforms/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/transforms/categorized_search.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/transport_security.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/update_check.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/utils/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/utils/config_hash.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/utils/config_write_lock.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/utils/data_paths.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/utils/python_sandbox.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/utils/skill_loader.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/utils/usage_logger.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/visibility/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/visibility/enforcement.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/visibility/model.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/visibility/persistence.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/visibility/resolver.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/tests/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/tests/test_constants.py +0 -0
- {ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/tests/test_env_manager.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ha-mcp-dev
|
|
3
|
-
Version: 7.14.2.
|
|
3
|
+
Version: 7.14.2.dev2165
|
|
4
4
|
Summary: Home Assistant MCP Server - Complete control of Home Assistant through MCP
|
|
5
5
|
Author-email: Julien <github@qc-h.net>
|
|
6
6
|
License: MIT
|
|
@@ -370,7 +370,7 @@ If your model can't see the tools or your Home Assistant, it may be getting hand
|
|
|
370
370
|
|
|
371
371
|
### Enable search-based discovery
|
|
372
372
|
|
|
373
|
-
Set ENABLE_TOOL_SEARCH=true (or toggle the option in the HA add-on). The full catalog is replaced in the tool list with four entry points plus a small set of always-visible "pinned" tools (
|
|
373
|
+
Set ENABLE_TOOL_SEARCH=true (or toggle the option in the HA add-on). The full catalog is replaced in the tool list with four entry points plus a small set of always-visible "pinned" tools (ha_search, ha_get_overview, ha_report_issue, etc.). All tools remain callable directly by name once discovered:
|
|
374
374
|
|
|
375
375
|
| Tool | Purpose |
|
|
376
376
|
|------|---------|
|
|
@@ -337,7 +337,7 @@ If your model can't see the tools or your Home Assistant, it may be getting hand
|
|
|
337
337
|
|
|
338
338
|
### Enable search-based discovery
|
|
339
339
|
|
|
340
|
-
Set ENABLE_TOOL_SEARCH=true (or toggle the option in the HA add-on). The full catalog is replaced in the tool list with four entry points plus a small set of always-visible "pinned" tools (
|
|
340
|
+
Set ENABLE_TOOL_SEARCH=true (or toggle the option in the HA add-on). The full catalog is replaced in the tool list with four entry points plus a small set of always-visible "pinned" tools (ha_search, ha_get_overview, ha_report_issue, etc.). All tools remain callable directly by name once discovered:
|
|
341
341
|
|
|
342
342
|
| Tool | Purpose |
|
|
343
343
|
|------|---------|
|
|
@@ -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.dev2165"
|
|
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
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Home Assistant MCP Server
|
|
3
3
|
|
|
4
4
|
A Model Context Protocol server that provides complete control over Home Assistant
|
|
5
|
-
through REST API and WebSocket integration
|
|
5
|
+
through REST API and WebSocket integration.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
from ._version import get_version
|
|
@@ -412,28 +412,6 @@ class HomeAssistantClient:
|
|
|
412
412
|
logger.debug(f"Fetching state for entity: {entity_id}")
|
|
413
413
|
return await self._request("GET", f"/states/{entity_id}")
|
|
414
414
|
|
|
415
|
-
async def set_entity_state(
|
|
416
|
-
self, entity_id: str, state: str, attributes: dict[str, Any] | None = None
|
|
417
|
-
) -> dict[str, Any]:
|
|
418
|
-
"""
|
|
419
|
-
Set entity state.
|
|
420
|
-
|
|
421
|
-
Args:
|
|
422
|
-
entity_id: Entity ID
|
|
423
|
-
state: New state value
|
|
424
|
-
attributes: Optional attributes dictionary
|
|
425
|
-
|
|
426
|
-
Returns:
|
|
427
|
-
Updated entity state
|
|
428
|
-
"""
|
|
429
|
-
logger.debug(f"Setting state for entity {entity_id} to {state}")
|
|
430
|
-
|
|
431
|
-
payload: dict[str, Any] = {"state": state}
|
|
432
|
-
if attributes:
|
|
433
|
-
payload["attributes"] = attributes
|
|
434
|
-
|
|
435
|
-
return await self._request("POST", f"/states/{entity_id}", json=payload)
|
|
436
|
-
|
|
437
415
|
async def call_service(
|
|
438
416
|
self,
|
|
439
417
|
domain: str,
|
|
@@ -490,41 +468,6 @@ class HomeAssistantClient:
|
|
|
490
468
|
logger.debug("Fetching available services")
|
|
491
469
|
return await self._request("GET", "/services")
|
|
492
470
|
|
|
493
|
-
async def get_history(
|
|
494
|
-
self,
|
|
495
|
-
entity_id: str | None = None,
|
|
496
|
-
start_time: str | None = None,
|
|
497
|
-
end_time: str | None = None,
|
|
498
|
-
) -> list[list[dict[str, Any]]]:
|
|
499
|
-
"""
|
|
500
|
-
Get historical data.
|
|
501
|
-
|
|
502
|
-
Args:
|
|
503
|
-
entity_id: Optional entity ID to filter
|
|
504
|
-
start_time: Optional start time (ISO format)
|
|
505
|
-
end_time: Optional end time (ISO format)
|
|
506
|
-
|
|
507
|
-
Returns:
|
|
508
|
-
Historical data
|
|
509
|
-
"""
|
|
510
|
-
logger.debug(f"Fetching history for entity: {entity_id}")
|
|
511
|
-
|
|
512
|
-
params = {}
|
|
513
|
-
if start_time:
|
|
514
|
-
params["start_time"] = start_time
|
|
515
|
-
if end_time:
|
|
516
|
-
params["end_time"] = end_time
|
|
517
|
-
|
|
518
|
-
endpoint = "/history/period"
|
|
519
|
-
if entity_id:
|
|
520
|
-
endpoint += f"/{entity_id}"
|
|
521
|
-
|
|
522
|
-
result = await self._request("GET", endpoint, params=params)
|
|
523
|
-
if isinstance(result, list):
|
|
524
|
-
return result
|
|
525
|
-
else:
|
|
526
|
-
return []
|
|
527
|
-
|
|
528
471
|
async def get_logbook(
|
|
529
472
|
self,
|
|
530
473
|
entity_id: str | None = None,
|
|
@@ -962,15 +905,6 @@ class HomeAssistantClient:
|
|
|
962
905
|
logger.error(f"Failed to connect to Home Assistant: {e}")
|
|
963
906
|
return False, str(e)
|
|
964
907
|
|
|
965
|
-
async def get_system_health(self) -> dict[str, Any]:
|
|
966
|
-
"""Get system health information."""
|
|
967
|
-
logger.debug("Fetching system health")
|
|
968
|
-
try:
|
|
969
|
-
return await self._request("GET", "/system_health/info")
|
|
970
|
-
except HomeAssistantAPIError:
|
|
971
|
-
# System health might not be available in all HA instances
|
|
972
|
-
return {"status": "unknown", "message": "System health not available"}
|
|
973
|
-
|
|
974
908
|
# Automation Configuration Management
|
|
975
909
|
|
|
976
910
|
async def _resolve_automation_id(self, identifier: str) -> str:
|
|
@@ -2097,14 +2031,3 @@ class HomeAssistantClient:
|
|
|
2097
2031
|
status_code=405,
|
|
2098
2032
|
) from e
|
|
2099
2033
|
raise
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
async def create_client() -> HomeAssistantClient:
|
|
2103
|
-
"""Create and return a new Home Assistant client."""
|
|
2104
|
-
return HomeAssistantClient()
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
async def test_connection_with_config() -> tuple[bool, str | None]:
|
|
2108
|
-
"""Test connection using configuration settings."""
|
|
2109
|
-
async with HomeAssistantClient() as client:
|
|
2110
|
-
return await client.test_connection()
|
{ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/client/websocket_listener.py
RENAMED
|
@@ -7,7 +7,6 @@ WebSocket events and updates operation status in real-time.
|
|
|
7
7
|
|
|
8
8
|
import asyncio
|
|
9
9
|
import logging
|
|
10
|
-
from datetime import datetime
|
|
11
10
|
from typing import Any
|
|
12
11
|
|
|
13
12
|
from ..config import get_global_settings
|
|
@@ -29,13 +28,6 @@ class WebSocketListenerService:
|
|
|
29
28
|
self.cleanup_task: asyncio.Task | None = None
|
|
30
29
|
self.running = False
|
|
31
30
|
self._event_loop: asyncio.AbstractEventLoop | None = None
|
|
32
|
-
self.stats: dict[str, Any] = {
|
|
33
|
-
"events_processed": 0,
|
|
34
|
-
"operations_updated": 0,
|
|
35
|
-
"connection_errors": 0,
|
|
36
|
-
"last_event_time": None,
|
|
37
|
-
"start_time": None,
|
|
38
|
-
}
|
|
39
31
|
|
|
40
32
|
async def start(self) -> bool:
|
|
41
33
|
"""Start the WebSocket listener service.
|
|
@@ -64,7 +56,6 @@ class WebSocketListenerService:
|
|
|
64
56
|
self.cleanup_task = asyncio.create_task(self._periodic_cleanup())
|
|
65
57
|
|
|
66
58
|
self.running = True
|
|
67
|
-
self.stats["start_time"] = datetime.now()
|
|
68
59
|
|
|
69
60
|
logger.info("WebSocket listener service started successfully")
|
|
70
61
|
return True
|
|
@@ -141,11 +132,6 @@ class WebSocketListenerService:
|
|
|
141
132
|
entity_id: str | None = None
|
|
142
133
|
new_state: dict[str, Any] | None = None
|
|
143
134
|
try:
|
|
144
|
-
events_processed = self.stats["events_processed"]
|
|
145
|
-
if isinstance(events_processed, int):
|
|
146
|
-
self.stats["events_processed"] = events_processed + 1
|
|
147
|
-
self.stats["last_event_time"] = datetime.now()
|
|
148
|
-
|
|
149
135
|
# Extract event data — fields are nested under ``event["data"]``.
|
|
150
136
|
event_data = event.get("data") or {}
|
|
151
137
|
entity_id = event_data.get("entity_id")
|
|
@@ -164,11 +150,6 @@ class WebSocketListenerService:
|
|
|
164
150
|
# Update pending operations
|
|
165
151
|
updated_ops = update_pending_operations(entity_id, new_state)
|
|
166
152
|
if updated_ops:
|
|
167
|
-
operations_updated = self.stats["operations_updated"]
|
|
168
|
-
if isinstance(operations_updated, int):
|
|
169
|
-
self.stats["operations_updated"] = operations_updated + len(
|
|
170
|
-
updated_ops
|
|
171
|
-
)
|
|
172
153
|
logger.info(f"Updated {len(updated_ops)} operations for {entity_id}")
|
|
173
154
|
|
|
174
155
|
except (RuntimeError, ConnectionError, OSError) as e:
|
|
@@ -198,14 +179,8 @@ class WebSocketListenerService:
|
|
|
198
179
|
ping_success = await self.websocket_client.ping()
|
|
199
180
|
if not ping_success:
|
|
200
181
|
logger.warning("WebSocket ping failed")
|
|
201
|
-
connection_errors = self.stats["connection_errors"]
|
|
202
|
-
if isinstance(connection_errors, int):
|
|
203
|
-
self.stats["connection_errors"] = connection_errors + 1
|
|
204
182
|
else:
|
|
205
183
|
logger.warning("WebSocket connection lost")
|
|
206
|
-
connection_errors = self.stats["connection_errors"]
|
|
207
|
-
if isinstance(connection_errors, int):
|
|
208
|
-
self.stats["connection_errors"] = connection_errors + 1
|
|
209
184
|
|
|
210
185
|
# Try to reconnect
|
|
211
186
|
try:
|
|
@@ -241,67 +216,6 @@ class WebSocketListenerService:
|
|
|
241
216
|
logger.error(f"Cleanup task error: {e}")
|
|
242
217
|
await asyncio.sleep(300)
|
|
243
218
|
|
|
244
|
-
def get_status(self) -> dict[str, Any]:
|
|
245
|
-
"""Get service status and statistics.
|
|
246
|
-
|
|
247
|
-
Returns:
|
|
248
|
-
Dictionary with service status and statistics
|
|
249
|
-
"""
|
|
250
|
-
uptime: float | None = None
|
|
251
|
-
start_time = self.stats["start_time"]
|
|
252
|
-
if isinstance(start_time, datetime):
|
|
253
|
-
uptime = (datetime.now() - start_time).total_seconds()
|
|
254
|
-
|
|
255
|
-
return {
|
|
256
|
-
"running": self.running,
|
|
257
|
-
"websocket_connected": (
|
|
258
|
-
self.websocket_client.is_connected if self.websocket_client else False
|
|
259
|
-
),
|
|
260
|
-
"uptime_seconds": uptime,
|
|
261
|
-
"statistics": {
|
|
262
|
-
**self.stats,
|
|
263
|
-
"last_event_time": (
|
|
264
|
-
self.stats["last_event_time"].isoformat()
|
|
265
|
-
if isinstance(self.stats["last_event_time"], datetime)
|
|
266
|
-
else None
|
|
267
|
-
),
|
|
268
|
-
"start_time": (
|
|
269
|
-
self.stats["start_time"].isoformat()
|
|
270
|
-
if isinstance(self.stats["start_time"], datetime)
|
|
271
|
-
else None
|
|
272
|
-
),
|
|
273
|
-
},
|
|
274
|
-
"operation_summary": self.operation_manager.get_operations_summary(),
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
async def force_reconnect(self) -> bool:
|
|
278
|
-
"""Force a WebSocket reconnection.
|
|
279
|
-
|
|
280
|
-
Returns:
|
|
281
|
-
True if reconnection successful
|
|
282
|
-
"""
|
|
283
|
-
try:
|
|
284
|
-
if self.websocket_client:
|
|
285
|
-
self.websocket_client.remove_event_handler(
|
|
286
|
-
"state_changed", self._handle_state_change
|
|
287
|
-
)
|
|
288
|
-
|
|
289
|
-
self.websocket_client = await get_websocket_client()
|
|
290
|
-
await self.websocket_client.subscribe_events("state_changed")
|
|
291
|
-
self.websocket_client.add_event_handler(
|
|
292
|
-
"state_changed", self._handle_state_change
|
|
293
|
-
)
|
|
294
|
-
|
|
295
|
-
logger.info("Forced WebSocket reconnection successful")
|
|
296
|
-
return True
|
|
297
|
-
|
|
298
|
-
except Exception as e:
|
|
299
|
-
logger.error(f"Forced reconnection failed: {e}")
|
|
300
|
-
connection_errors = self.stats["connection_errors"]
|
|
301
|
-
if isinstance(connection_errors, int):
|
|
302
|
-
self.stats["connection_errors"] = connection_errors + 1
|
|
303
|
-
return False
|
|
304
|
-
|
|
305
219
|
|
|
306
220
|
# Global listener service instance
|
|
307
221
|
_listener_service: WebSocketListenerService | None = None
|
|
@@ -353,40 +267,3 @@ async def stop_websocket_listener() -> None:
|
|
|
353
267
|
if _listener_service:
|
|
354
268
|
await _listener_service.stop()
|
|
355
269
|
_listener_service = None
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
async def get_listener_status() -> dict[str, Any]:
|
|
359
|
-
"""Get WebSocket listener service status."""
|
|
360
|
-
service = await get_listener_service()
|
|
361
|
-
return service.get_status()
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
class WebSocketContextManager:
|
|
365
|
-
"""Context manager for WebSocket listener lifecycle."""
|
|
366
|
-
|
|
367
|
-
def __init__(self) -> None:
|
|
368
|
-
self.service: WebSocketListenerService | None = None
|
|
369
|
-
|
|
370
|
-
async def __aenter__(self) -> WebSocketListenerService:
|
|
371
|
-
"""Start WebSocket listener."""
|
|
372
|
-
service = await get_listener_service()
|
|
373
|
-
success = await service.start()
|
|
374
|
-
if not success:
|
|
375
|
-
raise Exception("Failed to start WebSocket listener")
|
|
376
|
-
self.service = service
|
|
377
|
-
return service
|
|
378
|
-
|
|
379
|
-
async def __aexit__(
|
|
380
|
-
self,
|
|
381
|
-
exc_type: type[BaseException] | None,
|
|
382
|
-
exc_val: BaseException | None,
|
|
383
|
-
exc_tb: object,
|
|
384
|
-
) -> None:
|
|
385
|
-
"""Stop WebSocket listener."""
|
|
386
|
-
if self.service:
|
|
387
|
-
await self.service.stop()
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
def websocket_listener_context() -> WebSocketContextManager:
|
|
391
|
-
"""Create a WebSocket listener context manager."""
|
|
392
|
-
return WebSocketContextManager()
|
|
@@ -63,10 +63,9 @@ class Settings(BaseSettings):
|
|
|
63
63
|
|
|
64
64
|
# Tool configuration
|
|
65
65
|
fuzzy_threshold: int = Field(60, alias="FUZZY_THRESHOLD")
|
|
66
|
-
entity_search_limit: int = Field(20, alias="ENTITY_SEARCH_LIMIT")
|
|
67
66
|
|
|
68
67
|
# Smart-search config-fetch time budgets (seconds). Bound how long
|
|
69
|
-
# ha_search
|
|
68
|
+
# ha_search spends fetching automation/script/scene
|
|
70
69
|
# definitions during the per-id fallback before reporting a partial
|
|
71
70
|
# result. Surfaced in the Advanced settings panel (issue #1538) so
|
|
72
71
|
# add-on users — who cannot set raw env vars — can tune them. Consumed
|
|
@@ -466,11 +465,6 @@ class Settings(BaseSettings):
|
|
|
466
465
|
return int(val)
|
|
467
466
|
return val
|
|
468
467
|
|
|
469
|
-
@property
|
|
470
|
-
def env_file_name(self) -> str:
|
|
471
|
-
"""Get the current environment file name."""
|
|
472
|
-
return os.getenv("HAMCP_ENV_FILE", ".env")
|
|
473
|
-
|
|
474
468
|
@field_validator("homeassistant_url")
|
|
475
469
|
@classmethod
|
|
476
470
|
def validate_homeassistant_url(cls, v: str) -> str:
|
|
@@ -580,28 +574,6 @@ def get_settings() -> Settings:
|
|
|
580
574
|
return Settings() # type: ignore[call-arg]
|
|
581
575
|
|
|
582
576
|
|
|
583
|
-
def validate_settings() -> tuple[bool, str | None]:
|
|
584
|
-
"""
|
|
585
|
-
Validate settings and return (is_valid, error_message).
|
|
586
|
-
|
|
587
|
-
Returns:
|
|
588
|
-
tuple: (True, None) if valid, (False, error_message) if invalid
|
|
589
|
-
"""
|
|
590
|
-
try:
|
|
591
|
-
settings = get_settings()
|
|
592
|
-
|
|
593
|
-
# Additional validation
|
|
594
|
-
if not settings.homeassistant_url:
|
|
595
|
-
return False, "Home Assistant URL is required"
|
|
596
|
-
|
|
597
|
-
if not settings.homeassistant_token:
|
|
598
|
-
return False, "Home Assistant token is required"
|
|
599
|
-
|
|
600
|
-
return True, None
|
|
601
|
-
except Exception as e:
|
|
602
|
-
return False, str(e)
|
|
603
|
-
|
|
604
|
-
|
|
605
577
|
# Runtime-editable feature flags surfaced in the /settings web UI
|
|
606
578
|
# (issue #863). Each entry is (field_name, env_var_name, python_type).
|
|
607
579
|
# The web UI's /api/settings/features GET/POST endpoints iterate this
|
|
@@ -811,7 +783,6 @@ ADVANCED_SETTINGS_FIELDS: tuple[AdvancedField, ...] = (
|
|
|
811
783
|
AdvancedField("verify_ssl", "HA_VERIFY_SSL", bool, "operations", True),
|
|
812
784
|
# Search & matching.
|
|
813
785
|
AdvancedField("fuzzy_threshold", "FUZZY_THRESHOLD", int, "search", True),
|
|
814
|
-
AdvancedField("entity_search_limit", "ENTITY_SEARCH_LIMIT", int, "search", True),
|
|
815
786
|
# Smart-search config-fetch time budgets (#1538). Restart-required
|
|
816
787
|
# (consumed as import-time constants in smart_search/_config.py).
|
|
817
788
|
AdvancedField(
|
|
@@ -941,12 +912,11 @@ ADVANCED_SETTINGS_FIELDS: tuple[AdvancedField, ...] = (
|
|
|
941
912
|
# Bounds present on the Settings field today (mirrored):
|
|
942
913
|
# fuzzy_threshold (validator 0-100), code_mode_* (Field ge/le).
|
|
943
914
|
# Bounds added purely as UI/POST guardrails (no Field constraint):
|
|
944
|
-
# timeout, max_retries
|
|
915
|
+
# timeout, max_retries.
|
|
945
916
|
_ADVANCED_SETTINGS_BOUNDS: dict[str, tuple[float, float]] = {
|
|
946
917
|
"timeout": (1, 600),
|
|
947
918
|
"max_retries": (0, 20),
|
|
948
919
|
"fuzzy_threshold": (0, 100),
|
|
949
|
-
"entity_search_limit": (1, 1000),
|
|
950
920
|
"automation_config_time_budget": (1.0, 600.0),
|
|
951
921
|
"script_config_time_budget": (1.0, 600.0),
|
|
952
922
|
"scene_config_time_budget": (1.0, 600.0),
|
|
@@ -50,10 +50,6 @@ _CACHE_TTL_SECONDS = 30.0
|
|
|
50
50
|
_cache: dict[str, tuple[float, list[str]]] = {}
|
|
51
51
|
|
|
52
52
|
|
|
53
|
-
def value_source_for(tool_name: str, arg_path: str) -> str | None:
|
|
54
|
-
return VALUE_SOURCE_REGISTRY.get((tool_name, arg_path))
|
|
55
|
-
|
|
56
|
-
|
|
57
53
|
def all_value_sources_for(tool_name: str) -> dict[str, str]:
|
|
58
54
|
"""Return {arg_path: value_source} for one tool — used by the UI to
|
|
59
55
|
decide which paths render as dropdowns vs free-text."""
|
|
@@ -11,7 +11,7 @@ from __future__ import annotations
|
|
|
11
11
|
|
|
12
12
|
import logging
|
|
13
13
|
from pathlib import Path
|
|
14
|
-
from typing import TYPE_CHECKING, Annotated, Any, ClassVar
|
|
14
|
+
from typing import TYPE_CHECKING, Annotated, Any, ClassVar
|
|
15
15
|
|
|
16
16
|
import yaml # type: ignore[import-untyped]
|
|
17
17
|
from fastmcp import FastMCP
|
|
@@ -20,9 +20,7 @@ from pydantic import Field
|
|
|
20
20
|
|
|
21
21
|
from .config import _PACKAGE_VERSION, get_global_settings
|
|
22
22
|
from .errors import ErrorCode, create_error_response
|
|
23
|
-
from .tools.enhanced import EnhancedToolsMixin
|
|
24
23
|
from .tools.helpers import raise_tool_error
|
|
25
|
-
from .tools.util_helpers import strip_internal_fields
|
|
26
24
|
from .transforms import DEFAULT_PINNED_TOOLS
|
|
27
25
|
|
|
28
26
|
if TYPE_CHECKING:
|
|
@@ -77,7 +75,7 @@ SERVER_ICONS = [
|
|
|
77
75
|
]
|
|
78
76
|
|
|
79
77
|
|
|
80
|
-
class HomeAssistantSmartMCPServer
|
|
78
|
+
class HomeAssistantSmartMCPServer:
|
|
81
79
|
"""Home Assistant MCP Server with smart tools and fuzzy search.
|
|
82
80
|
|
|
83
81
|
Uses lazy initialization to improve startup time:
|
|
@@ -196,9 +194,6 @@ class HomeAssistantSmartMCPServer(EnhancedToolsMixin):
|
|
|
196
194
|
# Register tools
|
|
197
195
|
self.tools_registry.register_all_tools()
|
|
198
196
|
|
|
199
|
-
# Register enhanced tools for first/second interaction success
|
|
200
|
-
self.register_enhanced_tools()
|
|
201
|
-
|
|
202
197
|
# Register bundled skills as MCP resources
|
|
203
198
|
self._register_skills()
|
|
204
199
|
|
|
@@ -1789,51 +1784,6 @@ class HomeAssistantSmartMCPServer(EnhancedToolsMixin):
|
|
|
1789
1784
|
)
|
|
1790
1785
|
return response
|
|
1791
1786
|
|
|
1792
|
-
# Helper methods required by EnhancedToolsMixin
|
|
1793
|
-
|
|
1794
|
-
async def smart_entity_search(
|
|
1795
|
-
self, query: str, domain_filter: str | None = None, limit: int = 10
|
|
1796
|
-
) -> dict[str, Any]:
|
|
1797
|
-
"""Bridge method to existing smart search implementation."""
|
|
1798
|
-
return cast(
|
|
1799
|
-
dict[str, Any],
|
|
1800
|
-
await self.smart_tools.smart_entity_search(
|
|
1801
|
-
query=query, limit=limit, include_attributes=False
|
|
1802
|
-
),
|
|
1803
|
-
)
|
|
1804
|
-
|
|
1805
|
-
async def get_entity_state(self, entity_id: str) -> dict[str, Any]:
|
|
1806
|
-
"""Bridge method to existing entity state implementation."""
|
|
1807
|
-
return await self.client.get_entity_state(entity_id)
|
|
1808
|
-
|
|
1809
|
-
async def call_service(
|
|
1810
|
-
self,
|
|
1811
|
-
domain: str,
|
|
1812
|
-
service: str,
|
|
1813
|
-
entity_id: str | None = None,
|
|
1814
|
-
data: dict | None = None,
|
|
1815
|
-
) -> list[dict[str, Any]] | dict[str, Any]:
|
|
1816
|
-
"""Bridge method to existing service call implementation."""
|
|
1817
|
-
service_data = data or {}
|
|
1818
|
-
if entity_id:
|
|
1819
|
-
service_data["entity_id"] = entity_id
|
|
1820
|
-
return await self.client.call_service(domain, service, service_data)
|
|
1821
|
-
|
|
1822
|
-
async def get_entities_by_area(self, area_name: str) -> dict[str, Any]:
|
|
1823
|
-
"""Bridge method to existing area functionality.
|
|
1824
|
-
|
|
1825
|
-
``smart_tools.get_entities_by_area`` enriches per-entity dicts
|
|
1826
|
-
with leading-underscore internals (``_hidden_by`` etc.) so
|
|
1827
|
-
downstream search branches can apply the score penalty without
|
|
1828
|
-
a second registry lookup. Strip them here so this public bridge
|
|
1829
|
-
doesn't leak internals to MCP clients.
|
|
1830
|
-
"""
|
|
1831
|
-
result = await self.smart_tools.get_entities_by_area(
|
|
1832
|
-
area_query=area_name, group_by_domain=True
|
|
1833
|
-
)
|
|
1834
|
-
strip_internal_fields(result)
|
|
1835
|
-
return cast(dict[str, Any], result)
|
|
1836
|
-
|
|
1837
1787
|
async def start(self) -> None:
|
|
1838
1788
|
"""Start the Smart MCP server with async compatibility."""
|
|
1839
1789
|
logger.info(
|
|
@@ -1853,8 +1803,8 @@ class HomeAssistantSmartMCPServer(EnhancedToolsMixin):
|
|
|
1853
1803
|
except Exception as e:
|
|
1854
1804
|
logger.error(f"❌ Error testing connection: {e}")
|
|
1855
1805
|
|
|
1856
|
-
# Log
|
|
1857
|
-
logger.info("🔧
|
|
1806
|
+
# Log successful server initialization
|
|
1807
|
+
logger.info("🔧 Home Assistant MCP tools loaded")
|
|
1858
1808
|
|
|
1859
1809
|
# Run the MCP server with async compatibility
|
|
1860
1810
|
await self.mcp.run_async()
|
{ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/settings_ui/locales/de.json
RENAMED
|
@@ -371,8 +371,6 @@
|
|
|
371
371
|
"advanced.verify_ssl.help": "Deaktiviere TLS-Überprüfung nur in vertrauenswürdigen Netzwerken. Neustart erforderlich.",
|
|
372
372
|
"advanced.fuzzy_threshold.label": "Fuzzy-Such-Schwellenwert",
|
|
373
373
|
"advanced.fuzzy_threshold.help": "Niedrigere Werte erlauben lockerere Entitätsabgleichung. Bereich 0-100.",
|
|
374
|
-
"advanced.entity_search_limit.label": "Entitätssuchergebnis-Limit",
|
|
375
|
-
"advanced.entity_search_limit.help": "Maximale Entitäten, die von ha_search_entities zurückgegeben werden. Bereich 1-1000.",
|
|
376
374
|
"advanced.automation_config_time_budget.label": "Automatisierungskonfigurations-Zeitbudget (s)",
|
|
377
375
|
"advanced.automation_config_time_budget.help": "Maximale Zeit, die die Tiefensuche für das Abrufen von Automatisierungskonfigurationen aufwendet. Bereich 1-600. Neustart erforderlich.",
|
|
378
376
|
"advanced.script_config_time_budget.label": "Skriptkonfigurations-Zeitbudget (s)",
|
{ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/settings_ui/locales/en.json
RENAMED
|
@@ -371,8 +371,6 @@
|
|
|
371
371
|
"advanced.verify_ssl.help": "Disable TLS verification only on trusted networks. Restart required.",
|
|
372
372
|
"advanced.fuzzy_threshold.label": "Fuzzy-search threshold",
|
|
373
373
|
"advanced.fuzzy_threshold.help": "Lower values allow looser entity matching. Range 0–100.",
|
|
374
|
-
"advanced.entity_search_limit.label": "Entity search result limit",
|
|
375
|
-
"advanced.entity_search_limit.help": "Maximum entities returned by ha_search_entities. Range 1–1000.",
|
|
376
374
|
"advanced.automation_config_time_budget.label": "Automation config time budget (s)",
|
|
377
375
|
"advanced.automation_config_time_budget.help": "Maximum time deep search spends fetching automation configs. Range 1–600. Restart required.",
|
|
378
376
|
"advanced.script_config_time_budget.label": "Script config time budget (s)",
|
{ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/settings_ui/locales/fr.json
RENAMED
|
@@ -371,8 +371,6 @@
|
|
|
371
371
|
"advanced.verify_ssl.help": "Désactive la vérification TLS uniquement sur les réseaux de confiance. Redémarrage requis.",
|
|
372
372
|
"advanced.fuzzy_threshold.label": "Seuil de recherche floue",
|
|
373
373
|
"advanced.fuzzy_threshold.help": "Des valeurs plus basses permettent une correspondance d'entités plus lâche. Plage 0–100.",
|
|
374
|
-
"advanced.entity_search_limit.label": "Limite des résultats de recherche d'entités",
|
|
375
|
-
"advanced.entity_search_limit.help": "Nombre maximum d'entités renvoyées par ha_search_entities. Plage 1–1000.",
|
|
376
374
|
"advanced.automation_config_time_budget.label": "Budget de temps de configuration d'automatisation (s)",
|
|
377
375
|
"advanced.automation_config_time_budget.help": "Temps maximum que la recherche approfondie consacre à la récupération des configurations d'automatisation. Plage 1–600. Redémarrage requis.",
|
|
378
376
|
"advanced.script_config_time_budget.label": "Budget de temps de configuration de script (s)",
|
{ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/settings_ui/locales/ru.json
RENAMED
|
@@ -371,8 +371,6 @@
|
|
|
371
371
|
"advanced.verify_ssl.help": "Отключайте проверку TLS только в доверенной сети. Нужен перезапуск.",
|
|
372
372
|
"advanced.fuzzy_threshold.label": "Порог нечёткого поиска",
|
|
373
373
|
"advanced.fuzzy_threshold.help": "Меньшее значение даёт более свободное сопоставление сущностей. Диапазон 0–100.",
|
|
374
|
-
"advanced.entity_search_limit.label": "Лимит результатов поиска сущностей",
|
|
375
|
-
"advanced.entity_search_limit.help": "Максимум сущностей, возвращаемых ha_search_entities. Диапазон 1–1000.",
|
|
376
374
|
"advanced.automation_config_time_budget.label": "Бюджет времени конфигураций автоматизаций (с)",
|
|
377
375
|
"advanced.automation_config_time_budget.help": "Максимальное время глубокого поиска для загрузки автоматизаций. 1–600. Нужен перезапуск.",
|
|
378
376
|
"advanced.script_config_time_budget.label": "Бюджет времени конфигураций скриптов (с)",
|
{ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/settings_ui/locales/zh-Hans.json
RENAMED
|
@@ -371,8 +371,6 @@
|
|
|
371
371
|
"advanced.verify_ssl.help": "仅在受信任网络上禁用 TLS 验证。需重启。",
|
|
372
372
|
"advanced.fuzzy_threshold.label": "模糊搜索阈值",
|
|
373
373
|
"advanced.fuzzy_threshold.help": "较低的值允许更宽松的实体匹配。范围 0–100。",
|
|
374
|
-
"advanced.entity_search_limit.label": "实体搜索结果上限",
|
|
375
|
-
"advanced.entity_search_limit.help": "ha_search_entities 返回的最大实体数。范围 1–1000。",
|
|
376
374
|
"advanced.automation_config_time_budget.label": "自动化配置时间预算(秒)",
|
|
377
375
|
"advanced.automation_config_time_budget.help": "深度搜索获取自动化配置所花费的最大时间。范围 1–600。需重启。",
|
|
378
376
|
"advanced.script_config_time_budget.label": "脚本配置时间预算(秒)",
|
|
@@ -3475,10 +3475,9 @@ const ADVANCED_FIELD_META = {
|
|
|
3475
3475
|
max_retries: { label: "HA request max retries", help: "Retry budget per failed REST call. Range 0–20. Restart required." },
|
|
3476
3476
|
verify_ssl: { label: "Verify SSL certificates", help: "Skip TLS verification only on trusted networks (self-signed certs, hostname mismatch). Restart required." },
|
|
3477
3477
|
fuzzy_threshold: { label: "Fuzzy-search threshold", help: "Lower = looser entity match. Range 0–100." },
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
scene_config_time_budget: { label: "Scene config time budget (s)", help: "Max seconds ha_search/ha_deep_search spends fetching scene configs before returning a partial result. Range 1–600. Restart required." },
|
|
3478
|
+
automation_config_time_budget: { label: "Automation config time budget (s)", help: "Max seconds deep search spends fetching automation configs before returning a partial result. Raise on instances with many automations. Range 1–600. Restart required." },
|
|
3479
|
+
script_config_time_budget: { label: "Script config time budget (s)", help: "Max seconds deep search spends fetching script configs before returning a partial result. Range 1–600. Restart required." },
|
|
3480
|
+
scene_config_time_budget: { label: "Scene config time budget (s)", help: "Max seconds deep search spends fetching scene configs before returning a partial result. Range 1–600. Restart required." },
|
|
3482
3481
|
individual_config_timeout: { label: "Per-request config fetch timeout (s)", help: "Timeout for each individual automation/script/scene config fetch during deep search. On HA servers that serve config reads serially, raise this and/or lower the batch size so queued requests don't time out. Values above the HA request timeout (HA_TIMEOUT, default 30) have no extra effect — the HTTP client gives up first. Range 1–600. Restart required." },
|
|
3483
3482
|
individual_fetch_batch_size: { label: "Config fetch batch size", help: "How many per-id config fetches deep search issues concurrently. Lower toward 1 on HA servers that serve config reads serially (symptom: 'timed out' partial-result warnings). Range 1–100. Restart required." },
|
|
3484
3483
|
backup_hint: { label: "Backup-hint level", help: "Tunes how strongly the LLM is prompted to take a full-HA snapshot before risky writes." },
|
|
@@ -3512,7 +3511,7 @@ const ADVANCED_RESTART_REQUIRED = new Set([
|
|
|
3512
3511
|
"log_level", "debug",
|
|
3513
3512
|
"mcp_server_name", "mcp_server_version", "environment",
|
|
3514
3513
|
// fuzzy_threshold is read once by SmartSearchTools at the
|
|
3515
|
-
// lazy-init singleton (tools/smart_search
|
|
3514
|
+
// lazy-init singleton (tools/smart_search/) — changes
|
|
3516
3515
|
// need restart to rebuild the searcher.
|
|
3517
3516
|
"fuzzy_threshold",
|
|
3518
3517
|
// The three smart-search time budgets are read once at import by
|
{ha_mcp_dev-7.14.2.dev2162 → ha_mcp_dev-7.14.2.dev2165}/src/ha_mcp/tools/smart_search/_deep.py
RENAMED
|
@@ -138,7 +138,7 @@ class DeepSearchMixin(SceneSearchMixin):
|
|
|
138
138
|
(it never fetches the registry on its own — see
|
|
139
139
|
``_deep_search_helpers``). The orchestrator only hands one down
|
|
140
140
|
when both the entity and config-body branches run, so direct
|
|
141
|
-
``deep_search
|
|
141
|
+
``deep_search`` callers get ``None`` here.
|
|
142
142
|
|
|
143
143
|
Returns:
|
|
144
144
|
Dictionary with search results grouped by type
|
|
@@ -881,7 +881,7 @@ class DeepSearchMixin(SceneSearchMixin):
|
|
|
881
881
|
here — this path adds no request) to map each input_* helper's storage
|
|
882
882
|
``unique_id`` to its CURRENT entity_id + name so a UI rename doesn't
|
|
883
883
|
hide the helper from a search for its new name (#1794). ``None`` on the
|
|
884
|
-
direct ``deep_search
|
|
884
|
+
direct ``deep_search`` path yields an empty map and
|
|
885
885
|
storage-name-only matching.
|
|
886
886
|
"""
|
|
887
887
|
helper_types = [
|