ha-mcp-dev 7.5.0.dev531__tar.gz → 7.5.0.dev533__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.dev531/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.5.0.dev533}/PKG-INFO +1 -1
  2. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/pyproject.toml +1 -1
  3. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/tools/smart_search.py +3 -7
  4. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/tools/tools_config_automations.py +32 -0
  5. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/tools/tools_config_dashboards.py +54 -6
  6. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/tools/tools_config_scripts.py +28 -0
  7. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/tools/tools_yaml_config.py +2 -15
  8. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
  9. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/LICENSE +0 -0
  10. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/MANIFEST.in +0 -0
  11. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/README.md +0 -0
  12. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/setup.cfg +0 -0
  13. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/__init__.py +0 -0
  14. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/__main__.py +0 -0
  15. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/_pypi_marker +0 -0
  16. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/_version.py +0 -0
  17. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/auth/__init__.py +0 -0
  18. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/auth/consent_form.py +0 -0
  19. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/auth/provider.py +0 -0
  20. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/client/__init__.py +0 -0
  21. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/client/rest_client.py +0 -0
  22. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/client/supervisor_client.py +0 -0
  23. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/client/websocket_client.py +0 -0
  24. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/client/websocket_listener.py +0 -0
  25. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/config.py +0 -0
  26. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/errors.py +0 -0
  27. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/py.typed +0 -0
  28. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
  29. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
  30. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
  31. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
  32. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
  33. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
  34. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
  35. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
  36. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
  37. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
  38. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
  39. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
  40. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
  41. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
  42. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
  43. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
  44. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
  45. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
  46. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
  47. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
  48. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/server.py +0 -0
  49. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/settings_ui.py +0 -0
  50. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/smoke_test.py +0 -0
  51. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/tools/__init__.py +0 -0
  52. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/tools/backup.py +0 -0
  53. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/tools/best_practice_checker.py +0 -0
  54. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/tools/device_control.py +0 -0
  55. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/tools/enhanced.py +0 -0
  56. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/tools/helpers.py +0 -0
  57. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/tools/reference_validator.py +0 -0
  58. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/tools/registry.py +0 -0
  59. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/tools/tools_addons.py +0 -0
  60. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/tools/tools_areas.py +0 -0
  61. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/tools/tools_blueprints.py +0 -0
  62. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/tools/tools_bug_report.py +0 -0
  63. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/tools/tools_calendar.py +0 -0
  64. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/tools/tools_camera.py +0 -0
  65. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/tools/tools_categories.py +0 -0
  66. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/tools/tools_code.py +0 -0
  67. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/tools/tools_config_entry_flow.py +0 -0
  68. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
  69. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
  70. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/tools/tools_energy.py +0 -0
  71. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/tools/tools_entities.py +0 -0
  72. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/tools/tools_filesystem.py +0 -0
  73. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/tools/tools_groups.py +0 -0
  74. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/tools/tools_hacs.py +0 -0
  75. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/tools/tools_history.py +0 -0
  76. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/tools/tools_integrations.py +0 -0
  77. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/tools/tools_labels.py +0 -0
  78. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/tools/tools_mcp_component.py +0 -0
  79. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/tools/tools_registry.py +0 -0
  80. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/tools/tools_resources.py +0 -0
  81. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/tools/tools_search.py +0 -0
  82. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/tools/tools_service.py +0 -0
  83. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/tools/tools_services.py +0 -0
  84. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/tools/tools_system.py +0 -0
  85. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/tools/tools_todo.py +0 -0
  86. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/tools/tools_traces.py +0 -0
  87. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/tools/tools_updates.py +0 -0
  88. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/tools/tools_utility.py +0 -0
  89. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
  90. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/tools/tools_zones.py +0 -0
  91. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/tools/util_helpers.py +0 -0
  92. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/transforms/__init__.py +0 -0
  93. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/transforms/categorized_search.py +0 -0
  94. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
  95. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/utils/__init__.py +0 -0
  96. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/utils/config_hash.py +0 -0
  97. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/utils/data_paths.py +0 -0
  98. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/utils/domain_handlers.py +0 -0
  99. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/utils/fuzzy_search.py +0 -0
  100. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
  101. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/utils/operation_manager.py +0 -0
  102. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/utils/python_sandbox.py +0 -0
  103. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp/utils/usage_logger.py +0 -0
  104. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
  105. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
  106. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
  107. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
  108. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
  109. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/tests/__init__.py +0 -0
  110. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/tests/test_constants.py +0 -0
  111. {ha_mcp_dev-7.5.0.dev531 → ha_mcp_dev-7.5.0.dev533}/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.dev531
3
+ Version: 7.5.0.dev533
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.dev531"
7
+ version = "7.5.0.dev533"
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"
@@ -22,6 +22,7 @@ from ..utils.fuzzy_search import (
22
22
  tokenize,
23
23
  )
24
24
  from .helpers import exception_to_structured_error, safe_info, safe_progress
25
+ from .tools_config_dashboards import fetch_dashboards_list
25
26
 
26
27
  logger = logging.getLogger(__name__)
27
28
 
@@ -1773,14 +1774,9 @@ class SmartSearchTools:
1773
1774
  if "dashboard" in search_types:
1774
1775
  try:
1775
1776
  # List all storage-mode dashboards
1776
- dash_list_resp = await self.client.send_websocket_message(
1777
- {"type": "lovelace/dashboards/list"}
1777
+ dashboard_entries: list[dict[str, Any]] = (
1778
+ await fetch_dashboards_list(self.client) or []
1778
1779
  )
1779
- dashboard_entries: list[dict[str, Any]] = []
1780
- if isinstance(dash_list_resp, dict) and dash_list_resp.get(
1781
- "success"
1782
- ):
1783
- dashboard_entries = dash_list_resp.get("result", [])
1784
1780
 
1785
1781
  # Build list of dashboards to search (include default)
1786
1782
  dashboards_to_search: list[tuple[str, str]] = [
@@ -289,6 +289,21 @@ class AutomationConfigTools:
289
289
  For comprehensive automation documentation, use ha_get_skill_guide.
290
290
  """
291
291
  try:
292
+ # Empty/whitespace identifier would propagate to the internal
293
+ # config lookup and surface as a misleading
294
+ # ``RESOURCE_NOT_FOUND``. Structured ``VALIDATION_INVALID_PARAMETER``
295
+ # naming the parameter is cleaner — extension of the #1312
296
+ # validate_identifier_not_empty pattern to the automations
297
+ # family per #1313.
298
+ validate_identifier_not_empty(
299
+ identifier,
300
+ "identifier",
301
+ suggestions=[
302
+ "Pass an automation entity_id (e.g. 'automation.morning_routine')",
303
+ "Or pass the unique_id of an existing automation",
304
+ "Use ha_search_entities(domain_filter='automation') to list automations",
305
+ ],
306
+ )
292
307
  normalized_config, config_hash = await self._get_automation_config_internal(identifier)
293
308
 
294
309
  # Resolve entity_id and fetch category from entity registry
@@ -542,6 +557,23 @@ class AutomationConfigTools:
542
557
  """
543
558
  bp_warnings: list[str] = []
544
559
  try:
560
+ # ``identifier`` is optional (omit → create new with generated
561
+ # unique_id; pass → update existing). When provided, reject
562
+ # empty/whitespace up-front so the caller gets a structured
563
+ # parameter error instead of a misleading ``RESOURCE_NOT_FOUND``
564
+ # from the downstream lookup. The ``not identifier`` check
565
+ # further down the python_transform branch still handles the
566
+ # explicit ``identifier is None`` case for that mode.
567
+ if identifier is not None:
568
+ validate_identifier_not_empty(
569
+ identifier,
570
+ "identifier",
571
+ suggestions=[
572
+ "Omit identifier to create a new automation",
573
+ "Or pass a valid automation entity_id / unique_id to update",
574
+ ],
575
+ context={"action": "set"},
576
+ )
545
577
  # Validate mutual exclusivity of config and python_transform
546
578
  if config is not None and python_transform is not None:
547
579
  raise_tool_error(
@@ -26,6 +26,7 @@ from .helpers import (
26
26
  extract_tool_error_message,
27
27
  log_tool_usage,
28
28
  raise_tool_error,
29
+ validate_identifier_not_empty,
29
30
  )
30
31
  from .util_helpers import parse_json_param
31
32
 
@@ -322,7 +323,7 @@ def _should_lazy_resolve(error_msg: str) -> bool:
322
323
  return _LAZY_RESOLVE_TRIGGER in error_msg
323
324
 
324
325
 
325
- async def _fetch_dashboards_list(
326
+ async def fetch_dashboards_list(
326
327
  client: Any,
327
328
  ) -> list[dict[str, Any]] | None:
328
329
  """Fetch and normalise the lovelace/dashboards/list WebSocket response.
@@ -380,7 +381,7 @@ async def _resolve_dashboard(
380
381
  to the registry id before issuing the delete. Discards
381
382
  ``dashboards``.
382
383
  """
383
- dashboards = await _fetch_dashboards_list(client)
384
+ dashboards = await fetch_dashboards_list(client)
384
385
  if dashboards is None:
385
386
  return None, None
386
387
 
@@ -597,7 +598,7 @@ def register_config_dashboard_tools(mcp: Any, client: Any, **kwargs: Any) -> Non
597
598
  try:
598
599
  # List mode
599
600
  if list_only:
600
- dashboards = await _fetch_dashboards_list(client) or []
601
+ dashboards = await fetch_dashboards_list(client) or []
601
602
  return {
602
603
  "success": True,
603
604
  "action": "list",
@@ -605,6 +606,25 @@ def register_config_dashboard_tools(mcp: Any, client: Any, **kwargs: Any) -> Non
605
606
  "count": len(dashboards),
606
607
  }
607
608
 
609
+ # ``url_path`` is optional in this tool (omitted with
610
+ # ``list_only=True`` lists all dashboards — handled above; omitted
611
+ # without ``list_only`` falls back to the default dashboard via
612
+ # the resolver below). When provided, reject empty/whitespace
613
+ # up-front so the caller gets a structured parameter error
614
+ # instead of a misleading ``RESOURCE_NOT_FOUND``. Extension of
615
+ # the #1312 validate_identifier_not_empty pattern to the
616
+ # dashboards family per #1313.
617
+ if url_path is not None:
618
+ validate_identifier_not_empty(
619
+ url_path,
620
+ "url_path",
621
+ suggestions=[
622
+ "Pass a dashboard URL path (e.g. 'lovelace-home')",
623
+ "Omit url_path and pass list_only=True to list dashboards",
624
+ "Use 'default' to target the default dashboard",
625
+ ],
626
+ )
627
+
608
628
  # Search mode — find cards, badges, or header cards
609
629
  if search_mode:
610
630
  get_data: dict[str, Any] = {"type": "lovelace/config", "force": True}
@@ -996,6 +1016,22 @@ def register_config_dashboard_tools(mcp: Any, client: Any, **kwargs: Any) -> Non
996
1016
  are also updated if explicitly provided alongside (or instead of) a config change.
997
1017
  """
998
1018
  try:
1019
+ # ``url_path`` is required (always non-None). Reject empty/
1020
+ # whitespace up-front so the caller gets a structured parameter
1021
+ # error instead of a misleading downstream failure (the
1022
+ # subsequent "default" alias, pre-resolver, and hyphen check
1023
+ # all assume a usable string). Extension of the #1312
1024
+ # validate_identifier_not_empty pattern to the dashboards
1025
+ # family per #1313.
1026
+ validate_identifier_not_empty(
1027
+ url_path,
1028
+ "url_path",
1029
+ suggestions=[
1030
+ "Pass a dashboard URL path (e.g. 'my-dashboard')",
1031
+ "Use 'default' or 'lovelace' for the default dashboard",
1032
+ ],
1033
+ context={"action": "set"},
1034
+ )
999
1035
  # Handle "default" as alias for the default dashboard
1000
1036
  # (matches ha_config_get_dashboard behavior)
1001
1037
  if url_path == "default":
@@ -1213,9 +1249,7 @@ def register_config_dashboard_tools(mcp: Any, client: Any, **kwargs: Any) -> Non
1213
1249
  if pre_fetched_dashboards is not None:
1214
1250
  existing_dashboards = pre_fetched_dashboards
1215
1251
  else:
1216
- existing_dashboards = (
1217
- await _fetch_dashboards_list(client) or []
1218
- )
1252
+ existing_dashboards = await fetch_dashboards_list(client) or []
1219
1253
  dashboard_exists = any(
1220
1254
  d.get("url_path") == url_path for d in existing_dashboards
1221
1255
  )
@@ -1490,6 +1524,20 @@ def register_config_dashboard_tools(mcp: Any, client: Any, **kwargs: Any) -> Non
1490
1524
  Note: The default dashboard cannot be deleted via this method.
1491
1525
  """
1492
1526
  try:
1527
+ # ``url_path`` is required. Reject empty/whitespace up-front so
1528
+ # the caller gets a structured parameter error instead of a
1529
+ # misleading "no dashboard found" from the resolver below.
1530
+ # Extension of the #1312 validate_identifier_not_empty pattern
1531
+ # to the dashboards family per #1313.
1532
+ validate_identifier_not_empty(
1533
+ url_path,
1534
+ "url_path",
1535
+ suggestions=[
1536
+ "Pass a dashboard URL path or internal ID (e.g. 'my-dashboard')",
1537
+ "Use ha_config_get_dashboard(list_only=True) to list dashboards",
1538
+ ],
1539
+ context={"action": "delete"},
1540
+ )
1493
1541
  resolved, _ = await _resolve_dashboard(client, url_path)
1494
1542
  if resolved is None:
1495
1543
  raise_tool_error(
@@ -103,6 +103,19 @@ class ConfigScriptTools:
103
103
  For detailed script configuration help, use ha_get_skill_guide.
104
104
  """
105
105
  try:
106
+ # Empty/whitespace script_id would propagate to
107
+ # ``get_script_config`` and surface as a misleading
108
+ # ``RESOURCE_NOT_FOUND``. Extension of the #1312
109
+ # validate_identifier_not_empty pattern to the scripts
110
+ # family per #1313.
111
+ validate_identifier_not_empty(
112
+ script_id,
113
+ "script_id",
114
+ suggestions=[
115
+ "Pass a script identifier (e.g. 'morning_routine')",
116
+ "Use ha_search_entities(domain_filter='script') to list scripts",
117
+ ],
118
+ )
106
119
  config_result = await self._client.get_script_config(script_id)
107
120
  # Extract actual script config body and compute hash before category injection
108
121
  actual_config = config_result.get("config", config_result)
@@ -412,6 +425,21 @@ class ConfigScriptTools:
412
425
  """
413
426
  bp_warnings: list[str] = []
414
427
  try:
428
+ # ``script_id`` is required (always non-None). Reject empty/
429
+ # whitespace up-front so the caller gets a structured parameter
430
+ # error instead of a misleading ``RESOURCE_NOT_FOUND`` from
431
+ # the downstream upsert/fetch. Extension of the #1312
432
+ # validate_identifier_not_empty pattern to the scripts family
433
+ # per #1313.
434
+ validate_identifier_not_empty(
435
+ script_id,
436
+ "script_id",
437
+ suggestions=[
438
+ "Pass a script identifier (e.g. 'morning_routine')",
439
+ "Use ha_search_entities(domain_filter='script') to list scripts",
440
+ ],
441
+ context={"action": "set"},
442
+ )
415
443
  # Validate mutual exclusivity of config and python_transform
416
444
  if config is not None and python_transform is not None:
417
445
  raise_tool_error(
@@ -20,6 +20,7 @@ from pydantic import Field
20
20
  from ..config import get_global_settings
21
21
  from ..errors import ErrorCode, create_error_response
22
22
  from .helpers import exception_to_structured_error, log_tool_usage, raise_tool_error
23
+ from .tools_config_dashboards import fetch_dashboards_list
23
24
  from .tools_filesystem import (
24
25
  MCP_TOOLS_DOMAIN,
25
26
  _assert_mcp_tools_available,
@@ -45,9 +46,7 @@ async def _check_storage_mode_dashboard_collision(
45
46
  return
46
47
  url_path = yaml_path[len(_LOVELACE_DASHBOARD_PREFIX):]
47
48
  try:
48
- result = await client.send_websocket_message(
49
- {"type": "lovelace/dashboards/list"}
50
- )
49
+ dashboards = await fetch_dashboards_list(client)
51
50
  except Exception as exc:
52
51
  logger.warning(
53
52
  "lovelace/dashboards/list WS query failed (%s); skipping collision check",
@@ -55,18 +54,6 @@ async def _check_storage_mode_dashboard_collision(
55
54
  )
56
55
  return
57
56
 
58
- if isinstance(result, dict) and "result" in result:
59
- dashboards = result["result"]
60
- elif isinstance(result, list):
61
- dashboards = result
62
- else:
63
- logger.warning(
64
- "lovelace/dashboards/list returned unexpected shape (%s); "
65
- "skipping collision check",
66
- type(result).__name__,
67
- )
68
- return
69
-
70
57
  for entry in dashboards or []:
71
58
  if (
72
59
  isinstance(entry, dict)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ha-mcp-dev
3
- Version: 7.5.0.dev531
3
+ Version: 7.5.0.dev533
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