ha-mcp-dev 7.7.0.dev691__tar.gz → 7.7.0.dev693__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.dev691/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.7.0.dev693}/PKG-INFO +1 -1
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/pyproject.toml +1 -1
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/tools/tools_addons.py +5 -1
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/tools/tools_areas.py +4 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/tools/tools_calendar.py +97 -22
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/tools/tools_config_helpers.py +10 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/tools/tools_energy.py +9 -3
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/tools/tools_entities.py +4 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/tools/tools_groups.py +4 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/tools/tools_history.py +7 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/tools/tools_integrations.py +2 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/tools/tools_registry.py +2 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/tools/tools_search.py +9 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/tools/tools_service.py +8 -13
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/tools/tools_services.py +3 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/tools/tools_system.py +4 -1
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/tools/validation_middleware.py +23 -5
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/LICENSE +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/MANIFEST.in +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/README.md +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/setup.cfg +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/__init__.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/__main__.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/_pypi_marker +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/_version.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/auth/__init__.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/auth/consent_form.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/auth/provider.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/backup_manager.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/client/__init__.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/client/rest_client.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/client/supervisor_client.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/client/websocket_client.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/client/websocket_listener.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/config.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/dashboard_screenshot/__init__.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/dashboard_screenshot/capture.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/dashboard_screenshot/provision.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/errors.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/policy/__init__.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/policy/approval_queue.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/policy/evaluator.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/policy/handlers.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/policy/middleware.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/policy/model.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/policy/persistence.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/policy/value_sources.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/py.typed +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/read_only.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/yaml-only-integrations.md +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/server.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/settings.css +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/settings.js +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/settings_ui.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/smoke_test.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/tools/__init__.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/tools/auto_backup.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/tools/backup.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/tools/best_practice_checker.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/tools/config_entry_flow.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/tools/device_control.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/tools/enhanced.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/tools/helpers.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/tools/reference_validator.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/tools/registry.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/tools/smart_search/__init__.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/tools/smart_search/_base.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/tools/smart_search/_config.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/tools/smart_search/_deep.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/tools/smart_search/_entities.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/tools/smart_search/_fetch.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/tools/smart_search/_overview.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/tools/smart_search/_scenes.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/tools/smart_search/_scoring.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/tools/tools_blueprints.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/tools/tools_bug_report.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/tools/tools_camera.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/tools/tools_categories.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/tools/tools_code.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/tools/tools_config_automations.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/tools/tools_dashboard_screenshot.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/tools/tools_filesystem.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/tools/tools_hacs.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/tools/tools_labels.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/tools/tools_mcp_component.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/tools/tools_resources.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/tools/tools_themes.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/tools/tools_todo.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/tools/tools_traces.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/tools/tools_updates.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/tools/tools_utility.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/tools/tools_zones.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/tools/util_helpers.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/transforms/__init__.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/transforms/categorized_search.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/utils/__init__.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/utils/config_hash.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/utils/data_paths.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/utils/domain_handlers.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/utils/fuzzy_search.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/utils/operation_manager.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/utils/python_sandbox.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/utils/skill_loader.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp/utils/usage_logger.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/tests/__init__.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/tests/test_constants.py +0 -0
- {ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/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.dev693"
|
|
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,
|
|
@@ -204,11 +204,25 @@ class CalendarTools:
|
|
|
204
204
|
location: Annotated[
|
|
205
205
|
str | None, Field(description="Optional event location", default=None)
|
|
206
206
|
] = None,
|
|
207
|
+
rrule: Annotated[
|
|
208
|
+
str | None,
|
|
209
|
+
Field(
|
|
210
|
+
description=(
|
|
211
|
+
"Optional RFC 5545 recurrence rule, without 'RRULE:' prefix "
|
|
212
|
+
"(e.g., 'FREQ=WEEKLY;BYDAY=MO' or 'FREQ=MONTHLY;BYDAY=3SA'). "
|
|
213
|
+
"Creates a recurring event series."
|
|
214
|
+
),
|
|
215
|
+
default=None,
|
|
216
|
+
),
|
|
217
|
+
] = None,
|
|
207
218
|
) -> dict[str, Any]:
|
|
208
219
|
"""
|
|
209
220
|
Create a new event in a calendar.
|
|
210
221
|
|
|
211
|
-
Creates a
|
|
222
|
+
Creates a one-off event via the calendar.create_event service, or a
|
|
223
|
+
recurring series via the WebSocket ``calendar/event/create`` command
|
|
224
|
+
when ``rrule`` is provided (the REST service schema does not accept
|
|
225
|
+
recurrence rules).
|
|
212
226
|
|
|
213
227
|
**Parameters:**
|
|
214
228
|
- entity_id: Calendar entity ID (e.g., 'calendar.family')
|
|
@@ -217,6 +231,7 @@ class CalendarTools:
|
|
|
217
231
|
- end: Event end datetime in ISO format
|
|
218
232
|
- description: Optional event description
|
|
219
233
|
- location: Optional event location
|
|
234
|
+
- rrule: Optional RFC 5545 recurrence rule (creates a recurring series)
|
|
220
235
|
|
|
221
236
|
**Example Usage:**
|
|
222
237
|
```python
|
|
@@ -228,17 +243,21 @@ class CalendarTools:
|
|
|
228
243
|
end="2024-01-15T15:00:00"
|
|
229
244
|
)
|
|
230
245
|
|
|
231
|
-
# Create
|
|
246
|
+
# Create a recurring event (every Monday, 10 occurrences)
|
|
232
247
|
result = ha_config_set_calendar_event(
|
|
233
248
|
"calendar.work",
|
|
234
249
|
summary="Team meeting",
|
|
235
|
-
start="2024-01-
|
|
236
|
-
end="2024-01-
|
|
237
|
-
|
|
238
|
-
location="Conference Room A"
|
|
250
|
+
start="2024-01-15T10:00:00",
|
|
251
|
+
end="2024-01-15T11:00:00",
|
|
252
|
+
rrule="FREQ=WEEKLY;BYDAY=MO;COUNT=10"
|
|
239
253
|
)
|
|
240
254
|
```
|
|
241
255
|
|
|
256
|
+
**Note:**
|
|
257
|
+
Not every calendar integration supports event creation; recurring
|
|
258
|
+
events additionally require the integration to support recurrence
|
|
259
|
+
(the built-in Local Calendar does).
|
|
260
|
+
|
|
242
261
|
**Returns:**
|
|
243
262
|
- Success status and event details
|
|
244
263
|
"""
|
|
@@ -257,23 +276,71 @@ class CalendarTools:
|
|
|
257
276
|
)
|
|
258
277
|
)
|
|
259
278
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
279
|
+
if rrule:
|
|
280
|
+
# The ``calendar.create_event`` service schema has no rrule
|
|
281
|
+
# field — recurrence is only accepted by the WebSocket
|
|
282
|
+
# command ``calendar/event/create`` (see HA Core
|
|
283
|
+
# ``homeassistant/components/calendar/__init__.py``), the
|
|
284
|
+
# same split that forces the delete tool below onto the
|
|
285
|
+
# WebSocket API.
|
|
286
|
+
event: dict[str, Any] = {
|
|
287
|
+
"summary": summary,
|
|
288
|
+
"dtstart": start,
|
|
289
|
+
"dtend": end,
|
|
290
|
+
"rrule": rrule,
|
|
291
|
+
}
|
|
292
|
+
if description:
|
|
293
|
+
event["description"] = description
|
|
294
|
+
if location:
|
|
295
|
+
event["location"] = location
|
|
296
|
+
|
|
297
|
+
ws_client, conn_error = await get_connected_ws_client(
|
|
298
|
+
self._client.base_url,
|
|
299
|
+
self._client.token,
|
|
300
|
+
verify_ssl=self._client.verify_ssl,
|
|
301
|
+
)
|
|
302
|
+
if conn_error or ws_client is None:
|
|
303
|
+
raise_tool_error(
|
|
304
|
+
conn_error
|
|
305
|
+
or create_error_response(
|
|
306
|
+
ErrorCode.CONNECTION_FAILED,
|
|
307
|
+
"Failed to connect to Home Assistant WebSocket",
|
|
308
|
+
context={"entity_id": entity_id},
|
|
309
|
+
)
|
|
310
|
+
)
|
|
272
311
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
312
|
+
try:
|
|
313
|
+
result = await ws_client.send_command(
|
|
314
|
+
"calendar/event/create", entity_id=entity_id, event=event
|
|
315
|
+
)
|
|
316
|
+
finally:
|
|
317
|
+
# Guard disconnect: a transport-teardown error here would
|
|
318
|
+
# otherwise replace the original send_command exception.
|
|
319
|
+
try:
|
|
320
|
+
await ws_client.disconnect()
|
|
321
|
+
except Exception as disconnect_error:
|
|
322
|
+
logger.debug(
|
|
323
|
+
f"WebSocket disconnect after create_event for "
|
|
324
|
+
f"{entity_id}: {disconnect_error}"
|
|
325
|
+
)
|
|
326
|
+
else:
|
|
327
|
+
# Build service data
|
|
328
|
+
service_data: dict[str, Any] = {
|
|
329
|
+
"entity_id": entity_id,
|
|
330
|
+
"summary": summary,
|
|
331
|
+
"start_date_time": start,
|
|
332
|
+
"end_date_time": end,
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if description:
|
|
336
|
+
service_data["description"] = description
|
|
337
|
+
if location:
|
|
338
|
+
service_data["location"] = location
|
|
339
|
+
|
|
340
|
+
# Call the calendar.create_event service
|
|
341
|
+
result = await self._client.call_service(
|
|
342
|
+
"calendar", "create_event", service_data
|
|
343
|
+
)
|
|
277
344
|
|
|
278
345
|
return {
|
|
279
346
|
"success": True,
|
|
@@ -284,6 +351,7 @@ class CalendarTools:
|
|
|
284
351
|
"end": end,
|
|
285
352
|
"description": description,
|
|
286
353
|
"location": location,
|
|
354
|
+
"rrule": rrule,
|
|
287
355
|
},
|
|
288
356
|
"result": result,
|
|
289
357
|
"message": f"Successfully created event '{summary}' in {entity_id}",
|
|
@@ -300,6 +368,13 @@ class CalendarTools:
|
|
|
300
368
|
"Ensure end time is after start time",
|
|
301
369
|
"Some calendar integrations may be read-only",
|
|
302
370
|
]
|
|
371
|
+
if rrule:
|
|
372
|
+
suggestions.insert(
|
|
373
|
+
0,
|
|
374
|
+
"Check RRULE syntax (RFC 5545, without the 'RRULE:' prefix, "
|
|
375
|
+
"e.g. 'FREQ=WEEKLY;BYDAY=MO') and that this calendar "
|
|
376
|
+
"integration supports recurring events",
|
|
377
|
+
)
|
|
303
378
|
|
|
304
379
|
error_str = str(error)
|
|
305
380
|
if "404" in error_str or "not found" in error_str.lower():
|
{ha_mcp_dev-7.7.0.dev691 → ha_mcp_dev-7.7.0.dev693}/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=(
|