ha-mcp-dev 7.5.0.dev524__tar.gz → 7.5.0.dev525__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.5.0.dev524/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.5.0.dev525}/PKG-INFO +1 -1
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/pyproject.toml +1 -1
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/server.py +5 -5
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_config_entry_flow.py +63 -349
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_config_helpers.py +36 -35
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/LICENSE +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/MANIFEST.in +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/README.md +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/setup.cfg +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/__init__.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/__main__.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/_pypi_marker +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/_version.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/auth/__init__.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/auth/consent_form.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/auth/provider.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/client/__init__.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/client/rest_client.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/client/supervisor_client.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/client/websocket_client.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/client/websocket_listener.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/config.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/errors.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/py.typed +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/settings_ui.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/smoke_test.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/__init__.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/backup.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/best_practice_checker.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/device_control.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/enhanced.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/helpers.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/reference_validator.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/registry.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/smart_search.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_addons.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_areas.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_blueprints.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_bug_report.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_calendar.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_camera.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_categories.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_code.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_config_automations.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_energy.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_entities.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_filesystem.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_groups.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_hacs.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_history.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_integrations.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_labels.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_mcp_component.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_registry.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_resources.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_search.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_service.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_services.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_system.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_todo.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_traces.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_updates.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_utility.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_zones.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/util_helpers.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/transforms/__init__.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/transforms/categorized_search.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/utils/__init__.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/utils/config_hash.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/utils/data_paths.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/utils/domain_handlers.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/utils/fuzzy_search.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/utils/operation_manager.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/utils/python_sandbox.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/utils/usage_logger.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/tests/__init__.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/tests/test_constants.py +0 -0
- {ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/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.5.0.
|
|
7
|
+
version = "7.5.0.dev525"
|
|
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"
|
|
@@ -548,7 +548,7 @@ class HomeAssistantSmartMCPServer(EnhancedToolsMixin):
|
|
|
548
548
|
"utility_meter, derivative, statistics, trend, threshold, "
|
|
549
549
|
"filter, switch_as_x, etc.) cannot be listed through this "
|
|
550
550
|
"tool — use ha_search_entities or ha_deep_search.\n\n"
|
|
551
|
-
"For per-type schemas, see
|
|
551
|
+
"For per-type schemas and decision guidance, see "
|
|
552
552
|
"ha_get_skill_guide."
|
|
553
553
|
),
|
|
554
554
|
"ha_config_set_helper": (
|
|
@@ -558,10 +558,10 @@ class HomeAssistantSmartMCPServer(EnhancedToolsMixin):
|
|
|
558
558
|
"flow-based types (template, group, utility_meter, "
|
|
559
559
|
"derivative, statistics, trend, threshold, filter, "
|
|
560
560
|
"switch_as_x, and others).\n\n"
|
|
561
|
-
"
|
|
562
|
-
"
|
|
563
|
-
"matrix and worked examples (which helper type
|
|
564
|
-
"use case), see ha_get_skill_guide."
|
|
561
|
+
"Field set is delivered as `data_schema` on the first "
|
|
562
|
+
"validation error — submit once and self-correct. For "
|
|
563
|
+
"decision matrix and worked examples (which helper type "
|
|
564
|
+
"for which use case), see ha_get_skill_guide."
|
|
565
565
|
),
|
|
566
566
|
"ha_config_get_dashboard": (
|
|
567
567
|
"Get Home Assistant dashboard info (list mode, search "
|
{ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_config_entry_flow.py
RENAMED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Config Entry Flow API
|
|
2
|
+
Config Entry Flow API machinery for Home Assistant MCP server.
|
|
3
3
|
|
|
4
4
|
This module provides the shared machinery for creating and updating
|
|
5
5
|
config-entry-based helpers (template, group, utility_meter, etc.) via the
|
|
6
|
-
Config Entry Flow API
|
|
6
|
+
Config Entry Flow API.
|
|
7
7
|
|
|
8
8
|
The create/update entry point is the unified ha_config_set_helper tool in
|
|
9
9
|
tools_config_helpers.py, which routes to create_flow_helper / update_flow_helper
|
|
@@ -13,20 +13,11 @@ for the 15 helper types listed in FLOW_HELPER_TYPES.
|
|
|
13
13
|
import asyncio
|
|
14
14
|
import logging
|
|
15
15
|
from enum import StrEnum
|
|
16
|
-
from typing import
|
|
17
|
-
|
|
18
|
-
from fastmcp.exceptions import ToolError
|
|
19
|
-
from fastmcp.tools import tool
|
|
20
|
-
from pydantic import Field
|
|
16
|
+
from typing import Any, Literal
|
|
21
17
|
|
|
22
18
|
from ..client.rest_client import HomeAssistantAPIError
|
|
23
19
|
from ..errors import ErrorCode, create_error_response
|
|
24
|
-
from .helpers import
|
|
25
|
-
exception_to_structured_error,
|
|
26
|
-
log_tool_usage,
|
|
27
|
-
raise_tool_error,
|
|
28
|
-
register_tool_methods,
|
|
29
|
-
)
|
|
20
|
+
from .helpers import raise_tool_error
|
|
30
21
|
|
|
31
22
|
logger = logging.getLogger(__name__)
|
|
32
23
|
|
|
@@ -69,41 +60,6 @@ FLOW_HELPER_TYPES: frozenset[str] = frozenset({
|
|
|
69
60
|
"generic_hygrostat",
|
|
70
61
|
})
|
|
71
62
|
|
|
72
|
-
# Issue #1149: full set accepted by ha_get_helper_schema (15 flow + 12
|
|
73
|
-
# simple). Simple types route to a static dict in tools_config_helpers
|
|
74
|
-
# rather than starting an HA flow.
|
|
75
|
-
ALL_HELPER_TYPES = Literal[
|
|
76
|
-
# Flow helpers (mirrors SUPPORTED_HELPERS above)
|
|
77
|
-
"template",
|
|
78
|
-
"group",
|
|
79
|
-
"utility_meter",
|
|
80
|
-
"derivative",
|
|
81
|
-
"min_max",
|
|
82
|
-
"threshold",
|
|
83
|
-
"integration",
|
|
84
|
-
"statistics",
|
|
85
|
-
"trend",
|
|
86
|
-
"random",
|
|
87
|
-
"filter",
|
|
88
|
-
"tod",
|
|
89
|
-
"generic_thermostat",
|
|
90
|
-
"switch_as_x",
|
|
91
|
-
"generic_hygrostat",
|
|
92
|
-
# Simple helpers (mirrors SIMPLE_HELPER_TYPES in tools_config_helpers)
|
|
93
|
-
"input_button",
|
|
94
|
-
"input_boolean",
|
|
95
|
-
"input_select",
|
|
96
|
-
"input_number",
|
|
97
|
-
"input_text",
|
|
98
|
-
"input_datetime",
|
|
99
|
-
"counter",
|
|
100
|
-
"timer",
|
|
101
|
-
"schedule",
|
|
102
|
-
"zone",
|
|
103
|
-
"person",
|
|
104
|
-
"tag",
|
|
105
|
-
]
|
|
106
|
-
|
|
107
63
|
# Keys used to specify a menu selection — stripped before submitting form data.
|
|
108
64
|
_MENU_SELECTION_KEYS = frozenset({"group_type", "next_step_id", "menu_option"})
|
|
109
65
|
|
|
@@ -120,9 +76,8 @@ class _FlowType(StrEnum):
|
|
|
120
76
|
# Module-level flow machinery
|
|
121
77
|
#
|
|
122
78
|
# These functions are shared by the unified ha_config_set_helper tool in
|
|
123
|
-
# tools_config_helpers.py
|
|
124
|
-
#
|
|
125
|
-
# a closure-registered tool or a class method.
|
|
79
|
+
# tools_config_helpers.py. They take a client instance as an explicit
|
|
80
|
+
# parameter so the same logic can be used from any caller.
|
|
126
81
|
# ---------------------------------------------------------------------------
|
|
127
82
|
|
|
128
83
|
|
|
@@ -289,24 +244,33 @@ def _parse_flow_api_error(
|
|
|
289
244
|
}
|
|
290
245
|
|
|
291
246
|
|
|
292
|
-
async def
|
|
247
|
+
async def fetch_helper_flow_info(
|
|
293
248
|
client: Any,
|
|
294
249
|
helper_type: str | None,
|
|
295
|
-
menu_choice: str | None,
|
|
296
|
-
) ->
|
|
297
|
-
"""Best-effort
|
|
298
|
-
|
|
299
|
-
Starts a fresh introspection flow (always aborted)
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
250
|
+
menu_choice: str | None = None,
|
|
251
|
+
) -> dict[str, Any]:
|
|
252
|
+
"""Best-effort introspection of a helper's config-entry flow.
|
|
253
|
+
|
|
254
|
+
Starts a fresh introspection flow (always aborted) and returns a dict
|
|
255
|
+
with optional keys ``"schema"`` and ``"menu_options"`` so a single HA
|
|
256
|
+
round-trip serves both the schema-attach path (used by
|
|
257
|
+
``_raise_flow_api_error`` and the pre-flow validation gates in
|
|
258
|
+
``_handle_flow_helper``) and the menu-sub-types path (used when a
|
|
259
|
+
menu-rooted helper has no branch chosen yet — issue #1186).
|
|
260
|
+
|
|
261
|
+
Behaviour:
|
|
262
|
+
|
|
263
|
+
- FORM at top: ``{"schema": [...]}``
|
|
264
|
+
- MENU at top with ``menu_choice``: submits and returns the branch
|
|
265
|
+
form schema as ``{"schema": [...]}`` (no ``menu_options`` since
|
|
266
|
+
the caller already picked a branch)
|
|
267
|
+
- MENU at top without ``menu_choice``: ``{"menu_options": [...]}``
|
|
268
|
+
- any failure or unparseable shape: ``{}`` (callers branch on
|
|
269
|
+
``"schema" in info`` / ``"menu_options" in info``)
|
|
307
270
|
"""
|
|
271
|
+
info: dict[str, Any] = {}
|
|
308
272
|
if not helper_type or client is None:
|
|
309
|
-
return
|
|
273
|
+
return info
|
|
310
274
|
intro_flow_id: str | None = None
|
|
311
275
|
try:
|
|
312
276
|
flow_result = await client.start_config_flow(helper_type)
|
|
@@ -315,24 +279,38 @@ async def _fetch_data_schema_for_error_context(
|
|
|
315
279
|
|
|
316
280
|
if flow_type == _FlowType.FORM:
|
|
317
281
|
schema = flow_result.get("data_schema")
|
|
318
|
-
|
|
282
|
+
if isinstance(schema, list):
|
|
283
|
+
info["schema"] = schema
|
|
284
|
+
return info
|
|
319
285
|
|
|
320
|
-
if flow_type == _FlowType.MENU
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
286
|
+
if flow_type == _FlowType.MENU:
|
|
287
|
+
if menu_choice and intro_flow_id:
|
|
288
|
+
try:
|
|
289
|
+
step = await asyncio.wait_for(
|
|
290
|
+
client.submit_config_flow_step(
|
|
291
|
+
intro_flow_id, {"next_step_id": menu_choice}
|
|
292
|
+
),
|
|
293
|
+
timeout=10.0,
|
|
294
|
+
)
|
|
295
|
+
except Exception:
|
|
296
|
+
return info
|
|
297
|
+
if step.get("type") == _FlowType.FORM:
|
|
298
|
+
schema = step.get("data_schema")
|
|
299
|
+
if isinstance(schema, list):
|
|
300
|
+
info["schema"] = schema
|
|
301
|
+
return info
|
|
302
|
+
|
|
303
|
+
# MENU without a choice — surface the legal sub-types instead.
|
|
304
|
+
options = flow_result.get("menu_options")
|
|
305
|
+
if isinstance(options, list):
|
|
306
|
+
filtered = [opt for opt in options if isinstance(opt, str)]
|
|
307
|
+
if filtered:
|
|
308
|
+
info["menu_options"] = filtered
|
|
309
|
+
return info
|
|
310
|
+
|
|
311
|
+
return info
|
|
334
312
|
except Exception:
|
|
335
|
-
return
|
|
313
|
+
return info
|
|
336
314
|
finally:
|
|
337
315
|
if intro_flow_id:
|
|
338
316
|
try:
|
|
@@ -345,13 +323,6 @@ async def _fetch_data_schema_for_error_context(
|
|
|
345
323
|
)
|
|
346
324
|
|
|
347
325
|
|
|
348
|
-
# Public alias for use by pre-flow validation gates in tools_config_helpers
|
|
349
|
-
# (issue #1149). The underscore-prefixed original is kept to preserve the
|
|
350
|
-
# call sites already in this module; the alias avoids importing a private
|
|
351
|
-
# name across modules.
|
|
352
|
-
fetch_helper_data_schema = _fetch_data_schema_for_error_context
|
|
353
|
-
|
|
354
|
-
|
|
355
326
|
async def _raise_flow_api_error(
|
|
356
327
|
api_error: HomeAssistantAPIError,
|
|
357
328
|
*,
|
|
@@ -393,6 +364,10 @@ async def _raise_flow_api_error(
|
|
|
393
364
|
suggestions: list[str] = []
|
|
394
365
|
message: str
|
|
395
366
|
|
|
367
|
+
# Single introspection round-trip — used by both branches below.
|
|
368
|
+
info = await fetch_helper_flow_info(client, helper_type, menu_choice)
|
|
369
|
+
schema = info.get("schema")
|
|
370
|
+
|
|
396
371
|
if field_errors:
|
|
397
372
|
# Structured field errors — tell the caller which fields failed.
|
|
398
373
|
context["field_errors"] = field_errors
|
|
@@ -406,9 +381,6 @@ async def _raise_flow_api_error(
|
|
|
406
381
|
# codes — symmetric with the unstructured-error branch below.
|
|
407
382
|
# `field_errors` tells "what failed", `data_schema` tells "what's
|
|
408
383
|
# accepted"; together they're enough for self-correction.
|
|
409
|
-
schema = await _fetch_data_schema_for_error_context(
|
|
410
|
-
client, helper_type, menu_choice
|
|
411
|
-
)
|
|
412
384
|
if schema is not None:
|
|
413
385
|
context["data_schema"] = schema
|
|
414
386
|
else:
|
|
@@ -417,20 +389,12 @@ async def _raise_flow_api_error(
|
|
|
417
389
|
f"Home Assistant rejected the {helper_type or 'flow'} request "
|
|
418
390
|
f"({status_code}): {parsed['message']}"
|
|
419
391
|
)
|
|
420
|
-
schema = await _fetch_data_schema_for_error_context(
|
|
421
|
-
client, helper_type, menu_choice
|
|
422
|
-
)
|
|
423
392
|
if schema is not None:
|
|
424
393
|
context["data_schema"] = schema
|
|
425
394
|
suggestions.append(
|
|
426
395
|
"Inspect 'data_schema' in this error to see the fields HA expects, "
|
|
427
396
|
"then retry with a corrected config."
|
|
428
397
|
)
|
|
429
|
-
suggestions.append(
|
|
430
|
-
f"Call ha_get_helper_schema(helper_type='{helper_type}') for the "
|
|
431
|
-
f"full field list and selectors." if helper_type else
|
|
432
|
-
"Call ha_get_helper_schema for this helper to see required fields."
|
|
433
|
-
)
|
|
434
398
|
|
|
435
399
|
raise_tool_error(create_error_response(
|
|
436
400
|
ErrorCode.SERVICE_CALL_FAILED,
|
|
@@ -703,253 +667,3 @@ async def create_flow_helper(
|
|
|
703
667
|
"message": f"{helper_type} helper created successfully",
|
|
704
668
|
}
|
|
705
669
|
|
|
706
|
-
|
|
707
|
-
# ---------------------------------------------------------------------------
|
|
708
|
-
# Schema introspection tool (ha_get_helper_schema)
|
|
709
|
-
# ---------------------------------------------------------------------------
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
class ConfigEntryFlowTools:
|
|
713
|
-
"""Schema introspection tool for Config Entry Flow helpers."""
|
|
714
|
-
|
|
715
|
-
def __init__(self, client: Any) -> None:
|
|
716
|
-
self._client = client
|
|
717
|
-
|
|
718
|
-
@tool(
|
|
719
|
-
name="ha_get_helper_schema",
|
|
720
|
-
tags={"Helper Entities"},
|
|
721
|
-
annotations={
|
|
722
|
-
"readOnlyHint": True,
|
|
723
|
-
"title": "Get Helper Schema"
|
|
724
|
-
}
|
|
725
|
-
)
|
|
726
|
-
@log_tool_usage
|
|
727
|
-
async def ha_get_helper_schema(
|
|
728
|
-
self,
|
|
729
|
-
helper_type: Annotated[ALL_HELPER_TYPES, Field(description="Helper type")],
|
|
730
|
-
menu_option: Annotated[
|
|
731
|
-
str | None,
|
|
732
|
-
Field(
|
|
733
|
-
description=(
|
|
734
|
-
"For menu-based flow helpers (template, group): the sub-type to "
|
|
735
|
-
"inspect (e.g. 'sensor' or 'binary_sensor' for template). Omit to "
|
|
736
|
-
"see available menu options first. Ignored for simple helpers."
|
|
737
|
-
),
|
|
738
|
-
default=None,
|
|
739
|
-
),
|
|
740
|
-
] = None,
|
|
741
|
-
) -> dict[str, Any]:
|
|
742
|
-
"""Get configuration schema for a helper type.
|
|
743
|
-
|
|
744
|
-
Returns the field list and types needed to create this helper. Use
|
|
745
|
-
before ha_config_set_helper when unsure of the required `config`
|
|
746
|
-
(flow helpers) or required typed parameters (simple helpers). The
|
|
747
|
-
same schema is also auto-attached to validation-error responses
|
|
748
|
-
from ha_config_set_helper, so an explicit pre-call is optional.
|
|
749
|
-
|
|
750
|
-
Three branches:
|
|
751
|
-
|
|
752
|
-
1. Simple helpers (input_*, counter, timer, schedule, zone, person,
|
|
753
|
-
tag): static schema returned from a built-in dict.
|
|
754
|
-
ha_get_helper_schema("input_select")
|
|
755
|
-
→ {flow_type: "form", data_schema: [{name: "name", required: True, ...}, ...]}
|
|
756
|
-
|
|
757
|
-
2. Form-based flow helpers (min_max, utility_meter, statistics, ...):
|
|
758
|
-
starts a fresh HA flow, reads the schema, and aborts the flow.
|
|
759
|
-
ha_get_helper_schema("min_max")
|
|
760
|
-
→ {flow_type: "form", data_schema: [...]}
|
|
761
|
-
|
|
762
|
-
3. Menu-based flow helpers (template, group): two-call workflow.
|
|
763
|
-
# Step 1 — discover sub-types:
|
|
764
|
-
ha_get_helper_schema("template")
|
|
765
|
-
→ {flow_type: "menu", menu_options: ["sensor", "binary_sensor", ...]}
|
|
766
|
-
|
|
767
|
-
# Step 2 — inspect form fields for a sub-type:
|
|
768
|
-
ha_get_helper_schema("template", menu_option="sensor")
|
|
769
|
-
→ {flow_type: "form", menu_option: "sensor", data_schema: [...]}
|
|
770
|
-
"""
|
|
771
|
-
# Issue #1149: simple-helper dispatch — return a static schema
|
|
772
|
-
# without round-tripping HA. Lazy import keeps the existing
|
|
773
|
-
# tools_config_helpers ↔ tools_config_entry_flow boundary intact.
|
|
774
|
-
from .tools_config_helpers import (
|
|
775
|
-
SIMPLE_HELPER_TYPES,
|
|
776
|
-
get_simple_helper_schema,
|
|
777
|
-
)
|
|
778
|
-
|
|
779
|
-
if helper_type in SIMPLE_HELPER_TYPES:
|
|
780
|
-
schema = get_simple_helper_schema(helper_type)
|
|
781
|
-
# Invariant in tools_config_helpers asserts every simple type
|
|
782
|
-
# has a schema entry — None here would indicate a developer
|
|
783
|
-
# error that should fail loudly rather than mask as empty.
|
|
784
|
-
if schema is None:
|
|
785
|
-
raise_tool_error(create_error_response(
|
|
786
|
-
ErrorCode.INTERNAL_UNEXPECTED,
|
|
787
|
-
f"No simple-helper schema registered for '{helper_type}'",
|
|
788
|
-
context={"helper_type": helper_type},
|
|
789
|
-
))
|
|
790
|
-
if menu_option is not None:
|
|
791
|
-
# Simple helpers have no menu — flag the misuse rather than
|
|
792
|
-
# silently ignore the parameter.
|
|
793
|
-
raise_tool_error(create_error_response(
|
|
794
|
-
ErrorCode.VALIDATION_INVALID_PARAMETER,
|
|
795
|
-
f"menu_option is not applicable to simple helper "
|
|
796
|
-
f"'{helper_type}' (only flow helpers like 'template' "
|
|
797
|
-
f"and 'group' use menus).",
|
|
798
|
-
suggestions=["Omit menu_option for simple helpers."],
|
|
799
|
-
context={"helper_type": helper_type},
|
|
800
|
-
))
|
|
801
|
-
return {
|
|
802
|
-
"success": True,
|
|
803
|
-
"helper_type": helper_type,
|
|
804
|
-
"flow_type": _FlowType.FORM,
|
|
805
|
-
"step_id": "user",
|
|
806
|
-
"data_schema": schema,
|
|
807
|
-
"description_placeholders": {},
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
flow_id = None # Track flow_id for error context
|
|
811
|
-
try:
|
|
812
|
-
flow_result = await self._client.start_config_flow(helper_type)
|
|
813
|
-
flow_id = flow_result.get("flow_id")
|
|
814
|
-
flow_type = flow_result.get("type")
|
|
815
|
-
|
|
816
|
-
if flow_type == _FlowType.ABORT:
|
|
817
|
-
raise_tool_error(create_error_response(
|
|
818
|
-
ErrorCode.SERVICE_CALL_FAILED,
|
|
819
|
-
f"Could not get schema, flow aborted: {flow_result.get('reason')}",
|
|
820
|
-
context={"helper_type": helper_type, "details": flow_result},
|
|
821
|
-
))
|
|
822
|
-
|
|
823
|
-
if not flow_id:
|
|
824
|
-
raise_tool_error(create_error_response(
|
|
825
|
-
ErrorCode.SERVICE_CALL_FAILED,
|
|
826
|
-
"Failed to start config flow — no flow_id returned",
|
|
827
|
-
context={"helper_type": helper_type, "details": flow_result},
|
|
828
|
-
))
|
|
829
|
-
|
|
830
|
-
if menu_option is not None:
|
|
831
|
-
return await self._get_schema_with_menu_option(
|
|
832
|
-
helper_type, menu_option, flow_id, flow_result, flow_type,
|
|
833
|
-
)
|
|
834
|
-
|
|
835
|
-
return self._build_top_level_schema(helper_type, flow_result, flow_type)
|
|
836
|
-
|
|
837
|
-
except ToolError:
|
|
838
|
-
raise
|
|
839
|
-
except Exception as e:
|
|
840
|
-
logger.error(f"Error getting helper schema: {e}")
|
|
841
|
-
exception_to_structured_error(e, context={"helper_type": helper_type})
|
|
842
|
-
finally:
|
|
843
|
-
# Always abort the introspection flow to avoid leaking it in HA memory.
|
|
844
|
-
if flow_id:
|
|
845
|
-
try:
|
|
846
|
-
await self._client.abort_config_flow(flow_id)
|
|
847
|
-
except Exception as abort_err:
|
|
848
|
-
logger.warning(f"Failed to abort introspection flow {flow_id}: {abort_err}")
|
|
849
|
-
|
|
850
|
-
async def _get_schema_with_menu_option(
|
|
851
|
-
self,
|
|
852
|
-
helper_type: str,
|
|
853
|
-
menu_option: str,
|
|
854
|
-
flow_id: str,
|
|
855
|
-
flow_result: dict[str, Any],
|
|
856
|
-
flow_type: str | None,
|
|
857
|
-
) -> dict[str, Any]:
|
|
858
|
-
"""Submit a menu selection and return the resulting form schema.
|
|
859
|
-
|
|
860
|
-
Validates that the flow is a menu type, submits the menu option,
|
|
861
|
-
and returns the form schema for the selected sub-type.
|
|
862
|
-
"""
|
|
863
|
-
if flow_type != _FlowType.MENU:
|
|
864
|
-
raise_tool_error(create_error_response(
|
|
865
|
-
ErrorCode.VALIDATION_INVALID_PARAMETER,
|
|
866
|
-
f"menu_option is not applicable to '{helper_type}' "
|
|
867
|
-
f"(flow type is '{flow_type}', not 'menu')",
|
|
868
|
-
suggestions=["Omit menu_option for form-based helpers"],
|
|
869
|
-
context={"helper_type": helper_type, "flow_type": flow_type},
|
|
870
|
-
))
|
|
871
|
-
|
|
872
|
-
step_result = await self._client.submit_config_flow_step(
|
|
873
|
-
flow_id, {"next_step_id": menu_option}
|
|
874
|
-
)
|
|
875
|
-
sub_flow_type = step_result.get("type")
|
|
876
|
-
|
|
877
|
-
if sub_flow_type == _FlowType.ABORT:
|
|
878
|
-
raise_tool_error(create_error_response(
|
|
879
|
-
ErrorCode.VALIDATION_INVALID_PARAMETER,
|
|
880
|
-
f"menu_option '{menu_option}' is not valid for '{helper_type}': "
|
|
881
|
-
f"{step_result.get('reason')}",
|
|
882
|
-
suggestions=[f"Valid options: {flow_result.get('menu_options', [])}"],
|
|
883
|
-
context={
|
|
884
|
-
"helper_type": helper_type,
|
|
885
|
-
"menu_option": menu_option,
|
|
886
|
-
"details": step_result,
|
|
887
|
-
},
|
|
888
|
-
))
|
|
889
|
-
|
|
890
|
-
if sub_flow_type != _FlowType.FORM:
|
|
891
|
-
raise_tool_error(create_error_response(
|
|
892
|
-
ErrorCode.INTERNAL_UNEXPECTED,
|
|
893
|
-
f"Unexpected sub-flow type '{sub_flow_type}' after menu selection",
|
|
894
|
-
context={
|
|
895
|
-
"helper_type": helper_type,
|
|
896
|
-
"menu_option": menu_option,
|
|
897
|
-
"details": step_result,
|
|
898
|
-
},
|
|
899
|
-
))
|
|
900
|
-
|
|
901
|
-
return {
|
|
902
|
-
"success": True,
|
|
903
|
-
"helper_type": helper_type,
|
|
904
|
-
"flow_type": _FlowType.FORM,
|
|
905
|
-
"menu_option": menu_option,
|
|
906
|
-
"step_id": step_result.get("step_id"),
|
|
907
|
-
"data_schema": step_result.get("data_schema", []),
|
|
908
|
-
"description_placeholders": step_result.get("description_placeholders", {}),
|
|
909
|
-
}
|
|
910
|
-
|
|
911
|
-
@staticmethod
|
|
912
|
-
def _build_top_level_schema(
|
|
913
|
-
helper_type: str,
|
|
914
|
-
flow_result: dict[str, Any],
|
|
915
|
-
flow_type: str | None,
|
|
916
|
-
) -> dict[str, Any]:
|
|
917
|
-
"""Build the top-level schema response for a form or menu flow."""
|
|
918
|
-
if flow_type == _FlowType.FORM:
|
|
919
|
-
return {
|
|
920
|
-
"success": True,
|
|
921
|
-
"helper_type": helper_type,
|
|
922
|
-
"flow_type": _FlowType.FORM,
|
|
923
|
-
"step_id": flow_result.get("step_id"),
|
|
924
|
-
"data_schema": flow_result.get("data_schema", []),
|
|
925
|
-
"description_placeholders": flow_result.get(
|
|
926
|
-
"description_placeholders", {}
|
|
927
|
-
),
|
|
928
|
-
}
|
|
929
|
-
if flow_type == _FlowType.MENU:
|
|
930
|
-
return {
|
|
931
|
-
"success": True,
|
|
932
|
-
"helper_type": helper_type,
|
|
933
|
-
"flow_type": _FlowType.MENU,
|
|
934
|
-
"step_id": flow_result.get("step_id"),
|
|
935
|
-
"menu_options": flow_result.get("menu_options", []),
|
|
936
|
-
"description_placeholders": flow_result.get(
|
|
937
|
-
"description_placeholders", {}
|
|
938
|
-
),
|
|
939
|
-
"note": (
|
|
940
|
-
"This helper requires selecting from a menu first. "
|
|
941
|
-
"Include 'group_type' (or 'next_step_id') in your config "
|
|
942
|
-
"when calling ha_config_set_helper. "
|
|
943
|
-
"Call ha_get_helper_schema with menu_option=<sub-type> to inspect form fields."
|
|
944
|
-
),
|
|
945
|
-
}
|
|
946
|
-
raise_tool_error(create_error_response(
|
|
947
|
-
ErrorCode.INTERNAL_UNEXPECTED,
|
|
948
|
-
f"Unexpected flow type: {flow_type}",
|
|
949
|
-
context={"helper_type": helper_type, "details": flow_result},
|
|
950
|
-
))
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
def register_config_entry_flow_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
|
|
954
|
-
"""Register Config Entry Flow API tools with the MCP server."""
|
|
955
|
-
register_tool_methods(mcp, ConfigEntryFlowTools(client))
|
{ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_config_helpers.py
RENAMED
|
@@ -20,7 +20,7 @@ from .helpers import exception_to_structured_error, log_tool_usage, raise_tool_e
|
|
|
20
20
|
from .tools_config_entry_flow import (
|
|
21
21
|
FLOW_HELPER_TYPES,
|
|
22
22
|
create_flow_helper,
|
|
23
|
-
|
|
23
|
+
fetch_helper_flow_info,
|
|
24
24
|
get_user_step_field_names,
|
|
25
25
|
update_flow_helper,
|
|
26
26
|
)
|
|
@@ -141,11 +141,9 @@ class _HelperFieldSpec(_HelperFieldSpecBase, total=False):
|
|
|
141
141
|
|
|
142
142
|
# Per-simple-type field schemas — list-of-dicts shape mirroring HA's flow
|
|
143
143
|
# ``data_schema`` so callers can iterate one shape regardless of helper kind.
|
|
144
|
-
# Consumed by
|
|
145
|
-
#
|
|
146
|
-
#
|
|
147
|
-
# ``context["data_schema"]`` so the LLM sees field shape inline with the
|
|
148
|
-
# 4xx that just blocked it).
|
|
144
|
+
# Consumed by ``ha_config_set_helper`` validation errors (relevant entry
|
|
145
|
+
# attached to ``context["data_schema"]`` so the LLM sees field shape inline
|
|
146
|
+
# with the 4xx that just blocked it).
|
|
149
147
|
#
|
|
150
148
|
# Each field-spec dict carries:
|
|
151
149
|
# - ``name`` : argument key on ``ha_config_set_helper``.
|
|
@@ -541,7 +539,7 @@ def get_simple_helper_schema(helper_type: str) -> list[_HelperFieldSpec] | None:
|
|
|
541
539
|
Callers attach the result to validation-error context as ``data_schema``
|
|
542
540
|
so the LLM sees field shape inline with a 4xx response, matching the
|
|
543
541
|
auto-attach pattern already in use for flow helpers (see
|
|
544
|
-
``
|
|
542
|
+
``fetch_helper_flow_info`` in ``tools_config_entry_flow``).
|
|
545
543
|
Returns ``None`` for any helper_type not in ``SIMPLE_HELPER_SCHEMAS``,
|
|
546
544
|
so callers can write a single uniform ``if schema is not None: …`` branch.
|
|
547
545
|
"""
|
|
@@ -568,13 +566,14 @@ def _simple_helper_error_context(
|
|
|
568
566
|
|
|
569
567
|
|
|
570
568
|
# Flow helper types whose top-level config-flow step is a MENU rather than a
|
|
571
|
-
# FORM — for these, ``
|
|
569
|
+
# FORM — for these, ``fetch_helper_flow_info`` cannot return a ``data_schema``
|
|
572
570
|
# without a menu choice (``next_step_id`` / ``group_type`` / ``menu_option``).
|
|
573
571
|
# The pre-flow gates in ``_handle_flow_helper`` use this set to surface a
|
|
574
|
-
# ``data_schema_unavailable_reason: "menu_helper_requires_branch"`` marker
|
|
575
|
-
# the
|
|
576
|
-
#
|
|
577
|
-
# only sharpens the signal, missing entries fall back
|
|
572
|
+
# ``data_schema_unavailable_reason: "menu_helper_requires_branch"`` marker
|
|
573
|
+
# alongside the legal sub-types under ``menu_options`` so the LLM can pick
|
|
574
|
+
# a branch on the next try without a separate discovery round-trip. Hint
|
|
575
|
+
# set — extending it only sharpens the signal, missing entries fall back
|
|
576
|
+
# to silent ``None``.
|
|
578
577
|
_MENU_ROOTED_FLOW_HELPER_TYPES: frozenset[str] = frozenset({"template", "group"})
|
|
579
578
|
|
|
580
579
|
# Keys callers may pass inside ``config`` to select a menu branch — mirrors
|
|
@@ -625,13 +624,13 @@ async def _flow_helper_error_context(
|
|
|
625
624
|
For menu-rooted helpers (``template``, ``group``) without a derivable
|
|
626
625
|
``menu_choice``, the schema can't be fetched without picking a branch;
|
|
627
626
|
a ``data_schema_unavailable_reason: "menu_helper_requires_branch"``
|
|
628
|
-
marker is added instead
|
|
629
|
-
``
|
|
630
|
-
the
|
|
627
|
+
marker is added instead, along with the legal sub-types under
|
|
628
|
+
``menu_options`` (issue #1186), so the caller can pick a branch on
|
|
629
|
+
the next try without a separate discovery round-trip.
|
|
631
630
|
"""
|
|
632
631
|
context: dict[str, Any] = {"helper_type": helper_type}
|
|
633
632
|
try:
|
|
634
|
-
|
|
633
|
+
info = await fetch_helper_flow_info(
|
|
635
634
|
client, helper_type, menu_choice=menu_choice
|
|
636
635
|
)
|
|
637
636
|
except Exception as e:
|
|
@@ -640,17 +639,19 @@ async def _flow_helper_error_context(
|
|
|
640
639
|
# disappear silently — this PR raises the call rate by 5 sites
|
|
641
640
|
# and the swallow needs an audit-trail entry.
|
|
642
641
|
logger.debug(
|
|
643
|
-
"_flow_helper_error_context:
|
|
642
|
+
"_flow_helper_error_context: flow-info fetch failed for "
|
|
644
643
|
"helper_type=%r menu_choice=%r: %s",
|
|
645
644
|
helper_type,
|
|
646
645
|
menu_choice,
|
|
647
646
|
e,
|
|
648
647
|
)
|
|
649
|
-
|
|
650
|
-
if schema
|
|
651
|
-
context["data_schema"] = schema
|
|
648
|
+
info = {}
|
|
649
|
+
if "schema" in info:
|
|
650
|
+
context["data_schema"] = info["schema"]
|
|
652
651
|
elif helper_type in _MENU_ROOTED_FLOW_HELPER_TYPES and not menu_choice:
|
|
653
652
|
context["data_schema_unavailable_reason"] = "menu_helper_requires_branch"
|
|
653
|
+
if "menu_options" in info:
|
|
654
|
+
context["menu_options"] = info["menu_options"]
|
|
654
655
|
context.update(extra)
|
|
655
656
|
return context
|
|
656
657
|
|
|
@@ -723,7 +724,7 @@ def _validate_applicable_params(
|
|
|
723
724
|
inapplicable.sort()
|
|
724
725
|
if helper_type in FLOW_HELPER_TYPES:
|
|
725
726
|
applicable_msg = (
|
|
726
|
-
"config (
|
|
727
|
+
"config (see data_schema on a validation error for the field set), "
|
|
727
728
|
"name, helper_id, area_id, labels, category, wait"
|
|
728
729
|
)
|
|
729
730
|
else:
|
|
@@ -747,8 +748,8 @@ def _validate_applicable_params(
|
|
|
747
748
|
if helper_type in FLOW_HELPER_TYPES:
|
|
748
749
|
suggestions.append(
|
|
749
750
|
f"For flow-based helpers like {helper_type!r}, type-specific config "
|
|
750
|
-
"goes inside the `config` dict; the
|
|
751
|
-
|
|
751
|
+
"goes inside the `config` dict; submit with the wrong shape once "
|
|
752
|
+
"and the validation error returns the `data_schema` for that helper."
|
|
752
753
|
)
|
|
753
754
|
|
|
754
755
|
raise_tool_error(
|
|
@@ -2226,7 +2227,7 @@ def register_config_helper_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
|
|
|
2226
2227
|
"integration, statistics, trend, random, filter, tod, "
|
|
2227
2228
|
"generic_thermostat, switch_as_x, generic_hygrostat). "
|
|
2228
2229
|
"Accepts JSON string or dict. Ignored for simple helper types. "
|
|
2229
|
-
"
|
|
2230
|
+
"Field set is delivered as data_schema on the first validation error."
|
|
2230
2231
|
),
|
|
2231
2232
|
default=None,
|
|
2232
2233
|
),
|
|
@@ -2277,13 +2278,14 @@ def register_config_helper_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
|
|
|
2277
2278
|
without it the tool falls back to the implicit `helper_id`-presence
|
|
2278
2279
|
discriminator.
|
|
2279
2280
|
- For flow-based helpers, config keys not declared by any step's
|
|
2280
|
-
data_schema are silently ignored by HA;
|
|
2281
|
-
`
|
|
2281
|
+
data_schema are silently ignored by HA; submit once and the
|
|
2282
|
+
validation error returns the `data_schema` for that helper so
|
|
2283
|
+
subsequent calls use the correct field names.
|
|
2282
2284
|
- Validation errors raised by this tool carry the helper's
|
|
2283
|
-
`data_schema` in the response context
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2285
|
+
`data_schema` in the response context (and `menu_options` for
|
|
2286
|
+
menu-rooted helpers like `template`/`group` when no sub-type is
|
|
2287
|
+
chosen yet) so a follow-up call can self-correct without a
|
|
2288
|
+
separate schema-discovery round-trip.
|
|
2287
2289
|
|
|
2288
2290
|
EXAMPLES (menu-based types + tod, where first-call payload is non-obvious):
|
|
2289
2291
|
- template sensor:
|
|
@@ -2299,11 +2301,10 @@ def register_config_helper_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
|
|
|
2299
2301
|
ha_config_set_helper(helper_type="tod", name="Quiet Hours",
|
|
2300
2302
|
config={"after_time": "22:00:00", "before_time": "07:00:00"})
|
|
2301
2303
|
|
|
2302
|
-
For
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
`timer`, and `schedule` with worked examples and a decision matrix.
|
|
2304
|
+
For helper-design guidance (when to pick which helper type, YAML
|
|
2305
|
+
examples, per-type field tables), use ha_get_skill_guide — the
|
|
2306
|
+
skill's `helper-selection.md` reference covers all 27 helper types
|
|
2307
|
+
with worked examples and a decision matrix.
|
|
2307
2308
|
"""
|
|
2308
2309
|
try:
|
|
2309
2310
|
# Determine if this is a create or update — set early so the
|
|
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
|
|
File without changes
|
{ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/resources/skills-vendor/AGENTS.md
RENAMED
|
File without changes
|
{ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/resources/skills-vendor/CLAUDE.md
RENAMED
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/resources/skills-vendor/LICENSE
RENAMED
|
File without changes
|
{ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/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
|
{ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/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
|
|
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.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_config_automations.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_config_dashboards.py
RENAMED
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_config_scripts.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
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/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.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/transforms/categorized_search.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/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.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/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.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp_dev.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{ha_mcp_dev-7.5.0.dev524 → ha_mcp_dev-7.5.0.dev525}/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
|