ha-mcp-dev 7.7.0.dev692__tar.gz → 7.7.0.dev694__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.dev692/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.7.0.dev694}/PKG-INFO +1 -1
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/pyproject.toml +1 -1
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/tools_addons.py +5 -1
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/tools_areas.py +4 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/tools_config_helpers.py +10 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/tools_energy.py +9 -3
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/tools_entities.py +4 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/tools_groups.py +4 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/tools_history.py +7 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/tools_integrations.py +2 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/tools_registry.py +2 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/tools_search.py +9 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/tools_service.py +8 -13
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/tools_services.py +3 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/tools_system.py +4 -1
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/validation_middleware.py +23 -5
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/LICENSE +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/MANIFEST.in +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/README.md +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/setup.cfg +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/__init__.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/__main__.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/_pypi_marker +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/_version.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/auth/__init__.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/auth/consent_form.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/auth/provider.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/backup_manager.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/client/__init__.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/client/rest_client.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/client/supervisor_client.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/client/websocket_client.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/client/websocket_listener.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/config.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/dashboard_screenshot/__init__.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/dashboard_screenshot/capture.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/dashboard_screenshot/provision.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/errors.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/policy/__init__.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/policy/approval_queue.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/policy/evaluator.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/policy/handlers.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/policy/middleware.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/policy/model.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/policy/persistence.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/policy/value_sources.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/py.typed +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/read_only.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/yaml-only-integrations.md +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/server.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/settings.css +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/settings.js +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/settings_ui.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/smoke_test.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/__init__.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/auto_backup.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/backup.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/best_practice_checker.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/config_entry_flow.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/device_control.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/enhanced.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/helpers.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/reference_validator.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/registry.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/smart_search/__init__.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/smart_search/_base.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/smart_search/_config.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/smart_search/_deep.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/smart_search/_entities.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/smart_search/_fetch.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/smart_search/_overview.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/smart_search/_scenes.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/smart_search/_scoring.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/tools_blueprints.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/tools_bug_report.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/tools_calendar.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/tools_camera.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/tools_categories.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/tools_code.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/tools_config_automations.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/tools_dashboard_screenshot.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/tools_filesystem.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/tools_hacs.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/tools_labels.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/tools_mcp_component.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/tools_resources.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/tools_themes.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/tools_todo.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/tools_traces.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/tools_updates.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/tools_utility.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/tools_zones.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/util_helpers.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/transforms/__init__.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/transforms/categorized_search.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/utils/__init__.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/utils/config_hash.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/utils/data_paths.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/utils/domain_handlers.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/utils/fuzzy_search.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/utils/operation_manager.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/utils/python_sandbox.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/utils/skill_loader.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/utils/usage_logger.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/tests/__init__.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/tests/test_constants.py +0 -0
- {ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/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.7.0.
|
|
7
|
+
version = "7.7.0.dev694"
|
|
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"
|
|
@@ -41,7 +41,7 @@ from .helpers import (
|
|
|
41
41
|
raise_tool_error,
|
|
42
42
|
validate_identifier_not_empty,
|
|
43
43
|
)
|
|
44
|
-
from .util_helpers import ANSI_ESCAPE_RE
|
|
44
|
+
from .util_helpers import ANSI_ESCAPE_RE, JSON_STRING_COERCION
|
|
45
45
|
|
|
46
46
|
logger = logging.getLogger(__name__)
|
|
47
47
|
|
|
@@ -2712,6 +2712,7 @@ def register_addon_tools(mcp: Any, client: HomeAssistantClient, **kwargs: Any) -
|
|
|
2712
2712
|
] = None,
|
|
2713
2713
|
options: Annotated[
|
|
2714
2714
|
dict[str, Any] | None,
|
|
2715
|
+
JSON_STRING_COERCION,
|
|
2715
2716
|
Field(
|
|
2716
2717
|
description="Config mode: Add-on configuration values (the 'Configuration' tab in the UI).",
|
|
2717
2718
|
default=None,
|
|
@@ -2719,6 +2720,7 @@ def register_addon_tools(mcp: Any, client: HomeAssistantClient, **kwargs: Any) -
|
|
|
2719
2720
|
] = None,
|
|
2720
2721
|
network: Annotated[
|
|
2721
2722
|
dict[str, Any] | None,
|
|
2723
|
+
JSON_STRING_COERCION,
|
|
2722
2724
|
Field(
|
|
2723
2725
|
description="Config mode: Host port mappings (e.g., {'5800/tcp': 8081}).",
|
|
2724
2726
|
default=None,
|
|
@@ -2747,6 +2749,7 @@ def register_addon_tools(mcp: Any, client: HomeAssistantClient, **kwargs: Any) -
|
|
|
2747
2749
|
] = None,
|
|
2748
2750
|
array_patch: Annotated[
|
|
2749
2751
|
dict[str, Any] | None,
|
|
2752
|
+
JSON_STRING_COERCION,
|
|
2750
2753
|
Field(
|
|
2751
2754
|
description=(
|
|
2752
2755
|
"Array-patch mode: atomically GET a JSON array endpoint, "
|
|
@@ -2760,6 +2763,7 @@ def register_addon_tools(mcp: Any, client: HomeAssistantClient, **kwargs: Any) -
|
|
|
2760
2763
|
] = None,
|
|
2761
2764
|
request_headers: Annotated[
|
|
2762
2765
|
dict[str, str] | None,
|
|
2766
|
+
JSON_STRING_COERCION,
|
|
2763
2767
|
Field(
|
|
2764
2768
|
description=(
|
|
2765
2769
|
"Proxy/array-patch mode: extra HTTP headers to send to the addon API. "
|
|
@@ -23,6 +23,7 @@ from .helpers import (
|
|
|
23
23
|
validate_identifier_not_empty,
|
|
24
24
|
)
|
|
25
25
|
from .util_helpers import (
|
|
26
|
+
JSON_STRING_COERCION,
|
|
26
27
|
parse_string_list_param,
|
|
27
28
|
project_fields,
|
|
28
29
|
project_records,
|
|
@@ -148,6 +149,7 @@ class AreaTools:
|
|
|
148
149
|
self,
|
|
149
150
|
fields: Annotated[
|
|
150
151
|
str | list[str] | None,
|
|
152
|
+
JSON_STRING_COERCION,
|
|
151
153
|
Field(
|
|
152
154
|
default=None,
|
|
153
155
|
description=(
|
|
@@ -162,6 +164,7 @@ class AreaTools:
|
|
|
162
164
|
] = None,
|
|
163
165
|
area_fields: Annotated[
|
|
164
166
|
str | list[str] | None,
|
|
167
|
+
JSON_STRING_COERCION,
|
|
165
168
|
Field(
|
|
166
169
|
default=None,
|
|
167
170
|
description=(
|
|
@@ -446,6 +449,7 @@ class AreaTools:
|
|
|
446
449
|
] = None,
|
|
447
450
|
aliases: Annotated[
|
|
448
451
|
str | list[str] | None,
|
|
452
|
+
JSON_STRING_COERCION,
|
|
449
453
|
Field(
|
|
450
454
|
description="Alternative names for voice assistant recognition (e.g., ['lounge'], empty list to clear)",
|
|
451
455
|
default=None,
|
{ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/tools_config_helpers.py
RENAMED
|
@@ -3500,6 +3500,7 @@ class HelperConfigTools:
|
|
|
3500
3500
|
] = None,
|
|
3501
3501
|
labels: Annotated[
|
|
3502
3502
|
str | list[str] | None,
|
|
3503
|
+
JSON_STRING_COERCION,
|
|
3503
3504
|
Field(description="Labels to categorize the helper", default=None),
|
|
3504
3505
|
] = None,
|
|
3505
3506
|
min_value: Annotated[
|
|
@@ -3535,6 +3536,7 @@ class HelperConfigTools:
|
|
|
3535
3536
|
] = None,
|
|
3536
3537
|
options: Annotated[
|
|
3537
3538
|
str | list[str] | None,
|
|
3539
|
+
JSON_STRING_COERCION,
|
|
3538
3540
|
Field(
|
|
3539
3541
|
description="List of options for input_select (required for input_select)",
|
|
3540
3542
|
default=None,
|
|
@@ -3582,6 +3584,7 @@ class HelperConfigTools:
|
|
|
3582
3584
|
] = None,
|
|
3583
3585
|
monday: Annotated[
|
|
3584
3586
|
list[dict[str, Any]] | None,
|
|
3587
|
+
JSON_STRING_COERCION,
|
|
3585
3588
|
Field(
|
|
3586
3589
|
description="Schedule time ranges for Monday. List of {'from': 'HH:MM', 'to': 'HH:MM'} dicts. Optional 'data' dict for additional attributes (e.g. {'from': '07:00', 'to': '22:00', 'data': {'mode': 'comfort'}})",
|
|
3587
3590
|
default=None,
|
|
@@ -3589,6 +3592,7 @@ class HelperConfigTools:
|
|
|
3589
3592
|
] = None,
|
|
3590
3593
|
tuesday: Annotated[
|
|
3591
3594
|
list[dict[str, Any]] | None,
|
|
3595
|
+
JSON_STRING_COERCION,
|
|
3592
3596
|
Field(
|
|
3593
3597
|
description="Schedule time ranges for Tuesday. List of {'from': 'HH:MM', 'to': 'HH:MM'} dicts. Optional 'data' dict for additional attributes.",
|
|
3594
3598
|
default=None,
|
|
@@ -3596,6 +3600,7 @@ class HelperConfigTools:
|
|
|
3596
3600
|
] = None,
|
|
3597
3601
|
wednesday: Annotated[
|
|
3598
3602
|
list[dict[str, Any]] | None,
|
|
3603
|
+
JSON_STRING_COERCION,
|
|
3599
3604
|
Field(
|
|
3600
3605
|
description="Schedule time ranges for Wednesday. List of {'from': 'HH:MM', 'to': 'HH:MM'} dicts. Optional 'data' dict for additional attributes.",
|
|
3601
3606
|
default=None,
|
|
@@ -3603,6 +3608,7 @@ class HelperConfigTools:
|
|
|
3603
3608
|
] = None,
|
|
3604
3609
|
thursday: Annotated[
|
|
3605
3610
|
list[dict[str, Any]] | None,
|
|
3611
|
+
JSON_STRING_COERCION,
|
|
3606
3612
|
Field(
|
|
3607
3613
|
description="Schedule time ranges for Thursday. List of {'from': 'HH:MM', 'to': 'HH:MM'} dicts. Optional 'data' dict for additional attributes.",
|
|
3608
3614
|
default=None,
|
|
@@ -3610,6 +3616,7 @@ class HelperConfigTools:
|
|
|
3610
3616
|
] = None,
|
|
3611
3617
|
friday: Annotated[
|
|
3612
3618
|
list[dict[str, Any]] | None,
|
|
3619
|
+
JSON_STRING_COERCION,
|
|
3613
3620
|
Field(
|
|
3614
3621
|
description="Schedule time ranges for Friday. List of {'from': 'HH:MM', 'to': 'HH:MM'} dicts. Optional 'data' dict for additional attributes.",
|
|
3615
3622
|
default=None,
|
|
@@ -3617,6 +3624,7 @@ class HelperConfigTools:
|
|
|
3617
3624
|
] = None,
|
|
3618
3625
|
saturday: Annotated[
|
|
3619
3626
|
list[dict[str, Any]] | None,
|
|
3627
|
+
JSON_STRING_COERCION,
|
|
3620
3628
|
Field(
|
|
3621
3629
|
description="Schedule time ranges for Saturday. List of {'from': 'HH:MM', 'to': 'HH:MM'} dicts. Optional 'data' dict for additional attributes.",
|
|
3622
3630
|
default=None,
|
|
@@ -3624,6 +3632,7 @@ class HelperConfigTools:
|
|
|
3624
3632
|
] = None,
|
|
3625
3633
|
sunday: Annotated[
|
|
3626
3634
|
list[dict[str, Any]] | None,
|
|
3635
|
+
JSON_STRING_COERCION,
|
|
3627
3636
|
Field(
|
|
3628
3637
|
description="Schedule time ranges for Sunday. List of {'from': 'HH:MM', 'to': 'HH:MM'} dicts. Optional 'data' dict for additional attributes.",
|
|
3629
3638
|
default=None,
|
|
@@ -3654,6 +3663,7 @@ class HelperConfigTools:
|
|
|
3654
3663
|
] = None,
|
|
3655
3664
|
device_trackers: Annotated[
|
|
3656
3665
|
list[str] | None,
|
|
3666
|
+
JSON_STRING_COERCION,
|
|
3657
3667
|
Field(
|
|
3658
3668
|
description="List of device_tracker entity IDs for person", default=None
|
|
3659
3669
|
),
|
|
@@ -43,6 +43,7 @@ from .helpers import (
|
|
|
43
43
|
register_tool_methods,
|
|
44
44
|
validate_identifier_not_empty,
|
|
45
45
|
)
|
|
46
|
+
from .util_helpers import JSON_STRING_COERCION
|
|
46
47
|
|
|
47
48
|
logger = logging.getLogger(__name__)
|
|
48
49
|
|
|
@@ -341,6 +342,7 @@ class EnergyTools:
|
|
|
341
342
|
],
|
|
342
343
|
config: Annotated[
|
|
343
344
|
dict[str, Any] | None,
|
|
345
|
+
JSON_STRING_COERCION,
|
|
344
346
|
Field(
|
|
345
347
|
description=(
|
|
346
348
|
"Full prefs payload for mode='set'. Must contain the "
|
|
@@ -362,9 +364,12 @@ class EnergyTools:
|
|
|
362
364
|
"Hash from a previous mode='get' call. REQUIRED for "
|
|
363
365
|
"mode='set' unless dry_run=True. Two forms: str (full-"
|
|
364
366
|
"blob lock) or dict (per-key lock, taken from the "
|
|
365
|
-
"config_hash_per_key field of mode='get').
|
|
366
|
-
"
|
|
367
|
-
"
|
|
367
|
+
"config_hash_per_key field of mode='get'). Pass the dict "
|
|
368
|
+
"form as a native object, NOT a JSON-encoded string — a "
|
|
369
|
+
"stringified dict is treated as a full-blob token and will "
|
|
370
|
+
"report RESOURCE_LOCKED; clients that can only send strings "
|
|
371
|
+
"should use the str full-blob form. See the tool docstring "
|
|
372
|
+
"for fail-closed semantics. Ignored by convenience modes."
|
|
368
373
|
),
|
|
369
374
|
default=None,
|
|
370
375
|
),
|
|
@@ -438,6 +443,7 @@ class EnergyTools:
|
|
|
438
443
|
] = False,
|
|
439
444
|
source: Annotated[
|
|
440
445
|
dict[str, Any] | None,
|
|
446
|
+
JSON_STRING_COERCION,
|
|
441
447
|
Field(
|
|
442
448
|
description=(
|
|
443
449
|
"Single energy_sources entry for mode='add_source'. Must "
|
|
@@ -559,6 +559,7 @@ def register_entity_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
|
|
|
559
559
|
async def ha_set_entity(
|
|
560
560
|
entity_id: Annotated[
|
|
561
561
|
str | list[str],
|
|
562
|
+
JSON_STRING_COERCION,
|
|
562
563
|
Field(
|
|
563
564
|
description="Entity ID or list of entity IDs to update. Bulk operations (list) only support labels, expose_to, and categories parameters."
|
|
564
565
|
),
|
|
@@ -639,6 +640,7 @@ def register_entity_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
|
|
|
639
640
|
] = None,
|
|
640
641
|
aliases: Annotated[
|
|
641
642
|
str | list[str] | None,
|
|
643
|
+
JSON_STRING_COERCION,
|
|
642
644
|
Field(
|
|
643
645
|
description="List of voice assistant aliases for the entity (replaces existing aliases). Single entity only.",
|
|
644
646
|
default=None,
|
|
@@ -659,6 +661,7 @@ def register_entity_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
|
|
|
659
661
|
] = None,
|
|
660
662
|
labels: Annotated[
|
|
661
663
|
str | list[str] | None,
|
|
664
|
+
JSON_STRING_COERCION,
|
|
662
665
|
Field(
|
|
663
666
|
description="List of label IDs for the entity. Behavior depends on label_operation parameter. Supports bulk operations.",
|
|
664
667
|
default=None,
|
|
@@ -1117,6 +1120,7 @@ def register_entity_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
|
|
|
1117
1120
|
async def ha_get_entity(
|
|
1118
1121
|
entity_id: Annotated[
|
|
1119
1122
|
str | list[str],
|
|
1123
|
+
JSON_STRING_COERCION,
|
|
1120
1124
|
Field(
|
|
1121
1125
|
description="Entity ID or list of entity IDs to retrieve (e.g., 'sensor.temperature' or ['light.living_room', 'switch.porch'])"
|
|
1122
1126
|
),
|
|
@@ -26,6 +26,7 @@ from .helpers import (
|
|
|
26
26
|
validate_identifier_not_empty,
|
|
27
27
|
)
|
|
28
28
|
from .util_helpers import (
|
|
29
|
+
JSON_STRING_COERCION,
|
|
29
30
|
wait_for_entity_registered,
|
|
30
31
|
wait_for_entity_removed,
|
|
31
32
|
)
|
|
@@ -223,6 +224,7 @@ class GroupTools:
|
|
|
223
224
|
],
|
|
224
225
|
entities: Annotated[
|
|
225
226
|
list[str] | None,
|
|
227
|
+
JSON_STRING_COERCION,
|
|
226
228
|
Field(
|
|
227
229
|
description="List of entity IDs for the group. Required when creating new group. When updating, replaces all entities (mutually exclusive with add_entities/remove_entities).",
|
|
228
230
|
default=None,
|
|
@@ -251,6 +253,7 @@ class GroupTools:
|
|
|
251
253
|
] = None,
|
|
252
254
|
add_entities: Annotated[
|
|
253
255
|
list[str] | None,
|
|
256
|
+
JSON_STRING_COERCION,
|
|
254
257
|
Field(
|
|
255
258
|
description="Add these entities to an existing group (mutually exclusive with entities)",
|
|
256
259
|
default=None,
|
|
@@ -258,6 +261,7 @@ class GroupTools:
|
|
|
258
261
|
] = None,
|
|
259
262
|
remove_entities: Annotated[
|
|
260
263
|
list[str] | None,
|
|
264
|
+
JSON_STRING_COERCION,
|
|
261
265
|
Field(
|
|
262
266
|
description="Remove these entities from an existing group (mutually exclusive with entities)",
|
|
263
267
|
default=None,
|
|
@@ -30,6 +30,7 @@ from .helpers import (
|
|
|
30
30
|
safe_progress,
|
|
31
31
|
)
|
|
32
32
|
from .util_helpers import (
|
|
33
|
+
JSON_STRING_COERCION,
|
|
33
34
|
add_timezone_metadata,
|
|
34
35
|
build_pagination_metadata,
|
|
35
36
|
parse_string_list_param,
|
|
@@ -136,6 +137,7 @@ class HistoryTools:
|
|
|
136
137
|
self,
|
|
137
138
|
entity_ids: Annotated[
|
|
138
139
|
str | list[str],
|
|
140
|
+
JSON_STRING_COERCION,
|
|
139
141
|
Field(
|
|
140
142
|
description="Entity ID(s) to query. Can be a single ID, comma-separated string, or JSON array."
|
|
141
143
|
),
|
|
@@ -206,6 +208,7 @@ class HistoryTools:
|
|
|
206
208
|
] = "day",
|
|
207
209
|
statistic_types: Annotated[
|
|
208
210
|
str | list[str] | None,
|
|
211
|
+
JSON_STRING_COERCION,
|
|
209
212
|
Field(
|
|
210
213
|
description='Statistics types: "mean", "min", "max", "sum", "state", "change". Default: all. Ignored when source="history"',
|
|
211
214
|
default=None,
|
|
@@ -224,6 +227,7 @@ class HistoryTools:
|
|
|
224
227
|
] = "desc",
|
|
225
228
|
fields: Annotated[
|
|
226
229
|
str | list[str] | None,
|
|
230
|
+
JSON_STRING_COERCION,
|
|
227
231
|
Field(
|
|
228
232
|
default=None,
|
|
229
233
|
description=(
|
|
@@ -425,6 +429,9 @@ def _parse_entity_ids(entity_ids: str | list[str]) -> list[str]:
|
|
|
425
429
|
"""Parse entity_ids parameter into a list of strings."""
|
|
426
430
|
if isinstance(entity_ids, str):
|
|
427
431
|
if entity_ids.startswith("["):
|
|
432
|
+
# Belt-and-suspenders: JSON_STRING_COERCION on the param already
|
|
433
|
+
# parses a JSON-array string to a list upstream, so a string reaching
|
|
434
|
+
# here is normally CSV/single. This branch stays as a fallback.
|
|
428
435
|
parsed_ids = parse_string_list_param(entity_ids, "entity_ids")
|
|
429
436
|
if parsed_ids is None:
|
|
430
437
|
raise_tool_error(
|
|
@@ -33,6 +33,7 @@ from .tools_config_helpers import (
|
|
|
33
33
|
_get_entities_for_config_entry,
|
|
34
34
|
)
|
|
35
35
|
from .util_helpers import (
|
|
36
|
+
JSON_STRING_COERCION,
|
|
36
37
|
build_pagination_metadata,
|
|
37
38
|
fetch_integration_diagnostics,
|
|
38
39
|
get_logger_levels,
|
|
@@ -459,6 +460,7 @@ class IntegrationTools:
|
|
|
459
460
|
] = None,
|
|
460
461
|
diagnostics_fields: Annotated[
|
|
461
462
|
list[str] | str | None,
|
|
463
|
+
JSON_STRING_COERCION,
|
|
462
464
|
Field(
|
|
463
465
|
description=(
|
|
464
466
|
"Optional list of top-level keys to keep from the diagnostics "
|
|
@@ -21,6 +21,7 @@ from .helpers import (
|
|
|
21
21
|
validate_identifier_not_empty,
|
|
22
22
|
)
|
|
23
23
|
from .util_helpers import (
|
|
24
|
+
JSON_STRING_COERCION,
|
|
24
25
|
build_pagination_metadata,
|
|
25
26
|
parse_string_list_param,
|
|
26
27
|
)
|
|
@@ -627,6 +628,7 @@ def register_registry_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
|
|
|
627
628
|
] = None,
|
|
628
629
|
labels: Annotated[
|
|
629
630
|
str | list[str] | None,
|
|
631
|
+
JSON_STRING_COERCION,
|
|
630
632
|
Field(
|
|
631
633
|
description="Labels to assign to the device (replaces existing labels)",
|
|
632
634
|
default=None,
|
|
@@ -18,6 +18,7 @@ from ..transforms.categorized_search import DEFAULT_PINNED_TOOLS
|
|
|
18
18
|
from ..utils.fuzzy_search import apply_hidden_penalty
|
|
19
19
|
from .helpers import exception_to_structured_error, log_tool_usage, raise_tool_error
|
|
20
20
|
from .util_helpers import (
|
|
21
|
+
JSON_STRING_COERCION,
|
|
21
22
|
add_timezone_metadata,
|
|
22
23
|
build_pagination_metadata,
|
|
23
24
|
filter_active_repairs,
|
|
@@ -581,6 +582,7 @@ def register_search_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
|
|
|
581
582
|
] = None,
|
|
582
583
|
search_types: Annotated[
|
|
583
584
|
str | list[str] | None,
|
|
585
|
+
JSON_STRING_COERCION,
|
|
584
586
|
Field(
|
|
585
587
|
default=None,
|
|
586
588
|
description=(
|
|
@@ -672,6 +674,7 @@ def register_search_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
|
|
|
672
674
|
] = None,
|
|
673
675
|
result_fields: Annotated[
|
|
674
676
|
str | list[str] | None,
|
|
677
|
+
JSON_STRING_COERCION,
|
|
675
678
|
Field(
|
|
676
679
|
default=None,
|
|
677
680
|
description=(
|
|
@@ -682,6 +685,7 @@ def register_search_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
|
|
|
682
685
|
] = None,
|
|
683
686
|
fields: Annotated[
|
|
684
687
|
str | list[str] | None,
|
|
688
|
+
JSON_STRING_COERCION,
|
|
685
689
|
Field(
|
|
686
690
|
default=None,
|
|
687
691
|
description=(
|
|
@@ -1810,6 +1814,7 @@ def register_search_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
|
|
|
1810
1814
|
] = "minimal",
|
|
1811
1815
|
domains: Annotated[
|
|
1812
1816
|
str | list[str] | None,
|
|
1817
|
+
JSON_STRING_COERCION,
|
|
1813
1818
|
Field(
|
|
1814
1819
|
default=None,
|
|
1815
1820
|
description=(
|
|
@@ -1878,6 +1883,7 @@ def register_search_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
|
|
|
1878
1883
|
] = False,
|
|
1879
1884
|
fields: Annotated[
|
|
1880
1885
|
str | list[str] | None,
|
|
1886
|
+
JSON_STRING_COERCION,
|
|
1881
1887
|
Field(
|
|
1882
1888
|
default=None,
|
|
1883
1889
|
description=(
|
|
@@ -2326,6 +2332,7 @@ def register_search_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
|
|
|
2326
2332
|
async def ha_get_state(
|
|
2327
2333
|
entity_id: Annotated[
|
|
2328
2334
|
str | list[str],
|
|
2335
|
+
JSON_STRING_COERCION,
|
|
2329
2336
|
Field(
|
|
2330
2337
|
description="Entity ID or list of entity IDs to retrieve state for "
|
|
2331
2338
|
"(e.g., 'light.kitchen' or ['light.kitchen', 'sensor.temperature'])"
|
|
@@ -2333,6 +2340,7 @@ def register_search_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
|
|
|
2333
2340
|
],
|
|
2334
2341
|
fields: Annotated[
|
|
2335
2342
|
str | list[str] | None,
|
|
2343
|
+
JSON_STRING_COERCION,
|
|
2336
2344
|
Field(
|
|
2337
2345
|
default=None,
|
|
2338
2346
|
description=(
|
|
@@ -2346,6 +2354,7 @@ def register_search_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
|
|
|
2346
2354
|
] = None,
|
|
2347
2355
|
attribute_keys: Annotated[
|
|
2348
2356
|
str | list[str] | None,
|
|
2357
|
+
JSON_STRING_COERCION,
|
|
2349
2358
|
Field(
|
|
2350
2359
|
default=None,
|
|
2351
2360
|
description=(
|
|
@@ -315,6 +315,7 @@ class ServiceTools:
|
|
|
315
315
|
] = False,
|
|
316
316
|
result_fields: Annotated[
|
|
317
317
|
str | list[str] | None,
|
|
318
|
+
JSON_STRING_COERCION,
|
|
318
319
|
Field(
|
|
319
320
|
default=None,
|
|
320
321
|
description=(
|
|
@@ -327,6 +328,7 @@ class ServiceTools:
|
|
|
327
328
|
] = None,
|
|
328
329
|
result_attribute_keys: Annotated[
|
|
329
330
|
str | list[str] | None,
|
|
331
|
+
JSON_STRING_COERCION,
|
|
330
332
|
Field(
|
|
331
333
|
default=None,
|
|
332
334
|
description=(
|
|
@@ -522,6 +524,7 @@ class ServiceTools:
|
|
|
522
524
|
self,
|
|
523
525
|
operation_id: Annotated[
|
|
524
526
|
str | list[str],
|
|
527
|
+
JSON_STRING_COERCION,
|
|
525
528
|
Field(
|
|
526
529
|
description=(
|
|
527
530
|
"Single operation ID or list of operation IDs to check. "
|
|
@@ -544,23 +547,15 @@ class ServiceTools:
|
|
|
544
547
|
For current entity states, use ha_get_state instead.
|
|
545
548
|
"""
|
|
546
549
|
try:
|
|
547
|
-
#
|
|
548
|
-
|
|
549
|
-
if isinstance(operation_id,
|
|
550
|
-
try:
|
|
551
|
-
parsed = parse_json_param(operation_id, "operation_id")
|
|
552
|
-
if isinstance(parsed, list):
|
|
553
|
-
resolved_id = [str(item) for item in parsed]
|
|
554
|
-
except ValueError:
|
|
555
|
-
pass # Plain string — treat as single operation ID
|
|
556
|
-
|
|
557
|
-
if isinstance(resolved_id, list):
|
|
550
|
+
# JSON_STRING_COERCION turns a '["op1","op2"]' string into a list
|
|
551
|
+
# before the body runs, so operation_id is already the final shape.
|
|
552
|
+
if isinstance(operation_id, list):
|
|
558
553
|
result = await self._device_tools.get_bulk_operation_status(
|
|
559
|
-
operation_ids=
|
|
554
|
+
operation_ids=operation_id
|
|
560
555
|
)
|
|
561
556
|
return cast(dict[str, Any], result)
|
|
562
557
|
result = await self._device_tools.get_device_operation_status(
|
|
563
|
-
operation_id=
|
|
558
|
+
operation_id=operation_id, timeout_seconds=timeout_seconds
|
|
564
559
|
)
|
|
565
560
|
return cast(dict[str, Any], result)
|
|
566
561
|
except ToolError:
|
|
@@ -20,6 +20,7 @@ from .helpers import (
|
|
|
20
20
|
register_tool_methods,
|
|
21
21
|
)
|
|
22
22
|
from .util_helpers import (
|
|
23
|
+
JSON_STRING_COERCION,
|
|
23
24
|
build_pagination_metadata,
|
|
24
25
|
parse_string_list_param,
|
|
25
26
|
project_fields,
|
|
@@ -78,6 +79,7 @@ class ServiceDiscoveryTools:
|
|
|
78
79
|
] = "summary",
|
|
79
80
|
service_fields: Annotated[
|
|
80
81
|
str | list[str] | None,
|
|
82
|
+
JSON_STRING_COERCION,
|
|
81
83
|
Field(
|
|
82
84
|
default=None,
|
|
83
85
|
description=(
|
|
@@ -91,6 +93,7 @@ class ServiceDiscoveryTools:
|
|
|
91
93
|
] = None,
|
|
92
94
|
fields: Annotated[
|
|
93
95
|
str | list[str] | None,
|
|
96
|
+
JSON_STRING_COERCION,
|
|
94
97
|
Field(
|
|
95
98
|
default=None,
|
|
96
99
|
description=(
|
|
@@ -25,6 +25,7 @@ from .helpers import (
|
|
|
25
25
|
register_tool_methods,
|
|
26
26
|
)
|
|
27
27
|
from .util_helpers import (
|
|
28
|
+
JSON_STRING_COERCION,
|
|
28
29
|
fetch_integration_diagnostics,
|
|
29
30
|
filter_active_repairs,
|
|
30
31
|
parse_diagnostics_fields,
|
|
@@ -323,7 +324,9 @@ class SystemTools:
|
|
|
323
324
|
include_dismissed_repairs: bool | None = False,
|
|
324
325
|
config_entry_id: str | None = None,
|
|
325
326
|
device_id: str | None = None,
|
|
326
|
-
diagnostics_fields:
|
|
327
|
+
diagnostics_fields: Annotated[
|
|
328
|
+
list[str] | str | None, JSON_STRING_COERCION
|
|
329
|
+
] = None,
|
|
327
330
|
diagnostics_truncate_at_bytes: Annotated[int, Field(ge=1)] | None = None,
|
|
328
331
|
diagnostics_data_path: str | None = None,
|
|
329
332
|
diagnostics_data_offset: Annotated[int, Field(ge=0)] | None = 0,
|
{ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/validation_middleware.py
RENAMED
|
@@ -43,15 +43,33 @@ class ValidationErrorMiddleware(Middleware):
|
|
|
43
43
|
return await call_next(context)
|
|
44
44
|
except PydanticValidationError as exc:
|
|
45
45
|
errors = exc.errors(include_url=False)
|
|
46
|
-
|
|
46
|
+
# Group by the real argument path. A union param like
|
|
47
|
+
# `str | list[str]` emits one error per arm with loc (param, "str"),
|
|
48
|
+
# (param, "list[str]"); without grouping the user saw `param.str` /
|
|
49
|
+
# `param.list[str]` instead of `param` (#1601). We keep the param
|
|
50
|
+
# name plus any numeric list indices (so a bad element still reports
|
|
51
|
+
# `monday.1`) but drop the non-numeric union-arm tags.
|
|
52
|
+
grouped: dict[str, list[Any]] = {}
|
|
47
53
|
for err in errors:
|
|
48
|
-
loc = err.get("loc", ())
|
|
49
|
-
|
|
50
|
-
|
|
54
|
+
loc = [str(p) for p in err.get("loc", ()) if p != "__root__"]
|
|
55
|
+
if loc:
|
|
56
|
+
key = ".".join([loc[0], *(p for p in loc[1:] if p.isdigit())])
|
|
57
|
+
else:
|
|
58
|
+
key = ""
|
|
59
|
+
grouped.setdefault(key, []).append(err)
|
|
60
|
+
|
|
61
|
+
parts: list[str] = []
|
|
62
|
+
for param, errs in grouped.items():
|
|
63
|
+
# Prefer an actionable container hint when any arm produced one
|
|
64
|
+
# (dict_type/list_type); else fall back to the first raw message.
|
|
65
|
+
hint = next(
|
|
66
|
+
(_TYPE_HINTS[e["type"]] for e in errs if e["type"] in _TYPE_HINTS),
|
|
67
|
+
errs[0]["msg"],
|
|
68
|
+
)
|
|
51
69
|
parts.append(f"`{param}`: {hint}" if param else hint)
|
|
52
70
|
raise_tool_error(
|
|
53
71
|
create_validation_error(
|
|
54
72
|
"; ".join(parts) if parts else "Invalid argument types.",
|
|
55
|
-
details=", ".join(err["type"] for err in errors),
|
|
73
|
+
details=", ".join(dict.fromkeys(err["type"] for err in errors)),
|
|
56
74
|
)
|
|
57
75
|
)
|
|
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.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/dashboard_screenshot/__init__.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/dashboard_screenshot/capture.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/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.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/resources/skills-vendor/AGENTS.md
RENAMED
|
File without changes
|
{ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/resources/skills-vendor/CLAUDE.md
RENAMED
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/resources/skills-vendor/LICENSE
RENAMED
|
File without changes
|
{ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/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.dev692 → ha_mcp_dev-7.7.0.dev694}/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.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/smart_search/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/smart_search/_config.py
RENAMED
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/smart_search/_entities.py
RENAMED
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/smart_search/_overview.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/smart_search/_scenes.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/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
|
{ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/tools_config_automations.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/tools_config_dashboards.py
RENAMED
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/tools_config_scripts.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/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
|
{ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/tools/tools_voice_assistant.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/transforms/categorized_search.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/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.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp/utils/kill_signal_diagnostics.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.dev692 → ha_mcp_dev-7.7.0.dev694}/src/ha_mcp_dev.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{ha_mcp_dev-7.7.0.dev692 → ha_mcp_dev-7.7.0.dev694}/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
|