ha-mcp-dev 7.7.0.dev686__tar.gz → 7.7.0.dev687__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.7.0.dev686/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.7.0.dev687}/PKG-INFO +4 -4
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/README.md +3 -3
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/pyproject.toml +1 -1
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/tools_filesystem.py +8 -5
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/tools_system.py +48 -2
- ha_mcp_dev-7.7.0.dev687/src/ha_mcp/tools/tools_themes.py +191 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/tools_yaml_config.py +33 -7
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/util_helpers.py +17 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687/src/ha_mcp_dev.egg-info}/PKG-INFO +4 -4
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp_dev.egg-info/SOURCES.txt +1 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/LICENSE +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/MANIFEST.in +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/setup.cfg +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/__init__.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/__main__.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/_pypi_marker +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/_version.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/auth/__init__.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/auth/consent_form.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/auth/provider.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/backup_manager.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/client/__init__.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/client/rest_client.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/client/supervisor_client.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/client/websocket_client.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/client/websocket_listener.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/config.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/dashboard_screenshot/__init__.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/dashboard_screenshot/capture.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/dashboard_screenshot/provision.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/errors.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/policy/__init__.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/policy/approval_queue.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/policy/evaluator.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/policy/handlers.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/policy/middleware.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/policy/model.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/policy/persistence.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/policy/value_sources.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/py.typed +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/read_only.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/yaml-only-integrations.md +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/server.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/settings.css +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/settings.js +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/settings_ui.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/smoke_test.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/__init__.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/auto_backup.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/backup.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/best_practice_checker.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/config_entry_flow.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/device_control.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/enhanced.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/helpers.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/reference_validator.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/registry.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/smart_search/__init__.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/smart_search/_base.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/smart_search/_config.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/smart_search/_deep.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/smart_search/_entities.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/smart_search/_fetch.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/smart_search/_overview.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/smart_search/_scenes.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/smart_search/_scoring.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/tools_addons.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/tools_areas.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/tools_blueprints.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/tools_bug_report.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/tools_calendar.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/tools_camera.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/tools_categories.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/tools_code.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/tools_config_automations.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/tools_dashboard_screenshot.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/tools_energy.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/tools_entities.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/tools_groups.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/tools_hacs.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/tools_history.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/tools_integrations.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/tools_labels.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/tools_mcp_component.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/tools_registry.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/tools_resources.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/tools_search.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/tools_service.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/tools_services.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/tools_todo.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/tools_traces.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/tools_updates.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/tools_utility.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/tools_zones.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/validation_middleware.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/transforms/__init__.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/transforms/categorized_search.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/utils/__init__.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/utils/config_hash.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/utils/data_paths.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/utils/domain_handlers.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/utils/fuzzy_search.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/utils/operation_manager.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/utils/python_sandbox.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/utils/skill_loader.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/utils/usage_logger.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/tests/__init__.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/tests/test_constants.py +0 -0
- {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/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.7.0.
|
|
3
|
+
Version: 7.7.0.dev687
|
|
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
|
|
@@ -38,7 +38,7 @@ Dynamic: license-file
|
|
|
38
38
|
<!-- mcp-name: io.github.homeassistant-ai/ha-mcp -->
|
|
39
39
|
|
|
40
40
|
<p align="center">
|
|
41
|
-
<img src="https://img.shields.io/badge/tools-
|
|
41
|
+
<img src="https://img.shields.io/badge/tools-84-blue" alt="95+ Tools">
|
|
42
42
|
<a href="https://github.com/homeassistant-ai/ha-mcp/releases"><img src="https://img.shields.io/github/v/release/homeassistant-ai/ha-mcp" alt="Release"></a>
|
|
43
43
|
<a href="https://github.com/homeassistant-ai/ha-mcp/actions/workflows/e2e-tests.yml"><img src="https://img.shields.io/github/actions/workflow/status/homeassistant-ai/ha-mcp/e2e-tests.yml?branch=master&label=E2E%20Tests" alt="E2E Tests"></a>
|
|
44
44
|
<a href="LICENSE.md"><img src="https://img.shields.io/github/license/homeassistant-ai/ha-mcp.svg" alt="License"></a>
|
|
@@ -209,7 +209,7 @@ Spend less time configuring, more time enjoying your smart home.
|
|
|
209
209
|
<details>
|
|
210
210
|
<!-- TOOLS_TABLE_START -->
|
|
211
211
|
|
|
212
|
-
<summary><b>Complete Tool List (
|
|
212
|
+
<summary><b>Complete Tool List (84 tools)</b></summary>
|
|
213
213
|
|
|
214
214
|
| Category | Tools |
|
|
215
215
|
|----------|-------|
|
|
@@ -236,7 +236,7 @@ Spend less time configuring, more time enjoying your smart home.
|
|
|
236
236
|
| **Scripts** | `ha_config_get_script`, `ha_config_remove_script`, `ha_config_set_script` |
|
|
237
237
|
| **Search & Discovery** | `ha_get_overview`, `ha_get_state`, `ha_search` |
|
|
238
238
|
| **Service & Device Control** | `ha_bulk_control`, `ha_call_event`, `ha_call_service`, `ha_get_operation_status`, `ha_list_services` |
|
|
239
|
-
| **System** | `ha_config_set_yaml` *(beta)*, `ha_get_updates`, `ha_manage_backup`, `ha_manage_custom_tool` *(beta)*, `ha_reload_core`, `ha_restart` |
|
|
239
|
+
| **System** | `ha_config_set_yaml` *(beta)*, `ha_get_updates`, `ha_manage_backup`, `ha_manage_custom_tool` *(beta)*, `ha_manage_theme`, `ha_reload_core`, `ha_restart` |
|
|
240
240
|
| **Todo Lists** | `ha_get_todo`, `ha_remove_todo_item`, `ha_set_todo_item` |
|
|
241
241
|
| **Utilities** | `ha_eval_template`, `ha_install_mcp_tools` *(beta)*, `ha_report_issue` |
|
|
242
242
|
| **Zones** | `ha_get_zone`, `ha_remove_zone`, `ha_set_zone` |
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<!-- mcp-name: io.github.homeassistant-ai/ha-mcp -->
|
|
9
9
|
|
|
10
10
|
<p align="center">
|
|
11
|
-
<img src="https://img.shields.io/badge/tools-
|
|
11
|
+
<img src="https://img.shields.io/badge/tools-84-blue" alt="95+ Tools">
|
|
12
12
|
<a href="https://github.com/homeassistant-ai/ha-mcp/releases"><img src="https://img.shields.io/github/v/release/homeassistant-ai/ha-mcp" alt="Release"></a>
|
|
13
13
|
<a href="https://github.com/homeassistant-ai/ha-mcp/actions/workflows/e2e-tests.yml"><img src="https://img.shields.io/github/actions/workflow/status/homeassistant-ai/ha-mcp/e2e-tests.yml?branch=master&label=E2E%20Tests" alt="E2E Tests"></a>
|
|
14
14
|
<a href="LICENSE.md"><img src="https://img.shields.io/github/license/homeassistant-ai/ha-mcp.svg" alt="License"></a>
|
|
@@ -179,7 +179,7 @@ Spend less time configuring, more time enjoying your smart home.
|
|
|
179
179
|
<details>
|
|
180
180
|
<!-- TOOLS_TABLE_START -->
|
|
181
181
|
|
|
182
|
-
<summary><b>Complete Tool List (
|
|
182
|
+
<summary><b>Complete Tool List (84 tools)</b></summary>
|
|
183
183
|
|
|
184
184
|
| Category | Tools |
|
|
185
185
|
|----------|-------|
|
|
@@ -206,7 +206,7 @@ Spend less time configuring, more time enjoying your smart home.
|
|
|
206
206
|
| **Scripts** | `ha_config_get_script`, `ha_config_remove_script`, `ha_config_set_script` |
|
|
207
207
|
| **Search & Discovery** | `ha_get_overview`, `ha_get_state`, `ha_search` |
|
|
208
208
|
| **Service & Device Control** | `ha_bulk_control`, `ha_call_event`, `ha_call_service`, `ha_get_operation_status`, `ha_list_services` |
|
|
209
|
-
| **System** | `ha_config_set_yaml` *(beta)*, `ha_get_updates`, `ha_manage_backup`, `ha_manage_custom_tool` *(beta)*, `ha_reload_core`, `ha_restart` |
|
|
209
|
+
| **System** | `ha_config_set_yaml` *(beta)*, `ha_get_updates`, `ha_manage_backup`, `ha_manage_custom_tool` *(beta)*, `ha_manage_theme`, `ha_reload_core`, `ha_restart` |
|
|
210
210
|
| **Todo Lists** | `ha_get_todo`, `ha_remove_todo_item`, `ha_set_todo_item` |
|
|
211
211
|
| **Utilities** | `ha_eval_template`, `ha_install_mcp_tools` *(beta)*, `ha_report_issue` |
|
|
212
212
|
| **Zones** | `ha_get_zone`, `ha_remove_zone`, `ha_set_zone` |
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ha-mcp-dev"
|
|
7
|
-
version = "7.7.0.
|
|
7
|
+
version = "7.7.0.dev687"
|
|
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"
|
|
@@ -53,7 +53,11 @@ CALLER_TOKEN_BOOTSTRAP_SERVICE = "get_caller_token"
|
|
|
53
53
|
# server-side behavior change requires it. Older components (no
|
|
54
54
|
# ``version`` in the get_caller_token response, or a version below this)
|
|
55
55
|
# get an actionable "update via HACS" error.
|
|
56
|
-
|
|
56
|
+
# 0.8.0: ``ha_config_set_yaml`` now depends on the component's
|
|
57
|
+
# ``themes/*.yaml`` yaml_path scope; a <0.8.0 component reaches the old
|
|
58
|
+
# handler and rejects ``themes/<name>.yaml`` with a misleading "not
|
|
59
|
+
# allowed" message instead of this actionable update prompt.
|
|
60
|
+
MIN_COMPONENT_VERSION = "0.8.0"
|
|
57
61
|
|
|
58
62
|
|
|
59
63
|
def _version_tuple(version: str) -> tuple[int, ...]:
|
|
@@ -579,7 +583,6 @@ class FilesystemTools:
|
|
|
579
583
|
|
|
580
584
|
Creates or updates files in restricted directories only. This is useful for:
|
|
581
585
|
- Creating custom CSS/JS for dashboards
|
|
582
|
-
- Adding theme files
|
|
583
586
|
- Creating Jinja2 templates
|
|
584
587
|
|
|
585
588
|
**Allowed Write Directories:**
|
|
@@ -609,10 +612,10 @@ class FilesystemTools:
|
|
|
609
612
|
overwrite=True
|
|
610
613
|
)
|
|
611
614
|
|
|
612
|
-
# Create a
|
|
615
|
+
# Create a custom Jinja template file
|
|
613
616
|
result = ha_write_file(
|
|
614
|
-
path="
|
|
615
|
-
content="
|
|
617
|
+
path="custom_templates/formatters.jinja",
|
|
618
|
+
content="{% macro shout(text) %}{{ text | upper }}{% endmacro %}",
|
|
616
619
|
overwrite=False
|
|
617
620
|
)
|
|
618
621
|
```
|
|
@@ -28,6 +28,7 @@ from .util_helpers import (
|
|
|
28
28
|
fetch_integration_diagnostics,
|
|
29
29
|
filter_active_repairs,
|
|
30
30
|
parse_diagnostics_fields,
|
|
31
|
+
summarize_theme_listing,
|
|
31
32
|
)
|
|
32
33
|
|
|
33
34
|
logger = logging.getLogger(__name__)
|
|
@@ -340,6 +341,7 @@ class SystemTools:
|
|
|
340
341
|
- "zha_network": ZHA Zigbee devices with radio signal summary (name, LQI, RSSI)
|
|
341
342
|
- "zha_network_full": ZHA Zigbee devices with all device details (can be large on 100+ device networks; prefer "zha_network" for summary)
|
|
342
343
|
- "zwave_network": Z-Wave JS network status and node summary (status, security, routing)
|
|
344
|
+
- "themes": Installed theme names and defaults (sorted list of theme names, count, default_theme, default_dark_theme)
|
|
343
345
|
- "diagnostics": Per-integration diagnostics dump — integration-defined JSON
|
|
344
346
|
(commonly includes redacted config, device list, state snapshots; exact
|
|
345
347
|
top-level keys vary by integration). REQUIRES ``config_entry_id``. The
|
|
@@ -393,7 +395,13 @@ class SystemTools:
|
|
|
393
395
|
|
|
394
396
|
# Sections that require the system_health WebSocket connection; the
|
|
395
397
|
# REST-based sections (config_check, diagnostics) do not.
|
|
396
|
-
ws_backed = {
|
|
398
|
+
ws_backed = {
|
|
399
|
+
"repairs",
|
|
400
|
+
"zha_network",
|
|
401
|
+
"zha_network_full",
|
|
402
|
+
"zwave_network",
|
|
403
|
+
"themes",
|
|
404
|
+
}
|
|
397
405
|
|
|
398
406
|
ws_client = None
|
|
399
407
|
|
|
@@ -441,6 +449,7 @@ class SystemTools:
|
|
|
441
449
|
"zwave_network",
|
|
442
450
|
"diagnostics",
|
|
443
451
|
"config_check",
|
|
452
|
+
"themes",
|
|
444
453
|
}
|
|
445
454
|
unknown = includes - VALID_INCLUDES
|
|
446
455
|
if unknown:
|
|
@@ -462,6 +471,7 @@ class SystemTools:
|
|
|
462
471
|
want_repairs = "repairs" in includes
|
|
463
472
|
want_zha = zha_full or zha_summary
|
|
464
473
|
want_zwave = "zwave_network" in includes
|
|
474
|
+
want_themes = "themes" in includes
|
|
465
475
|
|
|
466
476
|
if ws_client is None:
|
|
467
477
|
# Health WebSocket unavailable: WS-backed sections can't run.
|
|
@@ -476,13 +486,15 @@ class SystemTools:
|
|
|
476
486
|
result["zha_network"] = {"error": ws_error}
|
|
477
487
|
if want_zwave:
|
|
478
488
|
result["zwave_network"] = {"error": ws_error}
|
|
489
|
+
if want_themes:
|
|
490
|
+
result["themes"] = {"error": ws_error}
|
|
479
491
|
unavailable = sorted(includes & ws_backed)
|
|
480
492
|
if unavailable:
|
|
481
493
|
result.setdefault("warnings", []).append(
|
|
482
494
|
"These sections require the system_health WebSocket, "
|
|
483
495
|
f"which is unavailable: {', '.join(unavailable)}"
|
|
484
496
|
)
|
|
485
|
-
want_repairs = want_zha = want_zwave = False
|
|
497
|
+
want_repairs = want_zha = want_zwave = want_themes = False
|
|
486
498
|
|
|
487
499
|
sections: list[tuple[str, Coroutine[Any, Any, dict[str, Any]]]] = []
|
|
488
500
|
if want_repairs:
|
|
@@ -501,6 +513,8 @@ class SystemTools:
|
|
|
501
513
|
)
|
|
502
514
|
if want_zwave:
|
|
503
515
|
sections.append(("zwave_network", self._fetch_zwave_network(ws_client)))
|
|
516
|
+
if want_themes:
|
|
517
|
+
sections.append(("themes", self._fetch_themes(ws_client)))
|
|
504
518
|
|
|
505
519
|
if sections:
|
|
506
520
|
gathered = await asyncio.gather(
|
|
@@ -859,6 +873,38 @@ class SystemTools:
|
|
|
859
873
|
)
|
|
860
874
|
return zwave_network
|
|
861
875
|
|
|
876
|
+
@staticmethod
|
|
877
|
+
async def _fetch_themes(ws_client: Any) -> dict[str, Any]:
|
|
878
|
+
"""Fetch installed theme names and defaults from Home Assistant.
|
|
879
|
+
|
|
880
|
+
Returns theme NAMES plus defaults, not the full per-theme CSS variable
|
|
881
|
+
dicts (installed community themes can carry hundreds of variables; this
|
|
882
|
+
section is a listing/verify surface, not a content dump).
|
|
883
|
+
"""
|
|
884
|
+
themes_data: dict[str, Any] = {
|
|
885
|
+
"themes": [],
|
|
886
|
+
"count": 0,
|
|
887
|
+
"default_theme": None,
|
|
888
|
+
"default_dark_theme": None,
|
|
889
|
+
}
|
|
890
|
+
try:
|
|
891
|
+
themes_result = await ws_client.send_command("frontend/get_themes")
|
|
892
|
+
if themes_result.get("success"):
|
|
893
|
+
themes_data = summarize_theme_listing(themes_result.get("result") or {})
|
|
894
|
+
else:
|
|
895
|
+
err = themes_result.get("error") or {}
|
|
896
|
+
err_msg = (
|
|
897
|
+
err.get("message") if isinstance(err, dict) else str(err)
|
|
898
|
+
) or "unknown error"
|
|
899
|
+
logger.warning(
|
|
900
|
+
"frontend/get_themes returned success=false: %s", err_msg
|
|
901
|
+
)
|
|
902
|
+
themes_data["error"] = f"Themes data not available: {err_msg}"
|
|
903
|
+
except Exception as e:
|
|
904
|
+
logger.warning("Failed to fetch themes: %s", e)
|
|
905
|
+
themes_data["error"] = f"Themes data not available: {e}"
|
|
906
|
+
return themes_data
|
|
907
|
+
|
|
862
908
|
async def _fetch_config_check(self) -> dict[str, Any]:
|
|
863
909
|
"""Validate HA configuration via POST /config/core/check_config.
|
|
864
910
|
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"""Frontend theme management tools for Home Assistant.
|
|
2
|
+
|
|
3
|
+
Themes are YAML/file-based: Home Assistant itself exposes no API to create or
|
|
4
|
+
edit theme files. This module covers what CAN be managed at runtime - listing
|
|
5
|
+
the installed themes and selecting the backend default theme. Creating or
|
|
6
|
+
editing custom theme files goes through ha_config_set_yaml (beta, ha-mcp
|
|
7
|
+
custom component); installing community themes goes through HACS.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
from typing import Annotated, Any, Literal
|
|
12
|
+
|
|
13
|
+
from fastmcp.exceptions import ToolError
|
|
14
|
+
from fastmcp.tools import tool
|
|
15
|
+
from pydantic import Field
|
|
16
|
+
|
|
17
|
+
from ..client.rest_client import HomeAssistantAPIError
|
|
18
|
+
from ..errors import ErrorCode, create_error_response
|
|
19
|
+
from .helpers import (
|
|
20
|
+
exception_to_structured_error,
|
|
21
|
+
log_tool_usage,
|
|
22
|
+
raise_tool_error,
|
|
23
|
+
register_tool_methods,
|
|
24
|
+
)
|
|
25
|
+
from .util_helpers import summarize_theme_listing, websocket_error_message
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
ThemeAction = Literal["list", "set"]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ThemesTools:
|
|
33
|
+
"""Frontend theme listing and selection tools."""
|
|
34
|
+
|
|
35
|
+
def __init__(self, client: Any) -> None:
|
|
36
|
+
self._client = client
|
|
37
|
+
|
|
38
|
+
async def _list_themes(self) -> dict[str, Any]:
|
|
39
|
+
"""Fetch installed theme names and defaults via websocket."""
|
|
40
|
+
result = await self._client.send_websocket_message(
|
|
41
|
+
{"type": "frontend/get_themes"}
|
|
42
|
+
)
|
|
43
|
+
if not result.get("success"):
|
|
44
|
+
error_msg = websocket_error_message(result.get("error", "Operation failed"))
|
|
45
|
+
raise_tool_error(
|
|
46
|
+
create_error_response(
|
|
47
|
+
ErrorCode.SERVICE_CALL_FAILED,
|
|
48
|
+
f"Failed to list themes: {error_msg}",
|
|
49
|
+
context={"action": "list"},
|
|
50
|
+
)
|
|
51
|
+
)
|
|
52
|
+
return summarize_theme_listing(result.get("result") or {})
|
|
53
|
+
|
|
54
|
+
@tool(
|
|
55
|
+
name="ha_manage_theme",
|
|
56
|
+
tags={"System"},
|
|
57
|
+
annotations={
|
|
58
|
+
"destructiveHint": True,
|
|
59
|
+
"idempotentHint": True,
|
|
60
|
+
"readOnlyHint": False,
|
|
61
|
+
"title": "Manage Frontend Themes",
|
|
62
|
+
},
|
|
63
|
+
)
|
|
64
|
+
@log_tool_usage
|
|
65
|
+
async def ha_manage_theme(
|
|
66
|
+
self,
|
|
67
|
+
action: Annotated[
|
|
68
|
+
ThemeAction,
|
|
69
|
+
Field(
|
|
70
|
+
description=(
|
|
71
|
+
"Theme operation: list installed themes or set the default theme."
|
|
72
|
+
),
|
|
73
|
+
),
|
|
74
|
+
],
|
|
75
|
+
theme_name: Annotated[
|
|
76
|
+
str | None,
|
|
77
|
+
Field(
|
|
78
|
+
description=(
|
|
79
|
+
"Theme name when action='set'. Must be an installed theme; "
|
|
80
|
+
"'default' restores the built-in theme, 'none' resets the "
|
|
81
|
+
"chosen mode to the built-in default."
|
|
82
|
+
),
|
|
83
|
+
default=None,
|
|
84
|
+
),
|
|
85
|
+
] = None,
|
|
86
|
+
mode: Annotated[
|
|
87
|
+
Literal["light", "dark"] | None,
|
|
88
|
+
Field(
|
|
89
|
+
description=(
|
|
90
|
+
"Which mode the theme applies to when action='set'. "
|
|
91
|
+
"Defaults to light."
|
|
92
|
+
),
|
|
93
|
+
default=None,
|
|
94
|
+
),
|
|
95
|
+
] = None,
|
|
96
|
+
) -> dict[str, Any]:
|
|
97
|
+
"""Manage Home Assistant frontend themes.
|
|
98
|
+
|
|
99
|
+
When NOT to use: themes are YAML files - Home Assistant has no API to
|
|
100
|
+
create or edit them. Installing community themes goes through HACS
|
|
101
|
+
(ha_manage_hacs); editing custom theme files goes through
|
|
102
|
+
ha_config_set_yaml (beta, edits themes/<name>.yaml keyed by theme name
|
|
103
|
+
and attempts an automatic theme reload).
|
|
104
|
+
|
|
105
|
+
When to use: action='list' discovers installed theme names and the
|
|
106
|
+
current defaults; action='set' selects the backend default theme
|
|
107
|
+
(optionally per light/dark mode).
|
|
108
|
+
|
|
109
|
+
Caveats: action='set' changes the backend-selected default only -
|
|
110
|
+
users who explicitly picked a theme in their profile keep their
|
|
111
|
+
choice. Theme names are validated by Home Assistant at call time.
|
|
112
|
+
|
|
113
|
+
EXAMPLES:
|
|
114
|
+
- List themes: ha_manage_theme(action="list")
|
|
115
|
+
- Set default theme: ha_manage_theme(action="set", theme_name="nord")
|
|
116
|
+
- Set dark-mode theme: ha_manage_theme(
|
|
117
|
+
action="set", theme_name="nord", mode="dark")
|
|
118
|
+
- Restore built-in default: ha_manage_theme(
|
|
119
|
+
action="set", theme_name="default")
|
|
120
|
+
"""
|
|
121
|
+
try:
|
|
122
|
+
if action == "list":
|
|
123
|
+
return {"success": True, "data": await self._list_themes()}
|
|
124
|
+
|
|
125
|
+
if not theme_name:
|
|
126
|
+
raise_tool_error(
|
|
127
|
+
create_error_response(
|
|
128
|
+
ErrorCode.VALIDATION_MISSING_PARAMETER,
|
|
129
|
+
"theme_name is required when action='set'",
|
|
130
|
+
context={"action": action},
|
|
131
|
+
suggestions=[
|
|
132
|
+
"Call ha_manage_theme(action='list') to see "
|
|
133
|
+
"installed themes",
|
|
134
|
+
],
|
|
135
|
+
)
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
service_data: dict[str, Any] = {"name": theme_name}
|
|
139
|
+
if mode is not None:
|
|
140
|
+
service_data["mode"] = mode
|
|
141
|
+
try:
|
|
142
|
+
await self._client.call_service("frontend", "set_theme", service_data)
|
|
143
|
+
except HomeAssistantAPIError as e:
|
|
144
|
+
# HA's REST layer collapses the service's validation detail
|
|
145
|
+
# ("Theme X not found") into a bare 400 - name the theme here.
|
|
146
|
+
raise_tool_error(
|
|
147
|
+
create_error_response(
|
|
148
|
+
ErrorCode.SERVICE_CALL_FAILED,
|
|
149
|
+
f"Failed to set theme '{theme_name}': {e}. "
|
|
150
|
+
"The theme may not be installed.",
|
|
151
|
+
context={
|
|
152
|
+
"action": action,
|
|
153
|
+
"theme_name": theme_name,
|
|
154
|
+
"mode": mode,
|
|
155
|
+
},
|
|
156
|
+
suggestions=[
|
|
157
|
+
"Call ha_manage_theme(action='list') to see "
|
|
158
|
+
"installed themes",
|
|
159
|
+
],
|
|
160
|
+
)
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
# Re-read the defaults so the agent sees the effective state.
|
|
164
|
+
listing = await self._list_themes()
|
|
165
|
+
return {
|
|
166
|
+
"success": True,
|
|
167
|
+
"data": {
|
|
168
|
+
"theme": theme_name,
|
|
169
|
+
# HA applies the theme to light mode when mode is omitted.
|
|
170
|
+
"mode": mode or "light",
|
|
171
|
+
"default_theme": listing.get("default_theme"),
|
|
172
|
+
"default_dark_theme": listing.get("default_dark_theme"),
|
|
173
|
+
},
|
|
174
|
+
}
|
|
175
|
+
except ToolError:
|
|
176
|
+
raise
|
|
177
|
+
except Exception as e:
|
|
178
|
+
exception_to_structured_error(
|
|
179
|
+
e,
|
|
180
|
+
context={"action": action, "theme_name": theme_name, "mode": mode},
|
|
181
|
+
suggestions=[
|
|
182
|
+
"Call ha_manage_theme(action='list') to see installed themes",
|
|
183
|
+
"Verify the Home Assistant connection",
|
|
184
|
+
],
|
|
185
|
+
)
|
|
186
|
+
return None # unreachable: exception_to_structured_error always raises
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def register_themes_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
|
|
190
|
+
"""Register frontend theme management tools."""
|
|
191
|
+
register_tool_methods(mcp, ThemesTools(client))
|
|
@@ -152,7 +152,9 @@ class YamlConfigTools:
|
|
|
152
152
|
"For YAML-mode dashboards, "
|
|
153
153
|
"use the dotted form 'lovelace.dashboards.<url_path>' where "
|
|
154
154
|
"<url_path> is lowercase, hyphenated, and not a reserved HA "
|
|
155
|
-
"route.
|
|
155
|
+
"route. For themes in themes/*.yaml, use the theme name "
|
|
156
|
+
"(simple name without dots; content is the mapping of "
|
|
157
|
+
"theme variables only, without the theme name). "
|
|
156
158
|
"'automation', 'script', and 'scene' are accepted only when "
|
|
157
159
|
"file is under packages/*.yaml; in configuration.yaml use "
|
|
158
160
|
"the dedicated storage-mode tools "
|
|
@@ -188,7 +190,9 @@ class YamlConfigTools:
|
|
|
188
190
|
default="configuration.yaml",
|
|
189
191
|
description=(
|
|
190
192
|
"Relative path to the YAML config file. Defaults to "
|
|
191
|
-
"'configuration.yaml'. Also supports 'packages/*.yaml'
|
|
193
|
+
"'configuration.yaml'. Also supports 'packages/*.yaml' and "
|
|
194
|
+
"'themes/*.yaml' (yaml_path is the theme name; "
|
|
195
|
+
"frontend.reload_themes is triggered automatically)."
|
|
192
196
|
),
|
|
193
197
|
),
|
|
194
198
|
] = "configuration.yaml",
|
|
@@ -207,7 +211,7 @@ class YamlConfigTools:
|
|
|
207
211
|
Field(default=True),
|
|
208
212
|
] = True,
|
|
209
213
|
) -> dict[str, Any]:
|
|
210
|
-
"""Update raw YAML configuration in configuration.yaml or
|
|
214
|
+
"""Update raw YAML configuration in configuration.yaml, packages/*.yaml, or themes/*.yaml (LAST RESORT). MUST call ha_get_skill_guide first.
|
|
211
215
|
|
|
212
216
|
**WARNING:** Destructive, disabled by default. Dedicated tools exist for
|
|
213
217
|
almost every use case and should be preferred:
|
|
@@ -224,8 +228,13 @@ class YamlConfigTools:
|
|
|
224
228
|
Intended for YAML-only integrations with no config-flow or API
|
|
225
229
|
equivalent (command_line, rest, shell_command, notify platforms),
|
|
226
230
|
for integrations with significant YAML-only configuration (knx
|
|
227
|
-
entities in package files),
|
|
228
|
-
``lovelace.dashboards.<url_path>`` (no other ``lovelace.*`` keys)
|
|
231
|
+
entities in package files), for registering YAML-mode dashboards via
|
|
232
|
+
``lovelace.dashboards.<url_path>`` (no other ``lovelace.*`` keys),
|
|
233
|
+
and for editing theme files in ``themes/*.yaml`` (keyed by theme name;
|
|
234
|
+
``frontend.reload_themes`` is triggered automatically so no restart is
|
|
235
|
+
needed). Themes only load when configuration.yaml carries the
|
|
236
|
+
``frontend: themes:`` include (e.g. ``!include_dir_merge_named themes``);
|
|
237
|
+
this tool cannot add that include (``frontend`` is not an allowed key).
|
|
229
238
|
Also accepts ``automation``, ``script``, and ``scene`` keys when
|
|
230
239
|
``file`` is a ``packages/*.yaml`` — for git-managed YAML configs
|
|
231
240
|
that track these alongside templates and other YAML items. Writes
|
|
@@ -233,8 +242,13 @@ class YamlConfigTools:
|
|
|
233
242
|
storage-mode and YAML-mode collections don't collide; use the
|
|
234
243
|
dedicated storage-mode tools instead.
|
|
235
244
|
Check ``post_action`` in the response: most keys need a full HA
|
|
236
|
-
restart
|
|
237
|
-
|
|
245
|
+
restart. For ``themes/*.yaml`` this tool *performs* the reload itself
|
|
246
|
+
(``frontend.reload_themes``), so ``post_action`` is ``reload_performed``
|
|
247
|
+
(or ``reload_available`` plus ``reload_error`` if that reload failed).
|
|
248
|
+
For template, mqtt, group, automation, script, and scene it only
|
|
249
|
+
*advertises* the reload service (``post_action: reload_available`` with
|
|
250
|
+
a ``reload_service`` to call yourself); the edit is on disk but not yet
|
|
251
|
+
live. Preserves YAML comments and HA tags (``!include``,
|
|
238
252
|
``!secret``) on round-trip; ``replace`` swaps the subtree as-is.
|
|
239
253
|
|
|
240
254
|
``template-guidelines.md`` ships in this response under ``skill_content``
|
|
@@ -352,6 +366,18 @@ class YamlConfigTools:
|
|
|
352
366
|
result = unwrap_service_response(result)
|
|
353
367
|
if not result.get("success", True):
|
|
354
368
|
raise_tool_error(result)
|
|
369
|
+
if "reload_error" in result:
|
|
370
|
+
# Theme edits trigger frontend.reload_themes in the
|
|
371
|
+
# component; the file write succeeded even when that
|
|
372
|
+
# reload failed, so degrade to a warning instead of
|
|
373
|
+
# masking the partial failure behind a bare success.
|
|
374
|
+
result.setdefault("warnings", []).append(
|
|
375
|
+
"The file was updated, but "
|
|
376
|
+
f"{result.get('reload_service', 'the reload service')} "
|
|
377
|
+
f"failed: {result['reload_error']}. Re-run the reload "
|
|
378
|
+
'(e.g. ha_reload_core(target="themes")) or restart '
|
|
379
|
+
"Home Assistant to apply the change."
|
|
380
|
+
)
|
|
355
381
|
attach_skill_content(
|
|
356
382
|
result,
|
|
357
383
|
MandatoryBPS=MandatoryBPS,
|
|
@@ -36,6 +36,23 @@ def websocket_error_message(error: Any) -> str:
|
|
|
36
36
|
return str(error)
|
|
37
37
|
|
|
38
38
|
|
|
39
|
+
def summarize_theme_listing(raw_themes: dict[str, Any]) -> dict[str, Any]:
|
|
40
|
+
"""Summarize a ``frontend/get_themes`` result into names plus defaults.
|
|
41
|
+
|
|
42
|
+
Returns theme NAMES, not the full per-theme CSS variable dicts (installed
|
|
43
|
+
community themes can carry hundreds of variables; listings are a
|
|
44
|
+
discovery/verify surface, not a content dump).
|
|
45
|
+
"""
|
|
46
|
+
themes_value = raw_themes.get("themes") or {}
|
|
47
|
+
theme_names = sorted(themes_value.keys() if isinstance(themes_value, dict) else [])
|
|
48
|
+
return {
|
|
49
|
+
"themes": theme_names,
|
|
50
|
+
"count": len(theme_names),
|
|
51
|
+
"default_theme": raw_themes.get("default_theme"),
|
|
52
|
+
"default_dark_theme": raw_themes.get("default_dark_theme"),
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
39
56
|
def strip_internal_fields(obj: Any, _seen: set[int] | None = None) -> Any:
|
|
40
57
|
"""Remove leading-underscore keys from ``obj`` and any nested dicts
|
|
41
58
|
or lists in place.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ha-mcp-dev
|
|
3
|
-
Version: 7.7.0.
|
|
3
|
+
Version: 7.7.0.dev687
|
|
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
|
|
@@ -38,7 +38,7 @@ Dynamic: license-file
|
|
|
38
38
|
<!-- mcp-name: io.github.homeassistant-ai/ha-mcp -->
|
|
39
39
|
|
|
40
40
|
<p align="center">
|
|
41
|
-
<img src="https://img.shields.io/badge/tools-
|
|
41
|
+
<img src="https://img.shields.io/badge/tools-84-blue" alt="95+ Tools">
|
|
42
42
|
<a href="https://github.com/homeassistant-ai/ha-mcp/releases"><img src="https://img.shields.io/github/v/release/homeassistant-ai/ha-mcp" alt="Release"></a>
|
|
43
43
|
<a href="https://github.com/homeassistant-ai/ha-mcp/actions/workflows/e2e-tests.yml"><img src="https://img.shields.io/github/actions/workflow/status/homeassistant-ai/ha-mcp/e2e-tests.yml?branch=master&label=E2E%20Tests" alt="E2E Tests"></a>
|
|
44
44
|
<a href="LICENSE.md"><img src="https://img.shields.io/github/license/homeassistant-ai/ha-mcp.svg" alt="License"></a>
|
|
@@ -209,7 +209,7 @@ Spend less time configuring, more time enjoying your smart home.
|
|
|
209
209
|
<details>
|
|
210
210
|
<!-- TOOLS_TABLE_START -->
|
|
211
211
|
|
|
212
|
-
<summary><b>Complete Tool List (
|
|
212
|
+
<summary><b>Complete Tool List (84 tools)</b></summary>
|
|
213
213
|
|
|
214
214
|
| Category | Tools |
|
|
215
215
|
|----------|-------|
|
|
@@ -236,7 +236,7 @@ Spend less time configuring, more time enjoying your smart home.
|
|
|
236
236
|
| **Scripts** | `ha_config_get_script`, `ha_config_remove_script`, `ha_config_set_script` |
|
|
237
237
|
| **Search & Discovery** | `ha_get_overview`, `ha_get_state`, `ha_search` |
|
|
238
238
|
| **Service & Device Control** | `ha_bulk_control`, `ha_call_event`, `ha_call_service`, `ha_get_operation_status`, `ha_list_services` |
|
|
239
|
-
| **System** | `ha_config_set_yaml` *(beta)*, `ha_get_updates`, `ha_manage_backup`, `ha_manage_custom_tool` *(beta)*, `ha_reload_core`, `ha_restart` |
|
|
239
|
+
| **System** | `ha_config_set_yaml` *(beta)*, `ha_get_updates`, `ha_manage_backup`, `ha_manage_custom_tool` *(beta)*, `ha_manage_theme`, `ha_reload_core`, `ha_restart` |
|
|
240
240
|
| **Todo Lists** | `ha_get_todo`, `ha_remove_todo_item`, `ha_set_todo_item` |
|
|
241
241
|
| **Utilities** | `ha_eval_template`, `ha_install_mcp_tools` *(beta)*, `ha_report_issue` |
|
|
242
242
|
| **Zones** | `ha_get_zone`, `ha_remove_zone`, `ha_set_zone` |
|
|
@@ -97,6 +97,7 @@ src/ha_mcp/tools/tools_search.py
|
|
|
97
97
|
src/ha_mcp/tools/tools_service.py
|
|
98
98
|
src/ha_mcp/tools/tools_services.py
|
|
99
99
|
src/ha_mcp/tools/tools_system.py
|
|
100
|
+
src/ha_mcp/tools/tools_themes.py
|
|
100
101
|
src/ha_mcp/tools/tools_todo.py
|
|
101
102
|
src/ha_mcp/tools/tools_traces.py
|
|
102
103
|
src/ha_mcp/tools/tools_updates.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
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/dashboard_screenshot/__init__.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/dashboard_screenshot/capture.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/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
|
{ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/resources/skills-vendor/AGENTS.md
RENAMED
|
File without changes
|
{ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/resources/skills-vendor/CLAUDE.md
RENAMED
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/resources/skills-vendor/LICENSE
RENAMED
|
File without changes
|
{ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/best_practice_checker.py
RENAMED
|
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.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/smart_search/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/smart_search/_config.py
RENAMED
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/smart_search/_entities.py
RENAMED
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/smart_search/_overview.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/smart_search/_scenes.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/smart_search/_scoring.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
|
{ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/tools_config_automations.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/tools_config_dashboards.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/tools_config_helpers.py
RENAMED
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/tools_config_scripts.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/tools_dashboard_screenshot.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.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/tools_voice_assistant.py
RENAMED
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/tools/validation_middleware.py
RENAMED
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/transforms/categorized_search.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/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
|
{ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp/utils/kill_signal_diagnostics.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp_dev.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev687}/src/ha_mcp_dev.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|