ha-mcp-dev 7.5.0.dev523__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.
Files changed (111) hide show
  1. {ha_mcp_dev-7.5.0.dev523/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.5.0.dev525}/PKG-INFO +1 -1
  2. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/pyproject.toml +1 -1
  3. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/server.py +5 -5
  4. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_config_entry_flow.py +63 -349
  5. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_config_helpers.py +36 -35
  6. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
  7. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/LICENSE +0 -0
  8. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/MANIFEST.in +0 -0
  9. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/README.md +0 -0
  10. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/setup.cfg +0 -0
  11. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/__init__.py +0 -0
  12. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/__main__.py +0 -0
  13. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/_pypi_marker +0 -0
  14. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/_version.py +0 -0
  15. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/auth/__init__.py +0 -0
  16. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/auth/consent_form.py +0 -0
  17. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/auth/provider.py +0 -0
  18. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/client/__init__.py +0 -0
  19. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/client/rest_client.py +0 -0
  20. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/client/supervisor_client.py +0 -0
  21. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/client/websocket_client.py +0 -0
  22. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/client/websocket_listener.py +0 -0
  23. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/config.py +0 -0
  24. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/errors.py +0 -0
  25. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/py.typed +0 -0
  26. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
  27. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
  28. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
  29. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
  30. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
  31. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
  32. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
  33. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
  34. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
  35. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
  36. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
  37. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
  38. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
  39. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
  40. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
  41. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
  42. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
  43. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
  44. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
  45. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
  46. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/settings_ui.py +0 -0
  47. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/smoke_test.py +0 -0
  48. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/__init__.py +0 -0
  49. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/backup.py +0 -0
  50. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/best_practice_checker.py +0 -0
  51. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/device_control.py +0 -0
  52. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/enhanced.py +0 -0
  53. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/helpers.py +0 -0
  54. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/reference_validator.py +0 -0
  55. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/registry.py +0 -0
  56. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/smart_search.py +0 -0
  57. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_addons.py +0 -0
  58. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_areas.py +0 -0
  59. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_blueprints.py +0 -0
  60. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_bug_report.py +0 -0
  61. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_calendar.py +0 -0
  62. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_camera.py +0 -0
  63. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_categories.py +0 -0
  64. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_code.py +0 -0
  65. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_config_automations.py +0 -0
  66. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
  67. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
  68. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
  69. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_energy.py +0 -0
  70. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_entities.py +0 -0
  71. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_filesystem.py +0 -0
  72. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_groups.py +0 -0
  73. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_hacs.py +0 -0
  74. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_history.py +0 -0
  75. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_integrations.py +0 -0
  76. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_labels.py +0 -0
  77. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_mcp_component.py +0 -0
  78. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_registry.py +0 -0
  79. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_resources.py +0 -0
  80. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_search.py +0 -0
  81. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_service.py +0 -0
  82. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_services.py +0 -0
  83. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_system.py +0 -0
  84. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_todo.py +0 -0
  85. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_traces.py +0 -0
  86. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_updates.py +0 -0
  87. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_utility.py +0 -0
  88. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
  89. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
  90. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/tools_zones.py +0 -0
  91. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/tools/util_helpers.py +0 -0
  92. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/transforms/__init__.py +0 -0
  93. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/transforms/categorized_search.py +0 -0
  94. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
  95. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/utils/__init__.py +0 -0
  96. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/utils/config_hash.py +0 -0
  97. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/utils/data_paths.py +0 -0
  98. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/utils/domain_handlers.py +0 -0
  99. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/utils/fuzzy_search.py +0 -0
  100. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
  101. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/utils/operation_manager.py +0 -0
  102. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/utils/python_sandbox.py +0 -0
  103. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp/utils/usage_logger.py +0 -0
  104. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
  105. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
  106. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
  107. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
  108. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
  109. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/tests/__init__.py +0 -0
  110. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/tests/test_constants.py +0 -0
  111. {ha_mcp_dev-7.5.0.dev523 → ha_mcp_dev-7.5.0.dev525}/tests/test_env_manager.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ha-mcp-dev
3
- Version: 7.5.0.dev523
3
+ Version: 7.5.0.dev525
4
4
  Summary: Home Assistant MCP Server - Complete control of Home Assistant through MCP
5
5
  Author-email: Julien <github@qc-h.net>
6
6
  License: MIT
@@ -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.dev523"
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 ha_get_helper_schema and "
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
- "For per-type config schemas, call "
562
- "ha_get_helper_schema(helper_type) first. For decision "
563
- "matrix and worked examples (which helper type for which "
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 "
@@ -1,9 +1,9 @@
1
1
  """
2
- Config Entry Flow API tools for Home Assistant MCP server.
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, plus the ha_get_helper_schema tool.
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 Annotated, Any, Literal
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 and by ha_get_helper_schema below. They take a
124
- # client instance as an explicit parameter so they can be called from either
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 _fetch_data_schema_for_error_context(
247
+ async def fetch_helper_flow_info(
293
248
  client: Any,
294
249
  helper_type: str | None,
295
- menu_choice: str | None,
296
- ) -> list[Any] | None:
297
- """Best-effort fetch of the helper's data_schema for error context.
298
-
299
- Starts a fresh introspection flow (always aborted), and returns the
300
- user step's ``data_schema`` so the LLM has something concrete to react
301
- to when HA's error body is unstructured. Returns ``None`` on any
302
- failure or when the helper is menu-based without a chosen branch.
303
-
304
- Public alias ``fetch_helper_data_schema`` is exported below for the
305
- pre-flow validation gates in ``_handle_flow_helper`` (issue #1149) —
306
- they need the same best-effort fetch but live in another module.
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 None
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
- return schema if isinstance(schema, list) else None
282
+ if isinstance(schema, list):
283
+ info["schema"] = schema
284
+ return info
319
285
 
320
- if flow_type == _FlowType.MENU and menu_choice and intro_flow_id:
321
- try:
322
- step = await asyncio.wait_for(
323
- client.submit_config_flow_step(
324
- intro_flow_id, {"next_step_id": menu_choice}
325
- ),
326
- timeout=10.0,
327
- )
328
- except Exception:
329
- return None
330
- if step.get("type") == _FlowType.FORM:
331
- schema = step.get("data_schema")
332
- return schema if isinstance(schema, list) else None
333
- return None
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 None
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))
@@ -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
- fetch_helper_data_schema,
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
- # - ``ha_get_helper_schema`` (returned verbatim for simple types).
146
- # - ``ha_config_set_helper`` validation errors (relevant entry attached to
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
- ``_fetch_data_schema_for_error_context`` in ``tools_config_entry_flow``).
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, ``fetch_helper_data_schema`` cannot return a ``data_schema``
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 so
575
- # the LLM gets a non-silent signal to call
576
- # ``ha_get_helper_schema(<type>, menu_option=...)``. Hint set extending it
577
- # only sharpens the signal, missing entries fall back to silent ``None``.
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 so the LLM gets a non-silent signal to call
629
- ``ha_get_helper_schema(<type>, menu_option=...)`` rather than reading
630
- the absence of ``data_schema`` as "no schema exists".
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
- schema = await fetch_helper_data_schema(
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: schema fetch failed for "
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
- schema = None
650
- if schema is not None:
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 (use ha_get_helper_schema to see fields), "
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 per-type fields are discoverable "
751
- f"via ha_get_helper_schema(helper_type='{helper_type}')."
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
- "Use ha_get_helper_schema(helper_type) to discover required fields."
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; verify field names with
2281
- `ha_get_helper_schema` before relying on them.
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 so a follow-up call can
2284
- self-correct. Calling `ha_get_helper_schema(helper_type)` ahead of
2285
- time is therefore optional the schema is delivered alongside the
2286
- first 4xx if you call without it.
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 complex schemas and per-type parameter details, use ha_get_helper_schema.
2303
- For broader helper-design guidance (when to pick which helper type, YAML
2304
- examples), use ha_get_skill_guide the skill's
2305
- `helper-selection.md` reference covers the `input_*` family, `counter`,
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ha-mcp-dev
3
- Version: 7.5.0.dev523
3
+ Version: 7.5.0.dev525
4
4
  Summary: Home Assistant MCP Server - Complete control of Home Assistant through MCP
5
5
  Author-email: Julien <github@qc-h.net>
6
6
  License: MIT