ha-mcp-dev 7.5.0.dev517__tar.gz → 7.5.0.dev518__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.dev517/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.5.0.dev518}/PKG-INFO +1 -1
  2. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/pyproject.toml +1 -1
  3. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/tools/tools_config_helpers.py +197 -64
  4. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
  5. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/LICENSE +0 -0
  6. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/MANIFEST.in +0 -0
  7. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/README.md +0 -0
  8. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/setup.cfg +0 -0
  9. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/__init__.py +0 -0
  10. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/__main__.py +0 -0
  11. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/_pypi_marker +0 -0
  12. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/_version.py +0 -0
  13. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/auth/__init__.py +0 -0
  14. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/auth/consent_form.py +0 -0
  15. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/auth/provider.py +0 -0
  16. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/client/__init__.py +0 -0
  17. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/client/rest_client.py +0 -0
  18. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/client/supervisor_client.py +0 -0
  19. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/client/websocket_client.py +0 -0
  20. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/client/websocket_listener.py +0 -0
  21. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/config.py +0 -0
  22. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/errors.py +0 -0
  23. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/py.typed +0 -0
  24. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
  25. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
  26. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
  27. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
  28. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
  29. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
  30. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
  31. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
  32. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
  33. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
  34. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
  35. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
  36. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
  37. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
  38. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
  39. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
  40. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
  41. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
  42. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
  43. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
  44. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/server.py +0 -0
  45. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/settings_ui.py +0 -0
  46. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/smoke_test.py +0 -0
  47. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/tools/__init__.py +0 -0
  48. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/tools/backup.py +0 -0
  49. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/tools/best_practice_checker.py +0 -0
  50. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/tools/device_control.py +0 -0
  51. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/tools/enhanced.py +0 -0
  52. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/tools/helpers.py +0 -0
  53. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/tools/reference_validator.py +0 -0
  54. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/tools/registry.py +0 -0
  55. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/tools/smart_search.py +0 -0
  56. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/tools/tools_addons.py +0 -0
  57. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/tools/tools_areas.py +0 -0
  58. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/tools/tools_blueprints.py +0 -0
  59. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/tools/tools_bug_report.py +0 -0
  60. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/tools/tools_calendar.py +0 -0
  61. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/tools/tools_camera.py +0 -0
  62. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/tools/tools_categories.py +0 -0
  63. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/tools/tools_code.py +0 -0
  64. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/tools/tools_config_automations.py +0 -0
  65. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
  66. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/tools/tools_config_entry_flow.py +0 -0
  67. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
  68. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
  69. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/tools/tools_energy.py +0 -0
  70. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/tools/tools_entities.py +0 -0
  71. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/tools/tools_filesystem.py +0 -0
  72. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/tools/tools_groups.py +0 -0
  73. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/tools/tools_hacs.py +0 -0
  74. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/tools/tools_history.py +0 -0
  75. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/tools/tools_integrations.py +0 -0
  76. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/tools/tools_labels.py +0 -0
  77. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/tools/tools_mcp_component.py +0 -0
  78. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/tools/tools_registry.py +0 -0
  79. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/tools/tools_resources.py +0 -0
  80. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/tools/tools_search.py +0 -0
  81. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/tools/tools_service.py +0 -0
  82. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/tools/tools_services.py +0 -0
  83. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/tools/tools_system.py +0 -0
  84. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/tools/tools_todo.py +0 -0
  85. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/tools/tools_traces.py +0 -0
  86. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/tools/tools_updates.py +0 -0
  87. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/tools/tools_utility.py +0 -0
  88. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
  89. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
  90. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/tools/tools_zones.py +0 -0
  91. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/tools/util_helpers.py +0 -0
  92. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/transforms/__init__.py +0 -0
  93. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/transforms/categorized_search.py +0 -0
  94. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
  95. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/utils/__init__.py +0 -0
  96. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/utils/config_hash.py +0 -0
  97. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/utils/data_paths.py +0 -0
  98. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/utils/domain_handlers.py +0 -0
  99. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/utils/fuzzy_search.py +0 -0
  100. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
  101. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/utils/operation_manager.py +0 -0
  102. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/utils/python_sandbox.py +0 -0
  103. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp/utils/usage_logger.py +0 -0
  104. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
  105. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
  106. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
  107. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
  108. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
  109. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/tests/__init__.py +0 -0
  110. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/tests/test_constants.py +0 -0
  111. {ha_mcp_dev-7.5.0.dev517 → ha_mcp_dev-7.5.0.dev518}/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.dev517
3
+ Version: 7.5.0.dev518
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.dev517"
7
+ version = "7.5.0.dev518"
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"
@@ -1472,6 +1472,69 @@ async def _apply_registry_updates_to_entity(
1472
1472
  return applied
1473
1473
 
1474
1474
 
1475
+ class HelperResponse(TypedDict, total=False):
1476
+ """Uniform response contract for ``ha_config_set_helper`` (issue #1293).
1477
+
1478
+ Documents the legal key set across all three branches (create, update,
1479
+ flow). ``total=False`` because per-branch fields (entity_id, flow extras,
1480
+ warnings) are conditional. Consumed by ``_helper_response`` below — all
1481
+ return literals in this module funnel through that builder so the shape
1482
+ has a single point of construction.
1483
+ """
1484
+
1485
+ success: bool
1486
+ action: str # "create" | "update"
1487
+ helper_type: str
1488
+ data: dict[str, Any]
1489
+ entity_id: str # absent on flow branch (use entity_ids[] for multi-entity)
1490
+ message: str | None
1491
+ warnings: list[str] # omitted when empty
1492
+ # Flow-helper convenience accessors (only set on the flow branch).
1493
+ method: str
1494
+ entry_id: str | None
1495
+ title: str | None
1496
+ updated: bool
1497
+ entity_ids: list[str]
1498
+ area_id: str | None
1499
+ labels: list[str]
1500
+ category: str
1501
+ applied: list[dict[str, Any]]
1502
+
1503
+
1504
+ def _helper_response(
1505
+ action: str,
1506
+ helper_type: str,
1507
+ *,
1508
+ data: dict[str, Any],
1509
+ entity_id: str | None = None,
1510
+ message: str | None = None,
1511
+ warnings: list[str] | None = None,
1512
+ **extras: Any,
1513
+ ) -> dict[str, Any]:
1514
+ """Single construction point for the ``ha_config_set_helper`` response.
1515
+
1516
+ Enforces the uniform shape from issue #1293: ``success`` → ``action`` →
1517
+ ``helper_type`` → ``data`` → ``entity_id`` (when present) → ``message`` →
1518
+ flow-helper extras → ``warnings`` (only when non-empty). Returning
1519
+ ``dict[str, Any]`` rather than ``HelperResponse`` keeps the call sites
1520
+ free of mypy gymnastics around the dynamic ``**extras`` keys; the
1521
+ TypedDict serves as the readable contract anchor instead.
1522
+ """
1523
+ resp: dict[str, Any] = {
1524
+ "success": True,
1525
+ "action": action,
1526
+ "helper_type": helper_type,
1527
+ "data": data,
1528
+ }
1529
+ if entity_id is not None:
1530
+ resp["entity_id"] = entity_id
1531
+ resp["message"] = message
1532
+ resp.update(extras)
1533
+ if warnings:
1534
+ resp["warnings"] = warnings
1535
+ return resp
1536
+
1537
+
1475
1538
  async def _handle_flow_helper(
1476
1539
  client: Any,
1477
1540
  helper_type: str,
@@ -1625,18 +1688,15 @@ async def _handle_flow_helper(
1625
1688
  helper_id, # type: ignore[arg-type]
1626
1689
  )
1627
1690
 
1691
+ # Cache the flow_result keys read multiple times below (issue #1293
1692
+ # follow-up: reduces three .get() lookups for entry_id and two for title
1693
+ # to one each, and makes the post-flow logic readable as a sequence of
1694
+ # named values rather than repeated dict access). ``.get()`` is kept —
1695
+ # ``create_flow_helper`` propagates HA's optional entry_id via ``.get()``
1696
+ # too (tools_config_entry_flow.py:L700), so a missing key must surface as
1697
+ # None for the ``if entry_id:`` guard below, not raise KeyError.
1628
1698
  entry_id = flow_result.get("entry_id")
1629
- result: dict[str, Any] = {
1630
- "success": True,
1631
- "action": action,
1632
- "helper_type": helper_type,
1633
- "method": "config_flow",
1634
- "entry_id": entry_id,
1635
- "title": flow_result.get("title"),
1636
- "message": flow_result.get("message"),
1637
- }
1638
- if action == "update":
1639
- result["updated"] = True
1699
+ title = flow_result.get("title")
1640
1700
 
1641
1701
  # Resolve all entities for this config entry (multi-entity helpers handled naturally).
1642
1702
  # For create with wait=True, poll briefly for at least one entity to appear —
@@ -1680,7 +1740,22 @@ async def _handle_flow_helper(
1680
1740
  else:
1681
1741
  entities = await _get_entities_for_config_entry(client, entry_id, warnings)
1682
1742
  entity_ids = [e["entity_id"] for e in entities if e.get("entity_id")]
1683
- result["entity_ids"] = entity_ids
1743
+
1744
+ # ``data`` mirrors the HA flow_result payload for cross-action uniformity
1745
+ # with the create/update branches (issue #1293). ``entry_id`` and ``title``
1746
+ # also stay flat as convenience accessors — they are the primary identifiers
1747
+ # callers reach for, and remain heavily used by per-action metadata
1748
+ # consumers throughout the codebase. Per-entity registry-write outcomes
1749
+ # live in the ``applied`` flat array, not nested in ``data``, because one
1750
+ # flow can yield N entities with different per-entity results.
1751
+ extras: dict[str, Any] = {
1752
+ "method": "config_flow",
1753
+ "entry_id": entry_id,
1754
+ "title": title,
1755
+ "entity_ids": entity_ids,
1756
+ }
1757
+ if action == "update":
1758
+ extras["updated"] = True
1684
1759
 
1685
1760
  # Apply registry updates (area_id / labels / category) to every entity.
1686
1761
  # Use `is not None` so an explicit empty value (area_id="" or labels=[])
@@ -1703,17 +1778,21 @@ async def _handle_flow_helper(
1703
1778
  )
1704
1779
  )
1705
1780
  if area_id is not None:
1706
- result["area_id"] = area_id if area_id else None
1781
+ extras["area_id"] = area_id if area_id else None
1707
1782
  if labels_list is not None:
1708
- result["labels"] = labels_list
1783
+ extras["labels"] = labels_list
1709
1784
  if category:
1710
- result["category"] = category
1711
- result["applied"] = applied_per_entity
1712
-
1713
- if warnings:
1714
- result["warnings"] = warnings
1715
-
1716
- return result
1785
+ extras["category"] = category
1786
+ extras["applied"] = applied_per_entity
1787
+
1788
+ return _helper_response(
1789
+ action,
1790
+ helper_type,
1791
+ data={"entry_id": entry_id, "title": title},
1792
+ message=flow_result.get("message"),
1793
+ warnings=warnings,
1794
+ **extras,
1795
+ )
1717
1796
 
1718
1797
 
1719
1798
  def _format_schedule_days(
@@ -2660,6 +2739,12 @@ def register_config_helper_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
2660
2739
  if not entity_id and helper_data.get("id"):
2661
2740
  entity_id = f"{helper_type}.{helper_data['id']}"
2662
2741
 
2742
+ # Issue #1293: collect warnings in a top-level list rather
2743
+ # than nest them in the payload dict. Aligns this branch
2744
+ # with the flow-helper path and lets callers do
2745
+ # ``result.get("warnings", [])`` uniformly.
2746
+ warnings: list[str] = []
2747
+
2663
2748
  # Wait for entity to be properly registered before proceeding
2664
2749
  wait_bool = coerce_bool_param(wait, "wait", default=True)
2665
2750
  if wait_bool and entity_id:
@@ -2668,11 +2753,11 @@ def register_config_helper_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
2668
2753
  client, entity_id
2669
2754
  )
2670
2755
  if not registered:
2671
- helper_data["warning"] = (
2756
+ warnings.append(
2672
2757
  f"Helper created but {entity_id} not yet queryable. It may take a moment to become available."
2673
2758
  )
2674
2759
  except Exception as e:
2675
- helper_data["warning"] = (
2760
+ warnings.append(
2676
2761
  f"Helper created but verification failed: {e}"
2677
2762
  )
2678
2763
 
@@ -2691,6 +2776,11 @@ def register_config_helper_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
2691
2776
  update_message
2692
2777
  )
2693
2778
  if update_result.get("success"):
2779
+ # Mirror the update branch's icon propagation
2780
+ # (line ~3343) so the create response's ``data``
2781
+ # carries the same registry-write echo set.
2782
+ if icon is not None:
2783
+ helper_data["icon"] = icon if icon else None
2694
2784
  if area_id is not None:
2695
2785
  helper_data["area_id"] = area_id if area_id else None
2696
2786
  if labels is not None:
@@ -2702,29 +2792,39 @@ def register_config_helper_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
2702
2792
  if isinstance(error_detail, dict)
2703
2793
  else str(error_detail)
2704
2794
  )
2705
- helper_data["warning"] = (
2795
+ warnings.append(
2706
2796
  f"Helper created but entity registry update failed: {error_msg}"
2707
2797
  )
2708
2798
 
2709
- # Apply category via shared helper (consistent with automations/scripts)
2799
+ # Apply category via shared helper (consistent with automations/scripts).
2800
+ # Issue #1293: route the success/failure through ``cat_result`` so any
2801
+ # ``category_warning`` lands in the top-level ``warnings`` list instead
2802
+ # of leaking nested into ``helper_data``. Mirrors the precedent in
2803
+ # ``_handle_flow_helper`` (the ``cat_result`` block near the end of
2804
+ # ``_apply_registry_updates_to_entity``).
2710
2805
  if category and entity_id:
2806
+ cat_result: dict[str, Any] = {}
2711
2807
  await apply_entity_category(
2712
2808
  client,
2713
2809
  entity_id,
2714
2810
  category,
2715
2811
  "helpers",
2716
- helper_data,
2812
+ cat_result,
2717
2813
  "helper",
2718
2814
  )
2815
+ if "category" in cat_result:
2816
+ helper_data["category"] = cat_result["category"]
2817
+ elif "category_warning" in cat_result:
2818
+ warnings.append(cat_result["category_warning"])
2719
2819
 
2720
- return {
2721
- "success": True,
2722
- "action": "create",
2723
- "helper_type": helper_type,
2724
- "helper_data": helper_data,
2725
- "entity_id": entity_id,
2726
- "message": f"Successfully created {helper_type}: {name}",
2727
- }
2820
+ return _helper_response(
2821
+ "create",
2822
+ helper_type,
2823
+ data=helper_data,
2824
+ entity_id=entity_id,
2825
+ message=f"Successfully created {helper_type}: {name}",
2826
+ warnings=warnings,
2827
+ )
2728
2828
  else:
2729
2829
  raise_tool_error(
2730
2830
  create_error_response(
@@ -2772,6 +2872,11 @@ def register_config_helper_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
2772
2872
  }
2773
2873
 
2774
2874
  updated_data: dict[str, Any] = {}
2875
+ # Issue #1293: collect warnings in a top-level list for the
2876
+ # update path too, mirroring create + flow-helper branches.
2877
+ # (No re-annotation — the create branch above already defined
2878
+ # ``warnings: list[str]``; mypy treats this as the same binding.)
2879
+ warnings = []
2775
2880
 
2776
2881
  if helper_type == "tag":
2777
2882
  # Tags use their own registry — no entity registry entries.
@@ -2806,14 +2911,19 @@ def register_config_helper_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
2806
2911
 
2807
2912
  # Tags don't have entity registry entries, so return directly
2808
2913
  # without wait_for_entity_registered (they're not entities).
2809
- return {
2810
- "success": True,
2811
- "action": "update",
2812
- "helper_type": helper_type,
2813
- "entity_id": entity_id,
2814
- "updated_data": updated_data,
2815
- "message": f"Successfully updated {helper_type}: {entity_id}",
2816
- }
2914
+ # Issue #1293: same uniform builder as the sibling create/update
2915
+ # branches. No producer appends to ``warnings`` on the tag path
2916
+ # today, but ``_helper_response`` already omits the key when the
2917
+ # list is empty — future warnings flow through the same contract
2918
+ # without further plumbing.
2919
+ return _helper_response(
2920
+ "update",
2921
+ helper_type,
2922
+ data=updated_data,
2923
+ entity_id=entity_id,
2924
+ message=f"Successfully updated {helper_type}: {entity_id}",
2925
+ warnings=warnings,
2926
+ )
2817
2927
 
2818
2928
  elif helper_type in config_store_types:
2819
2929
  # Person and zone: look up unique_id from entity registry
@@ -3290,30 +3400,49 @@ def register_config_helper_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
3290
3400
  reg_result = await client.send_websocket_message(
3291
3401
  registry_update
3292
3402
  )
3293
- if not reg_result.get("success"):
3403
+ if reg_result.get("success"):
3404
+ # Issue #1293: mirror the create branch by propagating the
3405
+ # registry writes into ``updated_data`` so the response's
3406
+ # ``data`` reflects the post-update state.
3407
+ if icon is not None:
3408
+ updated_data["icon"] = icon if icon else None
3409
+ if area_id is not None:
3410
+ updated_data["area_id"] = area_id if area_id else None
3411
+ if labels is not None:
3412
+ updated_data["labels"] = labels
3413
+ else:
3294
3414
  error_detail = reg_result.get("error", {})
3295
3415
  error_msg = (
3296
3416
  error_detail.get("message", "Unknown error")
3297
3417
  if isinstance(error_detail, dict)
3298
3418
  else str(error_detail)
3299
3419
  )
3300
- logger.warning(
3301
- f"Entity registry update failed for {entity_id}: {error_msg}"
3302
- )
3303
- updated_data["warning"] = (
3420
+ # No ``logger.warning`` here — the create-branch and
3421
+ # flow-helper registry-update failure paths surface this
3422
+ # via ``warnings.append`` only, and the response carries
3423
+ # the message to the caller in the top-level ``warnings``
3424
+ # list. Logging again would double-report.
3425
+ warnings.append(
3304
3426
  f"Config updated but entity registry update failed: {error_msg}"
3305
3427
  )
3306
3428
 
3307
- # Apply category via shared helper
3429
+ # Apply category via shared helper. Issue #1293: route through
3430
+ # ``cat_result`` so any ``category_warning`` lands in the top-level
3431
+ # ``warnings`` list instead of nested in ``updated_data``.
3308
3432
  if category:
3433
+ cat_result = {}
3309
3434
  await apply_entity_category(
3310
3435
  client,
3311
3436
  entity_id,
3312
3437
  category,
3313
3438
  "helpers",
3314
- updated_data,
3439
+ cat_result,
3315
3440
  "helper",
3316
3441
  )
3442
+ if "category" in cat_result:
3443
+ updated_data["category"] = cat_result["category"]
3444
+ elif "category_warning" in cat_result:
3445
+ warnings.append(cat_result["category_warning"])
3317
3446
 
3318
3447
  else:
3319
3448
  # Fallback for unknown/future helper types: entity registry update only
@@ -3347,39 +3476,43 @@ def register_config_helper_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
3347
3476
  )
3348
3477
  )
3349
3478
 
3350
- # Apply category via shared helper
3479
+ # Apply category via shared helper. Issue #1293: same temp-dict
3480
+ # routing as the simple-helper branch above.
3351
3481
  if category:
3482
+ cat_result = {}
3352
3483
  await apply_entity_category(
3353
3484
  client,
3354
3485
  entity_id,
3355
3486
  category,
3356
3487
  "helpers",
3357
- updated_data,
3488
+ cat_result,
3358
3489
  "helper",
3359
3490
  )
3491
+ if "category" in cat_result:
3492
+ updated_data["category"] = cat_result["category"]
3493
+ elif "category_warning" in cat_result:
3494
+ warnings.append(cat_result["category_warning"])
3360
3495
 
3361
3496
  # Wait for entity to reflect the update
3362
3497
  wait_bool = coerce_bool_param(wait, "wait", default=True)
3363
- response: dict[str, Any] = {
3364
- "success": True,
3365
- "action": "update",
3366
- "helper_type": helper_type,
3367
- "entity_id": entity_id,
3368
- "updated_data": updated_data,
3369
- "message": f"Successfully updated {helper_type}: {entity_id}",
3370
- }
3371
3498
  if wait_bool:
3372
3499
  try:
3373
3500
  registered = await wait_for_entity_registered(client, entity_id)
3374
3501
  if not registered:
3375
- response["warning"] = (
3502
+ warnings.append(
3376
3503
  f"Update applied but {entity_id} not yet queryable."
3377
3504
  )
3378
3505
  except Exception as e:
3379
- response["warning"] = (
3380
- f"Update applied but verification failed: {e}"
3381
- )
3382
- return response
3506
+ warnings.append(f"Update applied but verification failed: {e}")
3507
+
3508
+ return _helper_response(
3509
+ "update",
3510
+ helper_type,
3511
+ data=updated_data,
3512
+ entity_id=entity_id,
3513
+ message=f"Successfully updated {helper_type}: {entity_id}",
3514
+ warnings=warnings,
3515
+ )
3383
3516
 
3384
3517
  # This should never be reached since action is either "create" or "update"
3385
3518
  raise_tool_error(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ha-mcp-dev
3
- Version: 7.5.0.dev517
3
+ Version: 7.5.0.dev518
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