ha-mcp-dev 7.6.0.dev650__tar.gz → 7.6.0.dev651__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.6.0.dev650/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.6.0.dev651}/PKG-INFO +1 -1
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/pyproject.toml +1 -2
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/__main__.py +8 -3
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/client/rest_client.py +1 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/client/websocket_client.py +2 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/client/websocket_listener.py +11 -3
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/dashboard_screenshot/capture.py +2 -2
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/dashboard_screenshot/provision.py +2 -1
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/policy/middleware.py +6 -5
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/server.py +15 -4
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/settings_ui.py +8 -8
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/tools/backup.py +11 -8
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/tools/device_control.py +143 -108
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/tools/helpers.py +6 -4
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/tools/smart_search/_deep.py +1 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/tools/smart_search/_entities.py +8 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/tools/smart_search/_overview.py +3 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/tools/tools_addons.py +11 -7
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/tools/tools_areas.py +5 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/tools/tools_blueprints.py +91 -56
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/tools/tools_bug_report.py +4 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/tools/tools_calendar.py +3 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/tools/tools_categories.py +6 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/tools/tools_code.py +10 -10
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/tools/tools_config_automations.py +4 -1
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/tools/tools_config_dashboards.py +11 -2
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/tools/tools_config_helpers.py +4 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/tools/tools_config_scenes.py +10 -4
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/tools/tools_config_scripts.py +2 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/tools/tools_energy.py +17 -20
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/tools/tools_entities.py +3 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/tools/tools_filesystem.py +9 -1
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/tools/tools_groups.py +3 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/tools/tools_hacs.py +3 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/tools/tools_history.py +3 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/tools/tools_integrations.py +12 -4
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/tools/tools_labels.py +6 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/tools/tools_mcp_component.py +3 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/tools/tools_registry.py +5 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/tools/tools_resources.py +16 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/tools/tools_search.py +4 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/tools/tools_service.py +3 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/tools/tools_services.py +3 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/tools/tools_system.py +5 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/tools/tools_todo.py +5 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/tools/tools_traces.py +65 -34
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/tools/tools_updates.py +3 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/tools/tools_utility.py +15 -5
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/tools/tools_voice_assistant.py +144 -96
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/tools/tools_yaml_config.py +2 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/tools/tools_zones.py +5 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/tools/util_helpers.py +0 -15
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/transforms/categorized_search.py +4 -2
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/utils/kill_signal_diagnostics.py +40 -6
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/LICENSE +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/MANIFEST.in +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/README.md +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/setup.cfg +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/__init__.py +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/_pypi_marker +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/_version.py +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/auth/__init__.py +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/auth/consent_form.py +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/auth/provider.py +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/backup_manager.py +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/client/__init__.py +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/client/supervisor_client.py +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/config.py +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/dashboard_screenshot/__init__.py +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/errors.py +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/policy/__init__.py +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/policy/approval_queue.py +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/policy/evaluator.py +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/policy/handlers.py +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/policy/model.py +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/policy/persistence.py +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/policy/value_sources.py +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/py.typed +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/yaml-only-integrations.md +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/settings.css +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/settings.js +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/smoke_test.py +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/tools/__init__.py +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/tools/auto_backup.py +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/tools/best_practice_checker.py +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/tools/enhanced.py +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/tools/reference_validator.py +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/tools/registry.py +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/tools/smart_search/__init__.py +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/tools/smart_search/_base.py +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/tools/smart_search/_config.py +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/tools/smart_search/_fetch.py +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/tools/smart_search/_scenes.py +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/tools/smart_search/_scoring.py +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/tools/tools_camera.py +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/tools/tools_config_entry_flow.py +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/tools/tools_dashboard_screenshot.py +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/tools/validation_middleware.py +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/transforms/__init__.py +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/utils/__init__.py +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/utils/config_hash.py +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/utils/data_paths.py +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/utils/domain_handlers.py +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/utils/fuzzy_search.py +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/utils/operation_manager.py +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/utils/python_sandbox.py +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/utils/skill_loader.py +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/utils/usage_logger.py +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/tests/__init__.py +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/tests/test_constants.py +0 -0
- {ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/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.6.0.
|
|
7
|
+
version = "7.6.0.dev651"
|
|
8
8
|
description = "Home Assistant MCP Server - Complete control of Home Assistant through MCP"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.13,<3.14"
|
|
@@ -132,7 +132,6 @@ ignore = [
|
|
|
132
132
|
"RUF001", # ambiguous unicode — HA entity names use degree symbols etc
|
|
133
133
|
"RUF003", # ambiguous unicode in comments
|
|
134
134
|
"RUF010", # explicit f-string type conversion — str(x) is clearer than !s
|
|
135
|
-
"F841", # unused variable — too many to fix now
|
|
136
135
|
"RUF059", # unused unpacked variable
|
|
137
136
|
"SIM118", # in-dict-keys — style preference
|
|
138
137
|
"PIE810", # multiple-starts-ends-with — style preference
|
|
@@ -3,14 +3,15 @@
|
|
|
3
3
|
import sys
|
|
4
4
|
|
|
5
5
|
if sys.version_info < (3, 13): # noqa: UP036 — uvx can bypass requires-python and run on 3.12
|
|
6
|
-
print
|
|
6
|
+
# Write directly to stderr (not print) so this import-time version gate
|
|
7
|
+
# fires before any 3.13-only syntax in the rest of the module is parsed.
|
|
8
|
+
sys.stderr.write(
|
|
7
9
|
f"ERROR: ha-mcp requires Python 3.13+, but you are running Python "
|
|
8
10
|
f"{sys.version_info.major}.{sys.version_info.minor}.\n"
|
|
9
11
|
"If using uvx, add '--python 3.13' to your config args:\n"
|
|
10
12
|
' "args": ["--python", "3.13", "--refresh", "ha-mcp@latest"]\n'
|
|
11
13
|
"Or install Python 3.13: brew install python@3.13 (macOS) / "
|
|
12
|
-
"sudo apt install python3.13 (Linux)"
|
|
13
|
-
file=sys.stderr,
|
|
14
|
+
"sudo apt install python3.13 (Linux)\n"
|
|
14
15
|
)
|
|
15
16
|
sys.exit(1)
|
|
16
17
|
|
|
@@ -496,6 +497,8 @@ async def _cancel_tasks(*tasks: asyncio.Task) -> None:
|
|
|
496
497
|
try:
|
|
497
498
|
await task
|
|
498
499
|
except asyncio.CancelledError:
|
|
500
|
+
# Expected: we just cancelled this task, swallow its
|
|
501
|
+
# CancelledError so remaining tasks still get awaited.
|
|
499
502
|
pass
|
|
500
503
|
|
|
501
504
|
|
|
@@ -525,6 +528,8 @@ async def _run_with_shutdown(server_coro: Coroutine[Any, Any, Any]) -> None:
|
|
|
525
528
|
except TimeoutError:
|
|
526
529
|
logger.warning("Server did not stop within timeout")
|
|
527
530
|
except asyncio.CancelledError:
|
|
531
|
+
# Expected: we just cancelled server_task above; swallow its
|
|
532
|
+
# CancelledError so shutdown can proceed to cleanup.
|
|
528
533
|
pass
|
|
529
534
|
|
|
530
535
|
except asyncio.CancelledError:
|
|
@@ -711,6 +711,7 @@ class HomeAssistantClient:
|
|
|
711
711
|
if isinstance(msg, str) and msg:
|
|
712
712
|
message = msg
|
|
713
713
|
except json.JSONDecodeError:
|
|
714
|
+
# Body wasn't a JSON envelope; fall back to raw text below.
|
|
714
715
|
pass
|
|
715
716
|
if not message:
|
|
716
717
|
message = text_body.strip() or response.reason_phrase or "<empty body>"
|
|
@@ -352,6 +352,8 @@ class HomeAssistantWebSocketClient:
|
|
|
352
352
|
try:
|
|
353
353
|
await self.background_task
|
|
354
354
|
except asyncio.CancelledError:
|
|
355
|
+
# Expected: we just cancelled the task above; swallow the
|
|
356
|
+
# propagated CancelledError so disconnect can finish cleanly.
|
|
355
357
|
pass
|
|
356
358
|
finally:
|
|
357
359
|
self.background_task = None
|
|
@@ -87,6 +87,7 @@ class WebSocketListenerService:
|
|
|
87
87
|
try:
|
|
88
88
|
await self.listener_task
|
|
89
89
|
except asyncio.CancelledError:
|
|
90
|
+
# Expected: awaiting a cancelled task re-raises CancelledError.
|
|
90
91
|
pass
|
|
91
92
|
|
|
92
93
|
if self.cleanup_task and not self.cleanup_task.done():
|
|
@@ -94,6 +95,7 @@ class WebSocketListenerService:
|
|
|
94
95
|
try:
|
|
95
96
|
await self.cleanup_task
|
|
96
97
|
except asyncio.CancelledError:
|
|
98
|
+
# Expected: awaiting a cancelled task re-raises CancelledError.
|
|
97
99
|
pass
|
|
98
100
|
|
|
99
101
|
# Remove event handler if WebSocket client exists
|
|
@@ -164,7 +166,9 @@ class WebSocketListenerService:
|
|
|
164
166
|
if updated_ops:
|
|
165
167
|
operations_updated = self.stats["operations_updated"]
|
|
166
168
|
if isinstance(operations_updated, int):
|
|
167
|
-
self.stats["operations_updated"] = operations_updated + len(
|
|
169
|
+
self.stats["operations_updated"] = operations_updated + len(
|
|
170
|
+
updated_ops
|
|
171
|
+
)
|
|
168
172
|
logger.info(f"Updated {len(updated_ops)} operations for {entity_id}")
|
|
169
173
|
|
|
170
174
|
except (RuntimeError, ConnectionError, OSError) as e:
|
|
@@ -307,7 +311,6 @@ _listener_lock: asyncio.Lock | None = None
|
|
|
307
311
|
async def get_listener_service() -> WebSocketListenerService:
|
|
308
312
|
"""Get the global WebSocket listener service instance."""
|
|
309
313
|
global _listener_service, _listener_lock
|
|
310
|
-
import asyncio
|
|
311
314
|
|
|
312
315
|
current_loop = asyncio.get_event_loop()
|
|
313
316
|
|
|
@@ -373,7 +376,12 @@ class WebSocketContextManager:
|
|
|
373
376
|
self.service = service
|
|
374
377
|
return service
|
|
375
378
|
|
|
376
|
-
async def __aexit__(
|
|
379
|
+
async def __aexit__(
|
|
380
|
+
self,
|
|
381
|
+
exc_type: type[BaseException] | None,
|
|
382
|
+
exc_val: BaseException | None,
|
|
383
|
+
exc_tb: object,
|
|
384
|
+
) -> None:
|
|
377
385
|
"""Stop WebSocket listener."""
|
|
378
386
|
if self.service:
|
|
379
387
|
await self.service.stop()
|
{ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/dashboard_screenshot/capture.py
RENAMED
|
@@ -142,14 +142,14 @@ async def capture_dashboard_png(
|
|
|
142
142
|
context={"engine_url": engine},
|
|
143
143
|
suggestions=[
|
|
144
144
|
"Ensure the Puppet screenshot add-on (or sidecar) is "
|
|
145
|
-
"installed and running",
|
|
145
|
+
+ "installed and running",
|
|
146
146
|
# Puppet restarts itself when navigation fails, so a
|
|
147
147
|
# missing/invalid token shows up as a dropped connection
|
|
148
148
|
# rather than an HTTP error.
|
|
149
149
|
f"If it is running, its access token is likely missing or "
|
|
150
150
|
f"invalid — {TOKEN_HINT}",
|
|
151
151
|
"Check HAMCP_DASHBOARD_SCREENSHOT_ENGINE_URL on "
|
|
152
|
-
"Docker/Container deployments",
|
|
152
|
+
+ "Docker/Container deployments",
|
|
153
153
|
],
|
|
154
154
|
)
|
|
155
155
|
)
|
{ha_mcp_dev-7.6.0.dev650 → ha_mcp_dev-7.6.0.dev651}/src/ha_mcp/dashboard_screenshot/provision.py
RENAMED
|
@@ -96,10 +96,11 @@ async def resolve_engine_url() -> str:
|
|
|
96
96
|
suggestions=[
|
|
97
97
|
"Use HA OS / Supervised and install the screenshot engine add-on",
|
|
98
98
|
"Or run the engine as a sidecar and set "
|
|
99
|
-
"HAMCP_DASHBOARD_SCREENSHOT_ENGINE_URL",
|
|
99
|
+
+ "HAMCP_DASHBOARD_SCREENSHOT_ENGINE_URL",
|
|
100
100
|
],
|
|
101
101
|
)
|
|
102
102
|
)
|
|
103
|
+
raise AssertionError("unreachable: raise_tool_error always raises")
|
|
103
104
|
|
|
104
105
|
|
|
105
106
|
async def _discover_engine_url_via_supervisor() -> str:
|
|
@@ -5,7 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
import logging
|
|
6
6
|
from collections.abc import Callable
|
|
7
7
|
from datetime import UTC, datetime
|
|
8
|
-
from typing import Any
|
|
8
|
+
from typing import Any, NoReturn
|
|
9
9
|
|
|
10
10
|
import anyio
|
|
11
11
|
from anyio.to_thread import run_sync as run_in_thread
|
|
@@ -149,6 +149,7 @@ class PolicyMiddleware(Middleware):
|
|
|
149
149
|
name,
|
|
150
150
|
)
|
|
151
151
|
self._raise_pending_error(pending, rule)
|
|
152
|
+
return None # py/mixed-returns: explicit terminal; error handlers above always raise (NoReturn), unreachable
|
|
152
153
|
|
|
153
154
|
async def _wait_for_decision(
|
|
154
155
|
self,
|
|
@@ -176,7 +177,7 @@ class PolicyMiddleware(Middleware):
|
|
|
176
177
|
await pending.wait()
|
|
177
178
|
|
|
178
179
|
@staticmethod
|
|
179
|
-
def _raise_denied_error() ->
|
|
180
|
+
def _raise_denied_error() -> NoReturn:
|
|
180
181
|
raise_tool_error(
|
|
181
182
|
create_error_response(
|
|
182
183
|
ErrorCode.USER_DENIED,
|
|
@@ -189,7 +190,7 @@ class PolicyMiddleware(Middleware):
|
|
|
189
190
|
|
|
190
191
|
def _raise_pending_error(
|
|
191
192
|
self, pending: PendingApproval, rule: Rule | None = None
|
|
192
|
-
) ->
|
|
193
|
+
) -> NoReturn:
|
|
193
194
|
# Time-remaining, not total TTL: an LLM that re-calls a minute
|
|
194
195
|
# before expiry should see "~60s left", not the original 300s.
|
|
195
196
|
remaining = max(
|
|
@@ -216,9 +217,9 @@ class PolicyMiddleware(Middleware):
|
|
|
216
217
|
"with the same arguments after the user approves.",
|
|
217
218
|
suggestions=[
|
|
218
219
|
"Tell the user to open the Tool Security Policies tab in "
|
|
219
|
-
"the ha-mcp settings UI and approve the pending request.",
|
|
220
|
+
+ "the ha-mcp settings UI and approve the pending request.",
|
|
220
221
|
"Re-call this tool with the same arguments after the user "
|
|
221
|
-
"approves.",
|
|
222
|
+
+ "approves.",
|
|
222
223
|
],
|
|
223
224
|
context=context,
|
|
224
225
|
)
|
|
@@ -47,6 +47,20 @@ _OLD_SKILL_TOOL_ALIASES = (
|
|
|
47
47
|
"If you were going to call any of those, call this instead."
|
|
48
48
|
)
|
|
49
49
|
|
|
50
|
+
# Hint shipped at the top of ha_get_skill_guide responses that deliver
|
|
51
|
+
# best-practice skill content directly. Smart clients that fetch the reference
|
|
52
|
+
# files proactively via this tool would otherwise still receive duplicate
|
|
53
|
+
# canonical content from the per-call write-tool attach — this hint tells them
|
|
54
|
+
# to opt out so the same body doesn't ride along again on every subsequent
|
|
55
|
+
# write. Defined here (its only consumer) rather than in util_helpers.
|
|
56
|
+
_SKILL_GUIDE_MANDATORYBPS_HINT = (
|
|
57
|
+
"You now have this best-practice reference in your context. "
|
|
58
|
+
"Pass `MandatoryBPS=false` on subsequent write-tool calls in this "
|
|
59
|
+
"session (ha_config_set_automation / _script / _scene / _helper / "
|
|
60
|
+
"_dashboard / _yaml) to avoid re-receiving the canonical reference "
|
|
61
|
+
"files inline."
|
|
62
|
+
)
|
|
63
|
+
|
|
50
64
|
|
|
51
65
|
# Server icon configuration using GitHub-hosted images
|
|
52
66
|
# These icons are bundled in packaging/mcpb/ and also available via GitHub raw URLs
|
|
@@ -1447,10 +1461,7 @@ class HomeAssistantSmartMCPServer(EnhancedToolsMixin):
|
|
|
1447
1461
|
# parsing the (potentially large) content body. Scoped to the
|
|
1448
1462
|
# best-practice skill because that's the one the write-tool
|
|
1449
1463
|
# MandatoryBPS param gates; other skills (if any) are unrelated.
|
|
1450
|
-
from .tools.util_helpers import
|
|
1451
|
-
_HA_BEST_PRACTICES_SKILL_NAME,
|
|
1452
|
-
_SKILL_GUIDE_MANDATORYBPS_HINT,
|
|
1453
|
-
)
|
|
1464
|
+
from .tools.util_helpers import _HA_BEST_PRACTICES_SKILL_NAME
|
|
1454
1465
|
|
|
1455
1466
|
response: dict[str, Any] = {}
|
|
1456
1467
|
if skill == _HA_BEST_PRACTICES_SKILL_NAME:
|
|
@@ -1703,9 +1703,9 @@ def build_settings_handlers(
|
|
|
1703
1703
|
),
|
|
1704
1704
|
suggestions=[
|
|
1705
1705
|
"Include enable_beta_features=true in the same save "
|
|
1706
|
-
"payload as the sub-flag(s).",
|
|
1706
|
+
+ "payload as the sub-flag(s).",
|
|
1707
1707
|
"Or turn on the master 'Enable beta features' toggle "
|
|
1708
|
-
"first, then enable the sub-flag(s).",
|
|
1708
|
+
+ "first, then enable the sub-flag(s).",
|
|
1709
1709
|
],
|
|
1710
1710
|
context={"rejected": beta_sub_writes},
|
|
1711
1711
|
),
|
|
@@ -1874,10 +1874,10 @@ def build_settings_handlers(
|
|
|
1874
1874
|
"Supervisor helper returned ok=False with no error",
|
|
1875
1875
|
suggestions=[
|
|
1876
1876
|
"Check the Home Assistant Supervisor logs and "
|
|
1877
|
-
"the add-on logs for the underlying failure.",
|
|
1877
|
+
+ "the add-on logs for the underlying failure.",
|
|
1878
1878
|
"Report this at "
|
|
1879
|
-
"https://github.com/homeassistant-ai/ha-mcp/issues "
|
|
1880
|
-
"if it persists — this indicates an internal bug.",
|
|
1879
|
+
+ "https://github.com/homeassistant-ai/ha-mcp/issues "
|
|
1880
|
+
+ "if it persists — this indicates an internal bug.",
|
|
1881
1881
|
],
|
|
1882
1882
|
),
|
|
1883
1883
|
status_code=500,
|
|
@@ -2575,10 +2575,10 @@ def build_settings_handlers(
|
|
|
2575
2575
|
"Supervisor helper returned ok=False with no error",
|
|
2576
2576
|
suggestions=[
|
|
2577
2577
|
"Check the Home Assistant Supervisor logs and "
|
|
2578
|
-
"the add-on logs for the underlying failure.",
|
|
2578
|
+
+ "the add-on logs for the underlying failure.",
|
|
2579
2579
|
"Report this at "
|
|
2580
|
-
"https://github.com/homeassistant-ai/ha-mcp/issues "
|
|
2581
|
-
"if it persists — this indicates an internal bug.",
|
|
2580
|
+
+ "https://github.com/homeassistant-ai/ha-mcp/issues "
|
|
2581
|
+
+ "if it persists — this indicates an internal bug.",
|
|
2582
2582
|
],
|
|
2583
2583
|
),
|
|
2584
2584
|
status_code=500,
|
|
@@ -517,6 +517,7 @@ async def create_backup(
|
|
|
517
517
|
context={"tool": "create_backup"},
|
|
518
518
|
suggestions=["Check Home Assistant connection and backup configuration"],
|
|
519
519
|
)
|
|
520
|
+
return None # unreachable: exception_to_structured_error always raises
|
|
520
521
|
finally:
|
|
521
522
|
# Always disconnect WebSocket — narrow to transport errors; a
|
|
522
523
|
# programming error during cleanup should still surface.
|
|
@@ -714,6 +715,7 @@ async def restore_backup(
|
|
|
714
715
|
context={"tool": "restore_backup", "backup_id": backup_id},
|
|
715
716
|
suggestions=["Check Home Assistant connection and backup availability"],
|
|
716
717
|
)
|
|
718
|
+
return None # unreachable: exception_to_structured_error always raises
|
|
717
719
|
finally:
|
|
718
720
|
# Always disconnect WebSocket — narrow to transport errors; a
|
|
719
721
|
# programming error during cleanup should still surface.
|
|
@@ -726,6 +728,7 @@ async def restore_backup(
|
|
|
726
728
|
type(err).__name__,
|
|
727
729
|
err,
|
|
728
730
|
)
|
|
731
|
+
return None # py/mixed-returns: explicit terminal; error handlers above always raise (NoReturn), unreachable
|
|
729
732
|
|
|
730
733
|
|
|
731
734
|
# Valid (scope, action) combinations. Anything outside this set is
|
|
@@ -953,13 +956,13 @@ def register_backup_tools(
|
|
|
953
956
|
create_error_response(
|
|
954
957
|
ErrorCode.RESOURCE_NOT_FOUND,
|
|
955
958
|
f"Could not snapshot {dom}:{eid} — entity not found "
|
|
956
|
-
"or fetch returned no config",
|
|
959
|
+
+ "or fetch returned no config",
|
|
957
960
|
context={"domain": dom, "entity_id": eid},
|
|
958
961
|
suggestions=[
|
|
959
962
|
"Verify the entity exists via the matching "
|
|
960
|
-
"ha_config_get_* tool first",
|
|
963
|
+
+ "ha_config_get_* tool first",
|
|
961
964
|
"For helpers, pass domain='helper_<helper_type>' "
|
|
962
|
-
"(e.g. 'helper_input_boolean')",
|
|
965
|
+
+ "(e.g. 'helper_input_boolean')",
|
|
963
966
|
],
|
|
964
967
|
)
|
|
965
968
|
)
|
|
@@ -1053,13 +1056,13 @@ def register_backup_tools(
|
|
|
1053
1056
|
context={"backup_name": bname, "action": "restore"},
|
|
1054
1057
|
suggestions=[
|
|
1055
1058
|
"Verify the entity referenced by the backup still "
|
|
1056
|
-
"exists; restore re-POSTs to its current registry "
|
|
1057
|
-
"key",
|
|
1059
|
+
+ "exists; restore re-POSTs to its current registry "
|
|
1060
|
+
+ "key",
|
|
1058
1061
|
"Compare the captured schema vs current HA — HA "
|
|
1059
|
-
"minor versions occasionally drop/rename fields",
|
|
1062
|
+
+ "minor versions occasionally drop/rename fields",
|
|
1060
1063
|
"Inspect the snapshot YAML via "
|
|
1061
|
-
"ha_manage_backup(scope='edits', action='view', "
|
|
1062
|
-
"backup_name=...)",
|
|
1064
|
+
+ "ha_manage_backup(scope='edits', action='view', "
|
|
1065
|
+
+ "backup_name=...)",
|
|
1063
1066
|
],
|
|
1064
1067
|
)
|
|
1065
1068
|
return {
|