ha-mcp-dev 7.4.1.dev472__tar.gz → 7.4.1.dev474__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 (110) hide show
  1. {ha_mcp_dev-7.4.1.dev472/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.4.1.dev474}/PKG-INFO +1 -1
  2. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/pyproject.toml +1 -1
  3. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/tools/tools_config_dashboards.py +31 -42
  4. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/tools/tools_config_scenes.py +7 -10
  5. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/utils/python_sandbox.py +11 -0
  6. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
  7. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/LICENSE +0 -0
  8. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/MANIFEST.in +0 -0
  9. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/README.md +0 -0
  10. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/setup.cfg +0 -0
  11. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/__init__.py +0 -0
  12. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/__main__.py +0 -0
  13. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/_pypi_marker +0 -0
  14. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/_version.py +0 -0
  15. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/auth/__init__.py +0 -0
  16. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/auth/consent_form.py +0 -0
  17. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/auth/provider.py +0 -0
  18. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/client/__init__.py +0 -0
  19. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/client/rest_client.py +0 -0
  20. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/client/supervisor_client.py +0 -0
  21. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/client/websocket_client.py +0 -0
  22. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/client/websocket_listener.py +0 -0
  23. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/config.py +0 -0
  24. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/errors.py +0 -0
  25. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/py.typed +0 -0
  26. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
  27. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
  28. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
  29. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
  30. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
  31. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
  32. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
  33. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
  34. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
  35. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
  36. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
  37. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
  38. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
  39. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
  40. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
  41. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
  42. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
  43. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
  44. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
  45. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
  46. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/server.py +0 -0
  47. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/settings_ui.py +0 -0
  48. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/smoke_test.py +0 -0
  49. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/tools/__init__.py +0 -0
  50. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/tools/backup.py +0 -0
  51. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/tools/best_practice_checker.py +0 -0
  52. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/tools/device_control.py +0 -0
  53. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/tools/enhanced.py +0 -0
  54. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/tools/helpers.py +0 -0
  55. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/tools/reference_validator.py +0 -0
  56. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/tools/registry.py +0 -0
  57. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/tools/smart_search.py +0 -0
  58. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/tools/tools_addons.py +0 -0
  59. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/tools/tools_areas.py +0 -0
  60. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/tools/tools_blueprints.py +0 -0
  61. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/tools/tools_bug_report.py +0 -0
  62. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/tools/tools_calendar.py +0 -0
  63. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/tools/tools_camera.py +0 -0
  64. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/tools/tools_categories.py +0 -0
  65. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/tools/tools_code.py +0 -0
  66. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/tools/tools_config_automations.py +0 -0
  67. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/tools/tools_config_entry_flow.py +0 -0
  68. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
  69. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
  70. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/tools/tools_energy.py +0 -0
  71. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/tools/tools_entities.py +0 -0
  72. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/tools/tools_filesystem.py +0 -0
  73. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/tools/tools_groups.py +0 -0
  74. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/tools/tools_hacs.py +0 -0
  75. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/tools/tools_history.py +0 -0
  76. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/tools/tools_integrations.py +0 -0
  77. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/tools/tools_labels.py +0 -0
  78. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/tools/tools_mcp_component.py +0 -0
  79. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/tools/tools_registry.py +0 -0
  80. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/tools/tools_resources.py +0 -0
  81. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/tools/tools_search.py +0 -0
  82. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/tools/tools_service.py +0 -0
  83. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/tools/tools_services.py +0 -0
  84. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/tools/tools_system.py +0 -0
  85. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/tools/tools_todo.py +0 -0
  86. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/tools/tools_traces.py +0 -0
  87. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/tools/tools_updates.py +0 -0
  88. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/tools/tools_utility.py +0 -0
  89. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
  90. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
  91. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/tools/tools_zones.py +0 -0
  92. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/tools/util_helpers.py +0 -0
  93. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/transforms/__init__.py +0 -0
  94. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/transforms/categorized_search.py +0 -0
  95. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/utils/__init__.py +0 -0
  96. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/utils/config_hash.py +0 -0
  97. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/utils/data_paths.py +0 -0
  98. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/utils/domain_handlers.py +0 -0
  99. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/utils/fuzzy_search.py +0 -0
  100. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
  101. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/utils/operation_manager.py +0 -0
  102. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp/utils/usage_logger.py +0 -0
  103. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
  104. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
  105. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
  106. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
  107. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
  108. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/tests/__init__.py +0 -0
  109. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/tests/test_constants.py +0 -0
  110. {ha_mcp_dev-7.4.1.dev472 → ha_mcp_dev-7.4.1.dev474}/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.4.1.dev472
3
+ Version: 7.4.1.dev474
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.4.1.dev472"
7
+ version = "7.4.1.dev474"
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"
@@ -265,6 +265,32 @@ def _should_lazy_resolve(error_msg: str) -> bool:
265
265
  return _LAZY_RESOLVE_TRIGGER in error_msg
266
266
 
267
267
 
268
+ async def _fetch_dashboards_list(
269
+ client: Any,
270
+ ) -> list[dict[str, Any]] | None:
271
+ """Fetch and normalise the lovelace/dashboards/list WebSocket response.
272
+
273
+ Returns the list of dashboard registry entries on success, or ``None``
274
+ when the response shape is unrecognised. A warning is logged on
275
+ unexpected shapes so that future HA response-format changes surface at
276
+ every fetch site rather than silently degrading.
277
+
278
+ Callers decide how to handle ``None`` (e.g. fall through to ``[]`` or
279
+ propagate the failure).
280
+ """
281
+ result = await client.send_websocket_message({"type": "lovelace/dashboards/list"})
282
+ if isinstance(result, dict) and isinstance(result.get("result"), list):
283
+ return cast(list[dict[str, Any]], result["result"])
284
+ if isinstance(result, list):
285
+ return cast(list[dict[str, Any]], result)
286
+ logger.warning(
287
+ "lovelace/dashboards/list returned an unexpected shape (type=%s); "
288
+ "treating as no-match",
289
+ type(result).__name__,
290
+ )
291
+ return None
292
+
293
+
268
294
  async def _resolve_dashboard(
269
295
  client: Any, identifier: str
270
296
  ) -> tuple[dict[str, str] | None, list[dict[str, Any]] | None]:
@@ -297,21 +323,8 @@ async def _resolve_dashboard(
297
323
  to the registry id before issuing the delete. Discards
298
324
  ``dashboards``.
299
325
  """
300
- result = await client.send_websocket_message({"type": "lovelace/dashboards/list"})
301
- if isinstance(result, dict) and "result" in result:
302
- dashboards = result["result"]
303
- elif isinstance(result, list):
304
- dashboards = result
305
- else:
306
- # Neither dict-with-result nor list — either HA returned an error
307
- # envelope (unknown shape) or the response format changed.
308
- # Surface a warning so the next response-shape change isn't a
309
- # silent "always no match" regression.
310
- logger.warning(
311
- "lovelace/dashboards/list returned an unexpected shape (type=%s); "
312
- "treating as no-match",
313
- type(result).__name__,
314
- )
326
+ dashboards = await _fetch_dashboards_list(client)
327
+ if dashboards is None:
315
328
  return None, None
316
329
 
317
330
  for d in dashboards:
@@ -527,16 +540,7 @@ def register_config_dashboard_tools(mcp: Any, client: Any, **kwargs: Any) -> Non
527
540
  try:
528
541
  # List mode
529
542
  if list_only:
530
- result = await client.send_websocket_message(
531
- {"type": "lovelace/dashboards/list"}
532
- )
533
- if isinstance(result, dict) and "result" in result:
534
- dashboards = result["result"]
535
- elif isinstance(result, list):
536
- dashboards = result
537
- else:
538
- dashboards = []
539
-
543
+ dashboards = await _fetch_dashboards_list(client) or []
540
544
  return {
541
545
  "success": True,
542
546
  "action": "list",
@@ -1158,24 +1162,9 @@ def register_config_dashboard_tools(mcp: Any, client: Any, **kwargs: Any) -> Non
1158
1162
  if pre_fetched_dashboards is not None:
1159
1163
  existing_dashboards = pre_fetched_dashboards
1160
1164
  else:
1161
- result = await client.send_websocket_message(
1162
- {"type": "lovelace/dashboards/list"}
1165
+ existing_dashboards = (
1166
+ await _fetch_dashboards_list(client) or []
1163
1167
  )
1164
- if isinstance(result, dict) and "result" in result:
1165
- existing_dashboards = result["result"]
1166
- elif isinstance(result, list):
1167
- existing_dashboards = result
1168
- else:
1169
- # Mirror the warning emitted by ``_resolve_dashboard`` on
1170
- # the same response-shape failure, so a future HA shape
1171
- # change shows up at every fetch site rather than going
1172
- # silent on this one.
1173
- logger.warning(
1174
- "lovelace/dashboards/list returned an unexpected shape "
1175
- "(type=%s); treating as no-match",
1176
- type(result).__name__,
1177
- )
1178
- existing_dashboards = []
1179
1168
  dashboard_exists = any(
1180
1169
  d.get("url_path") == url_path for d in existing_dashboards
1181
1170
  )
@@ -23,6 +23,7 @@ from ..errors import ErrorCode, create_error_response
23
23
  from ..utils.config_hash import compute_config_hash
24
24
  from ..utils.python_sandbox import (
25
25
  PythonSandboxError,
26
+ format_sandbox_error,
26
27
  get_security_documentation,
27
28
  safe_execute,
28
29
  )
@@ -572,16 +573,12 @@ class ConfigSceneTools:
572
573
  try:
573
574
  transformed_config = safe_execute(python_transform, actual_config)
574
575
  except PythonSandboxError as e:
576
+ message, suggestions = format_sandbox_error(e, python_transform)
575
577
  raise_tool_error(
576
578
  create_error_response(
577
579
  ErrorCode.VALIDATION_FAILED,
578
- str(e),
579
- suggestions=[
580
- "Check expression syntax",
581
- "Ensure only allowed operations are used",
582
- "See tool description for allowed operations",
583
- f"Expression: {python_transform[:100]}{'...' if len(python_transform) > 100 else ''}",
584
- ],
580
+ message,
581
+ suggestions=suggestions,
585
582
  context={
586
583
  "action": "python_transform",
587
584
  "scene_id": resolved_id,
@@ -656,9 +653,9 @@ class ConfigSceneTools:
656
653
  # is the in-place equivalent of ``del`` — normalise both
657
654
  # by checking against ``(None, resolved_id)``, so only an
658
655
  # explicit non-None mismatch triggers the reject.
659
- if (
660
- "id" in transformed_config
661
- and transformed_config["id"] not in (None, resolved_id)
656
+ if "id" in transformed_config and transformed_config["id"] not in (
657
+ None,
658
+ resolved_id,
662
659
  ):
663
660
  raise_tool_error(
664
661
  create_error_response(
@@ -472,6 +472,17 @@ def format_sandbox_error(
472
472
  "See tool description for allowed operations",
473
473
  f"Expression: {preview}",
474
474
  ]
475
+ # Agents commonly emit ``\"`` (intending a JSON-style quote escape)
476
+ # inside transforms. Outside Python string literals this is a
477
+ # line-continuation token followed by an unexpected character.
478
+ # Pinpoint the actual mistake so the agent fixes it on the first
479
+ # retry instead of guessing at "syntax".
480
+ if "line continuation character" in str(error):
481
+ suggestions.insert(
482
+ 0,
483
+ 'Remove the leading backslash before quotes — write "foo" '
484
+ 'not \\"foo\\" in the Python source.',
485
+ )
475
486
  if variable_name != "config":
476
487
  suggestions = [
477
488
  f"Operate on the `{variable_name}` variable (in-place or reassign)",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ha-mcp-dev
3
- Version: 7.4.1.dev472
3
+ Version: 7.4.1.dev474
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