ha-mcp-dev 7.2.0.dev340__tar.gz → 7.2.0.dev342__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 (100) hide show
  1. {ha_mcp_dev-7.2.0.dev340/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.2.0.dev342}/PKG-INFO +4 -4
  2. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/README.md +3 -3
  3. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/pyproject.toml +1 -1
  4. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/tools/tools_config_dashboards.py +171 -236
  5. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/tools/tools_yaml_config.py +4 -2
  6. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342/src/ha_mcp_dev.egg-info}/PKG-INFO +4 -4
  7. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/LICENSE +0 -0
  8. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/MANIFEST.in +0 -0
  9. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/setup.cfg +0 -0
  10. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/__init__.py +0 -0
  11. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/__main__.py +0 -0
  12. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/_pypi_marker +0 -0
  13. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/auth/__init__.py +0 -0
  14. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/auth/consent_form.py +0 -0
  15. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/auth/provider.py +0 -0
  16. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/client/__init__.py +0 -0
  17. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/client/rest_client.py +0 -0
  18. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/client/websocket_client.py +0 -0
  19. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/client/websocket_listener.py +0 -0
  20. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/config.py +0 -0
  21. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/errors.py +0 -0
  22. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/py.typed +0 -0
  23. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
  24. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
  25. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
  26. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
  27. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
  28. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
  29. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
  30. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
  31. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
  32. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
  33. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
  34. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
  35. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
  36. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
  37. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
  38. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
  39. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
  40. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
  41. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
  42. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
  43. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/server.py +0 -0
  44. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/smoke_test.py +0 -0
  45. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/tools/__init__.py +0 -0
  46. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/tools/backup.py +0 -0
  47. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/tools/best_practice_checker.py +0 -0
  48. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/tools/device_control.py +0 -0
  49. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/tools/enhanced.py +0 -0
  50. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/tools/helpers.py +0 -0
  51. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/tools/registry.py +0 -0
  52. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/tools/smart_search.py +0 -0
  53. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/tools/tools_addons.py +0 -0
  54. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/tools/tools_areas.py +0 -0
  55. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/tools/tools_blueprints.py +0 -0
  56. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/tools/tools_bug_report.py +0 -0
  57. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/tools/tools_calendar.py +0 -0
  58. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/tools/tools_camera.py +0 -0
  59. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/tools/tools_categories.py +0 -0
  60. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/tools/tools_config_automations.py +0 -0
  61. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/tools/tools_config_entry_flow.py +0 -0
  62. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
  63. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
  64. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/tools/tools_entities.py +0 -0
  65. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/tools/tools_filesystem.py +0 -0
  66. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/tools/tools_groups.py +0 -0
  67. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/tools/tools_hacs.py +0 -0
  68. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/tools/tools_history.py +0 -0
  69. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/tools/tools_integrations.py +0 -0
  70. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/tools/tools_labels.py +0 -0
  71. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/tools/tools_mcp_component.py +0 -0
  72. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/tools/tools_registry.py +0 -0
  73. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/tools/tools_resources.py +0 -0
  74. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/tools/tools_search.py +0 -0
  75. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/tools/tools_service.py +0 -0
  76. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/tools/tools_services.py +0 -0
  77. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/tools/tools_system.py +0 -0
  78. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/tools/tools_todo.py +0 -0
  79. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/tools/tools_traces.py +0 -0
  80. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/tools/tools_updates.py +0 -0
  81. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/tools/tools_utility.py +0 -0
  82. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
  83. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/tools/tools_zones.py +0 -0
  84. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/tools/util_helpers.py +0 -0
  85. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/transforms/__init__.py +0 -0
  86. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/transforms/categorized_search.py +0 -0
  87. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/utils/__init__.py +0 -0
  88. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/utils/domain_handlers.py +0 -0
  89. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/utils/fuzzy_search.py +0 -0
  90. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/utils/operation_manager.py +0 -0
  91. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/utils/python_sandbox.py +0 -0
  92. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp/utils/usage_logger.py +0 -0
  93. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
  94. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
  95. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
  96. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
  97. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
  98. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/tests/__init__.py +0 -0
  99. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/tests/test_constants.py +0 -0
  100. {ha_mcp_dev-7.2.0.dev340 → ha_mcp_dev-7.2.0.dev342}/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.2.0.dev340
3
+ Version: 7.2.0.dev342
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
@@ -37,7 +37,7 @@ Dynamic: license-file
37
37
  <!-- mcp-name: io.github.homeassistant-ai/ha-mcp -->
38
38
 
39
39
  <p align="center">
40
- <img src="https://img.shields.io/badge/tools-88-blue" alt="95+ Tools">
40
+ <img src="https://img.shields.io/badge/tools-87-blue" alt="95+ Tools">
41
41
  <a href="https://github.com/homeassistant-ai/ha-mcp/releases"><img src="https://img.shields.io/github/v/release/homeassistant-ai/ha-mcp" alt="Release"></a>
42
42
  <a href="https://github.com/homeassistant-ai/ha-mcp/actions/workflows/e2e-tests.yml"><img src="https://img.shields.io/github/actions/workflow/status/homeassistant-ai/ha-mcp/e2e-tests.yml?branch=master&label=E2E%20Tests" alt="E2E Tests"></a>
43
43
  <a href="LICENSE.md"><img src="https://img.shields.io/github/license/homeassistant-ai/ha-mcp.svg" alt="License"></a>
@@ -160,7 +160,7 @@ Spend less time configuring, more time enjoying your smart home.
160
160
  <details>
161
161
  <!-- TOOLS_TABLE_START -->
162
162
 
163
- <summary><b>Complete Tool List (88 tools)</b></summary>
163
+ <summary><b>Complete Tool List (87 tools)</b></summary>
164
164
 
165
165
  | Category | Tools |
166
166
  |----------|-------|
@@ -170,7 +170,7 @@ Spend less time configuring, more time enjoying your smart home.
170
170
  | **Blueprints** | `ha_get_blueprint`, `ha_import_blueprint` |
171
171
  | **Calendar** | `ha_config_get_calendar_events`, `ha_config_remove_calendar_event`, `ha_config_set_calendar_event` |
172
172
  | **Camera** | `ha_get_camera_image` |
173
- | **Dashboards** | `ha_config_delete_dashboard_resource`, `ha_config_delete_dashboard`, `ha_config_get_dashboard`, `ha_config_list_dashboard_resources`, `ha_config_set_dashboard_resource`, `ha_config_set_dashboard`, `ha_dashboard_find_card` |
173
+ | **Dashboards** | `ha_config_delete_dashboard_resource`, `ha_config_delete_dashboard`, `ha_config_get_dashboard`, `ha_config_list_dashboard_resources`, `ha_config_set_dashboard_resource`, `ha_config_set_dashboard` |
174
174
  | **Device Registry** | `ha_get_device`, `ha_remove_device`, `ha_update_device` |
175
175
  | **Entity Registry** | `ha_get_entity_exposure`, `ha_get_entity`, `ha_remove_entity`, `ha_set_entity` |
176
176
  | **Files** | `ha_delete_file`, `ha_list_files`, `ha_read_file`, `ha_write_file` |
@@ -8,7 +8,7 @@
8
8
  <!-- mcp-name: io.github.homeassistant-ai/ha-mcp -->
9
9
 
10
10
  <p align="center">
11
- <img src="https://img.shields.io/badge/tools-88-blue" alt="95+ Tools">
11
+ <img src="https://img.shields.io/badge/tools-87-blue" alt="95+ Tools">
12
12
  <a href="https://github.com/homeassistant-ai/ha-mcp/releases"><img src="https://img.shields.io/github/v/release/homeassistant-ai/ha-mcp" alt="Release"></a>
13
13
  <a href="https://github.com/homeassistant-ai/ha-mcp/actions/workflows/e2e-tests.yml"><img src="https://img.shields.io/github/actions/workflow/status/homeassistant-ai/ha-mcp/e2e-tests.yml?branch=master&label=E2E%20Tests" alt="E2E Tests"></a>
14
14
  <a href="LICENSE.md"><img src="https://img.shields.io/github/license/homeassistant-ai/ha-mcp.svg" alt="License"></a>
@@ -131,7 +131,7 @@ Spend less time configuring, more time enjoying your smart home.
131
131
  <details>
132
132
  <!-- TOOLS_TABLE_START -->
133
133
 
134
- <summary><b>Complete Tool List (88 tools)</b></summary>
134
+ <summary><b>Complete Tool List (87 tools)</b></summary>
135
135
 
136
136
  | Category | Tools |
137
137
  |----------|-------|
@@ -141,7 +141,7 @@ Spend less time configuring, more time enjoying your smart home.
141
141
  | **Blueprints** | `ha_get_blueprint`, `ha_import_blueprint` |
142
142
  | **Calendar** | `ha_config_get_calendar_events`, `ha_config_remove_calendar_event`, `ha_config_set_calendar_event` |
143
143
  | **Camera** | `ha_get_camera_image` |
144
- | **Dashboards** | `ha_config_delete_dashboard_resource`, `ha_config_delete_dashboard`, `ha_config_get_dashboard`, `ha_config_list_dashboard_resources`, `ha_config_set_dashboard_resource`, `ha_config_set_dashboard`, `ha_dashboard_find_card` |
144
+ | **Dashboards** | `ha_config_delete_dashboard_resource`, `ha_config_delete_dashboard`, `ha_config_get_dashboard`, `ha_config_list_dashboard_resources`, `ha_config_set_dashboard_resource`, `ha_config_set_dashboard` |
145
145
  | **Device Registry** | `ha_get_device`, `ha_remove_device`, `ha_update_device` |
146
146
  | **Entity Registry** | `ha_get_entity_exposure`, `ha_get_entity`, `ha_remove_entity`, `ha_set_entity` |
147
147
  | **Files** | `ha_delete_file`, `ha_list_files`, `ha_read_file`, `ha_write_file` |
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ha-mcp-dev"
7
- version = "7.2.0.dev340"
7
+ version = "7.2.0.dev342"
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"
@@ -4,7 +4,6 @@ Configuration management tools for Home Assistant Lovelace dashboards.
4
4
  This module provides tools for managing dashboard metadata and content.
5
5
  """
6
6
 
7
- import asyncio
8
7
  import hashlib
9
8
  import json
10
9
  import logging
@@ -14,7 +13,6 @@ from typing import Annotated, Any, cast
14
13
  from fastmcp.exceptions import ToolError
15
14
  from pydantic import Field
16
15
 
17
- from ..config import get_global_settings
18
16
  from ..errors import ErrorCode, create_error_response, create_resource_not_found_error
19
17
  from ..utils.python_sandbox import (
20
18
  PythonSandboxError,
@@ -285,26 +283,78 @@ def register_config_dashboard_tools(mcp: Any, client: Any, **kwargs: Any) -> Non
285
283
  ),
286
284
  ] = False,
287
285
  force_reload: Annotated[
288
- bool, Field(description="Force reload from storage (bypass cache)")
286
+ bool, Field(description="Force reload from storage (bypass cache). Not applicable in search mode (search always uses force=True for fresh results).")
287
+ ] = False,
288
+ entity_id: Annotated[
289
+ str | None,
290
+ Field(
291
+ description="Find cards by entity ID. Supports wildcards, e.g. "
292
+ "'sensor.temperature_*'. Matches cards with this entity in "
293
+ "'entity' or 'entities' field, view-level badges, and header cards. "
294
+ "When provided, activates search mode (returns matches, not full config)."
295
+ ),
296
+ ] = None,
297
+ card_type: Annotated[
298
+ str | None,
299
+ Field(
300
+ description="Find cards by type, e.g. 'tile', 'button', 'heading'. "
301
+ "When provided, activates search mode."
302
+ ),
303
+ ] = None,
304
+ heading: Annotated[
305
+ str | None,
306
+ Field(
307
+ description="Find cards by heading/title text (case-insensitive partial match). "
308
+ "When provided, activates search mode."
309
+ ),
310
+ ] = None,
311
+ include_config: Annotated[
312
+ bool,
313
+ Field(
314
+ description="In search mode: include each matched card's own configuration "
315
+ "object in results (increases output size). Does not affect whether the full "
316
+ "dashboard config is returned — search mode always returns matches only, "
317
+ "not the full dashboard. Ignored outside search mode."
318
+ ),
289
319
  ] = False,
290
320
  ) -> dict[str, Any]:
291
321
  """
292
- Get dashboard info - list all dashboards or get config for a specific one.
322
+ Get dashboard info - list all dashboards, get config, or search for cards.
293
323
 
294
- Without url_path (or with list_only=True): Lists all storage-mode dashboards
295
- with metadata including url_path, title, icon, admin requirements.
324
+ MODE 1 List: list_only=True
325
+ Lists all storage-mode dashboards with metadata (url_path, title, icon).
296
326
 
297
- With url_path: Returns the full Lovelace dashboard configuration
298
- including all views and cards.
327
+ MODE 2 — Search: any of entity_id / card_type / heading provided
328
+ Finds cards, badges, and header cards matching the criteria.
329
+ Returns matches with jq_path for use with ha_config_set_dashboard(python_transform=...).
330
+ Multiple criteria are AND-ed. Always fetches fresh config (force=True).
331
+ Strategy dashboards are not searchable (no explicit cards).
332
+
333
+ MODE 3 — Get: Active when list_only=False and no search parameters are provided.
334
+ Returns the full Lovelace dashboard config, defaulting to the
335
+ main dashboard if url_path is omitted.
299
336
 
300
337
  EXAMPLES:
301
338
  - List all dashboards: ha_config_get_dashboard(list_only=True)
302
339
  - Get default dashboard: ha_config_get_dashboard(url_path="default")
303
340
  - Get custom dashboard: ha_config_get_dashboard(url_path="lovelace-mobile")
304
341
  - Force reload: ha_config_get_dashboard(url_path="lovelace-home", force_reload=True)
342
+ - Find cards by entity: ha_config_get_dashboard(url_path="my-dash", entity_id="light.living_room")
343
+ - Find by wildcard: ha_config_get_dashboard(url_path="my-dash", entity_id="sensor.temperature_*")
344
+ - Find by type: ha_config_get_dashboard(url_path="my-dash", card_type="tile")
345
+ - Find heading: ha_config_get_dashboard(url_path="my-dash", heading="Climate", card_type="heading")
346
+
347
+ SEARCH WORKFLOW EXAMPLE:
348
+ 1. find = ha_config_get_dashboard(url_path="my-dash", entity_id="light.bedroom")
349
+ 2. ha_config_set_dashboard(
350
+ url_path="my-dash",
351
+ config_hash=find["config_hash"],
352
+ python_transform=f'config{find["matches"][0]["jq_path"]}["icon"] = "mdi:lamp"'
353
+ )
305
354
 
306
355
  Note: YAML-mode dashboards (defined in configuration.yaml) are not included in list.
307
356
  """
357
+ search_mode = entity_id is not None or card_type is not None or heading is not None
308
358
  try:
309
359
  # List mode
310
360
  if list_only:
@@ -325,6 +375,86 @@ def register_config_dashboard_tools(mcp: Any, client: Any, **kwargs: Any) -> Non
325
375
  "count": len(dashboards),
326
376
  }
327
377
 
378
+ # Search mode — find cards, badges, or header cards
379
+ if search_mode:
380
+ get_data: dict[str, Any] = {"type": "lovelace/config", "force": True}
381
+ if url_path and url_path != "default":
382
+ get_data["url_path"] = url_path
383
+
384
+ response = await client.send_websocket_message(get_data)
385
+
386
+ if isinstance(response, dict) and not response.get("success", True):
387
+ error_msg = response.get("error", {})
388
+ if isinstance(error_msg, dict):
389
+ error_msg = error_msg.get("message", str(error_msg))
390
+ raise_tool_error(
391
+ create_error_response(
392
+ ErrorCode.SERVICE_CALL_FAILED,
393
+ f"Failed to get dashboard: {error_msg}",
394
+ suggestions=[
395
+ "Verify dashboard exists with ha_config_get_dashboard(list_only=True)",
396
+ "Check HA connection",
397
+ ],
398
+ context={"action": "find_card", "url_path": url_path},
399
+ )
400
+ )
401
+
402
+ config = (
403
+ response.get("result") if isinstance(response, dict) else response
404
+ )
405
+ if not isinstance(config, dict):
406
+ raise_tool_error(
407
+ create_error_response(
408
+ ErrorCode.SERVICE_CALL_FAILED,
409
+ "Dashboard config is empty or invalid",
410
+ suggestions=[
411
+ "Initialize dashboard with ha_config_set_dashboard"
412
+ ],
413
+ context={"action": "find_card", "url_path": url_path},
414
+ )
415
+ )
416
+
417
+ if "strategy" in config:
418
+ raise_tool_error(
419
+ create_error_response(
420
+ ErrorCode.VALIDATION_FAILED,
421
+ "Strategy dashboards have no explicit cards to search",
422
+ suggestions=[
423
+ "Use 'Take Control' in HA UI to convert to editable",
424
+ "Or create a non-strategy dashboard",
425
+ ],
426
+ context={"action": "find_card", "url_path": url_path},
427
+ )
428
+ )
429
+
430
+ matches = _find_cards_in_config(config, entity_id, card_type, heading)
431
+
432
+ if not include_config:
433
+ for match in matches:
434
+ del match["card_config"]
435
+
436
+ config_hash: str | None = _compute_config_hash(config)
437
+
438
+ return {
439
+ "success": True,
440
+ "action": "find_card",
441
+ "url_path": url_path,
442
+ "config_hash": config_hash,
443
+ "search_criteria": {
444
+ "entity_id": entity_id,
445
+ "card_type": card_type,
446
+ "heading": heading,
447
+ },
448
+ "matches": matches,
449
+ "match_count": len(matches),
450
+ "hint": (
451
+ "Use jq_path with ha_config_set_dashboard(python_transform=...) "
452
+ "for targeted updates"
453
+ if matches
454
+ else "No matches found. Try broader search criteria."
455
+ ),
456
+ }
457
+
328
458
  # Get mode - build WebSocket message
329
459
  data: dict[str, Any] = {"type": "lovelace/config", "force": force_reload}
330
460
  # Handle "default" as special value for default dashboard
@@ -375,7 +505,8 @@ def register_config_dashboard_tools(mcp: Any, client: Any, **kwargs: Any) -> Non
375
505
  if config_size >= 10000:
376
506
  result["hint"] = (
377
507
  f"Large config ({config_size:,} bytes). For edits, use "
378
- "ha_dashboard_find_card() + ha_config_set_dashboard(python_transform=...) "
508
+ "ha_config_get_dashboard(entity_id=...) to find card positions, "
509
+ "then ha_config_set_dashboard(python_transform=...) "
379
510
  "instead of full config replacement."
380
511
  )
381
512
 
@@ -383,18 +514,39 @@ def register_config_dashboard_tools(mcp: Any, client: Any, **kwargs: Any) -> Non
383
514
  except ToolError:
384
515
  raise
385
516
  except Exception as e:
386
- logger.error(f"Error getting dashboard: {e}")
387
- exception_to_structured_error(
388
- e,
389
- context={
390
- "action": "get" if not list_only else "list",
517
+ if search_mode:
518
+ logger.error(
519
+ f"Error finding card in dashboard: url_path={url_path}, "
520
+ f"entity_id={entity_id}, card_type={card_type}, heading={heading}, "
521
+ f"error={e}",
522
+ exc_info=True,
523
+ )
524
+ suggestions = [
525
+ "Check HA connection",
526
+ "Verify dashboard with ha_config_get_dashboard(list_only=True)",
527
+ ]
528
+ context: dict[str, Any] = {
529
+ "action": "find_card",
391
530
  "url_path": url_path,
392
- },
393
- suggestions=[
531
+ "entity_id": entity_id,
532
+ "card_type": card_type,
533
+ "heading": heading,
534
+ }
535
+ else:
536
+ logger.error(f"Error getting dashboard: {e}", exc_info=True)
537
+ suggestions = [
394
538
  "Use ha_config_get_dashboard(list_only=True) to see available dashboards",
395
539
  "Check if you have permission to access this dashboard",
396
540
  "Use url_path='default' for default dashboard",
397
- ],
541
+ ]
542
+ context = {
543
+ "action": "get" if not list_only else "list",
544
+ "url_path": url_path,
545
+ }
546
+ exception_to_structured_error(
547
+ e,
548
+ context=context,
549
+ suggestions=suggestions,
398
550
  )
399
551
 
400
552
  @mcp.tool(
@@ -485,10 +637,10 @@ def register_config_dashboard_tools(mcp: Any, client: Any, **kwargs: Any) -> Non
485
637
  - config: New dashboards only, or full restructure. Replaces everything.
486
638
 
487
639
  IMPORTANT: After delete/add operations, indices shift! Subsequent python_transform calls
488
- must use fresh config_hash from ha_dashboard_find_card() or ha_config_get_dashboard()
640
+ must use fresh config_hash from ha_config_get_dashboard()
489
641
  to get updated structure. Chain multiple ops in ONE expression when possible.
490
642
 
491
- TIP: Use ha_dashboard_find_card() to get the path for any card.
643
+ TIP: Use ha_config_get_dashboard(entity_id=...) to get the path for any card.
492
644
 
493
645
  PYTHON TRANSFORM EXAMPLES (RECOMMENDED):
494
646
  - Update card icon: 'config["views"][0]["cards"][0]["icon"] = "mdi:thermometer"'
@@ -1142,220 +1294,3 @@ def register_config_dashboard_tools(mcp: Any, client: Any, **kwargs: Any) -> Non
1142
1294
  # - ha_config_delete_dashboard_resource: Delete resources
1143
1295
  # =========================================================================
1144
1296
 
1145
- # =========================================================================
1146
- # Card Search Tool (partial update tools - controlled by feature flag)
1147
- # Card add/update/remove replaced by python_transform in ha_config_set_dashboard
1148
- # =========================================================================
1149
-
1150
- # Check feature flag for partial update tools (lazy check, default enabled)
1151
- try:
1152
- settings = get_global_settings()
1153
- if not settings.enable_dashboard_partial_tools:
1154
- return # Skip registering find_card if partial tools disabled
1155
- except Exception:
1156
- pass # Default: register the tool if settings unavailable
1157
-
1158
- @mcp.tool(
1159
- tags={"Dashboards"},
1160
- annotations={
1161
- "idempotentHint": True,
1162
- "readOnlyHint": True,
1163
- "title": "Find Dashboard Card"
1164
- }
1165
- )
1166
- @log_tool_usage
1167
- async def ha_dashboard_find_card(
1168
- url_path: Annotated[
1169
- str | None,
1170
- Field(
1171
- description="Dashboard URL path, e.g. 'lovelace-home'. Omit for default."
1172
- ),
1173
- ] = None,
1174
- entity_id: Annotated[
1175
- str | None,
1176
- Field(
1177
- description="Find cards by entity ID. Supports wildcards, e.g. 'sensor.temperature_*'. "
1178
- "Matches cards with this entity in 'entity' or 'entities' field."
1179
- ),
1180
- ] = None,
1181
- card_type: Annotated[
1182
- str | None,
1183
- Field(description="Find cards by type, e.g. 'tile', 'button', 'heading'."),
1184
- ] = None,
1185
- heading: Annotated[
1186
- str | None,
1187
- Field(
1188
- description="Find cards by heading/title text (case-insensitive partial match). "
1189
- "Useful for finding section headings (type: 'heading')."
1190
- ),
1191
- ] = None,
1192
- include_config: Annotated[
1193
- bool,
1194
- Field(
1195
- description="Include full card configuration in results (increases output size)."
1196
- ),
1197
- ] = False,
1198
- ) -> dict[str, Any]:
1199
- """
1200
- Find cards, badges, and header cards in a dashboard by entity_id, type, or heading text.
1201
-
1202
- Returns card/badge/header locations (view_index, section_index, card_index/badge_index)
1203
- and path for use with ha_config_set_dashboard(python_transform=...).
1204
-
1205
- Also searches view-level badges (views[n].badges) and sections-view header cards
1206
- (views[n].header.card). Badges are the chip row at the top of a view, and header
1207
- cards are Markdown cards in the view header — both reference entities and are
1208
- often missed during entity rename operations.
1209
-
1210
- Use this tool BEFORE targeted updates to find exact card positions without
1211
- manually parsing the full dashboard config.
1212
-
1213
- SEARCH CRITERIA (at least one required):
1214
- - entity_id: Match cards and badges containing this entity (supports wildcards with *)
1215
- - card_type: Match cards of this type (e.g., 'tile', 'button', 'heading')
1216
- - heading: Match cards with this text in heading/title (partial, case-insensitive)
1217
-
1218
- Multiple criteria are AND-ed together.
1219
-
1220
- EXAMPLES:
1221
-
1222
- Find all tile cards:
1223
- ha_dashboard_find_card(url_path="my-dashboard", card_type="tile")
1224
-
1225
- Find cards for a specific entity:
1226
- ha_dashboard_find_card(url_path="my-dashboard", entity_id="light.living_room")
1227
-
1228
- Find all temperature sensors (wildcard):
1229
- ha_dashboard_find_card(url_path="my-dashboard", entity_id="sensor.temperature_*")
1230
-
1231
- Find the "Climate" section heading:
1232
- ha_dashboard_find_card(url_path="my-dashboard", heading="Climate", card_type="heading")
1233
-
1234
- WORKFLOW EXAMPLE:
1235
- 1. find = ha_dashboard_find_card(url_path="my-dash", entity_id="light.bedroom")
1236
- 2. # Use jq_path and config_hash from result to update:
1237
- 3. ha_config_set_dashboard(
1238
- url_path="my-dash",
1239
- config_hash=find["config_hash"],
1240
- python_transform=f'config{find["matches"][0]["jq_path"]}["icon"] = "mdi:lamp"'
1241
- )
1242
- """
1243
- try:
1244
- # Validate at least one search criteria
1245
- if entity_id is None and card_type is None and heading is None:
1246
- raise_tool_error(
1247
- create_error_response(
1248
- ErrorCode.VALIDATION_INVALID_PARAMETER,
1249
- "At least one search criteria required",
1250
- suggestions=[
1251
- "Provide entity_id, card_type, or heading parameter",
1252
- "Use entity_id='sensor.*' to find all sensor cards",
1253
- "Use card_type='heading' to find section headings",
1254
- ],
1255
- context={"action": "find_card"},
1256
- )
1257
- )
1258
-
1259
- # Fetch dashboard config
1260
- get_data: dict[str, Any] = {"type": "lovelace/config", "force": True}
1261
- if url_path:
1262
- get_data["url_path"] = url_path
1263
-
1264
- response = await client.send_websocket_message(get_data)
1265
-
1266
- if isinstance(response, dict) and not response.get("success", True):
1267
- error_msg = response.get("error", {})
1268
- if isinstance(error_msg, dict):
1269
- error_msg = error_msg.get("message", str(error_msg))
1270
- raise_tool_error(
1271
- create_error_response(
1272
- ErrorCode.SERVICE_CALL_FAILED,
1273
- f"Failed to get dashboard: {error_msg}",
1274
- suggestions=[
1275
- "Verify dashboard exists with ha_config_get_dashboard(list_only=True)",
1276
- "Check HA connection",
1277
- ],
1278
- context={"action": "find_card", "url_path": url_path},
1279
- )
1280
- )
1281
-
1282
- config = response.get("result") if isinstance(response, dict) else response
1283
- if not isinstance(config, dict):
1284
- raise_tool_error(
1285
- create_error_response(
1286
- ErrorCode.SERVICE_CALL_FAILED,
1287
- "Dashboard config is empty or invalid",
1288
- suggestions=[
1289
- "Initialize dashboard with ha_config_set_dashboard"
1290
- ],
1291
- context={"action": "find_card", "url_path": url_path},
1292
- )
1293
- )
1294
-
1295
- # Check for strategy dashboard
1296
- if "strategy" in config:
1297
- raise_tool_error(
1298
- create_error_response(
1299
- ErrorCode.VALIDATION_FAILED,
1300
- "Strategy dashboards have no explicit cards to search",
1301
- suggestions=[
1302
- "Use 'Take Control' in HA UI to convert to editable",
1303
- "Or create a non-strategy dashboard",
1304
- ],
1305
- context={"action": "find_card", "url_path": url_path},
1306
- )
1307
- )
1308
-
1309
- # Find matching cards
1310
- matches = _find_cards_in_config(config, entity_id, card_type, heading)
1311
-
1312
- # Optionally strip config to reduce output size
1313
- if not include_config:
1314
- for match in matches:
1315
- del match["card_config"]
1316
-
1317
- # Compute config hash for potential follow-up operations
1318
- config_hash = _compute_config_hash(config)
1319
-
1320
- return {
1321
- "success": True,
1322
- "action": "find_card",
1323
- "url_path": url_path,
1324
- "config_hash": config_hash,
1325
- "search_criteria": {
1326
- "entity_id": entity_id,
1327
- "card_type": card_type,
1328
- "heading": heading,
1329
- },
1330
- "matches": matches,
1331
- "match_count": len(matches),
1332
- "hint": "Use jq_path with ha_config_set_dashboard(python_transform=...) for targeted updates"
1333
- if matches
1334
- else "No matches found. Try broader search criteria.",
1335
- }
1336
-
1337
- except asyncio.CancelledError:
1338
- raise
1339
- except ToolError:
1340
- raise
1341
- except Exception as e:
1342
- logger.error(
1343
- f"Error finding card: url_path={url_path}, "
1344
- f"entity_id={entity_id}, card_type={card_type}, heading={heading}, "
1345
- f"error={e}",
1346
- exc_info=True,
1347
- )
1348
- exception_to_structured_error(
1349
- e,
1350
- context={
1351
- "action": "find_card",
1352
- "url_path": url_path,
1353
- "entity_id": entity_id,
1354
- "card_type": card_type,
1355
- "heading": heading,
1356
- },
1357
- suggestions=[
1358
- "Check HA connection",
1359
- "Verify dashboard with ha_config_get_dashboard(list_only=True)",
1360
- ],
1361
- )
@@ -123,8 +123,10 @@ def register_yaml_config_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
123
123
  a full HA restart ('restart_required'). Only template, mqtt, and
124
124
  group support reload ('reload_available' with 'reload_service').
125
125
 
126
- YAML comments and Home Assistant tags (!include, !secret, etc.)
127
- are preserved through edits.
126
+ Preserves YAML comments on sibling keys, file-level comments,
127
+ and Home Assistant tags (!include, !secret, etc.). The 'replace' action
128
+ substitutes the subtree as-is, so comments from the old subtree
129
+ do not carry over.
128
130
  """
129
131
  try:
130
132
  # Validate action
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ha-mcp-dev
3
- Version: 7.2.0.dev340
3
+ Version: 7.2.0.dev342
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
@@ -37,7 +37,7 @@ Dynamic: license-file
37
37
  <!-- mcp-name: io.github.homeassistant-ai/ha-mcp -->
38
38
 
39
39
  <p align="center">
40
- <img src="https://img.shields.io/badge/tools-88-blue" alt="95+ Tools">
40
+ <img src="https://img.shields.io/badge/tools-87-blue" alt="95+ Tools">
41
41
  <a href="https://github.com/homeassistant-ai/ha-mcp/releases"><img src="https://img.shields.io/github/v/release/homeassistant-ai/ha-mcp" alt="Release"></a>
42
42
  <a href="https://github.com/homeassistant-ai/ha-mcp/actions/workflows/e2e-tests.yml"><img src="https://img.shields.io/github/actions/workflow/status/homeassistant-ai/ha-mcp/e2e-tests.yml?branch=master&label=E2E%20Tests" alt="E2E Tests"></a>
43
43
  <a href="LICENSE.md"><img src="https://img.shields.io/github/license/homeassistant-ai/ha-mcp.svg" alt="License"></a>
@@ -160,7 +160,7 @@ Spend less time configuring, more time enjoying your smart home.
160
160
  <details>
161
161
  <!-- TOOLS_TABLE_START -->
162
162
 
163
- <summary><b>Complete Tool List (88 tools)</b></summary>
163
+ <summary><b>Complete Tool List (87 tools)</b></summary>
164
164
 
165
165
  | Category | Tools |
166
166
  |----------|-------|
@@ -170,7 +170,7 @@ Spend less time configuring, more time enjoying your smart home.
170
170
  | **Blueprints** | `ha_get_blueprint`, `ha_import_blueprint` |
171
171
  | **Calendar** | `ha_config_get_calendar_events`, `ha_config_remove_calendar_event`, `ha_config_set_calendar_event` |
172
172
  | **Camera** | `ha_get_camera_image` |
173
- | **Dashboards** | `ha_config_delete_dashboard_resource`, `ha_config_delete_dashboard`, `ha_config_get_dashboard`, `ha_config_list_dashboard_resources`, `ha_config_set_dashboard_resource`, `ha_config_set_dashboard`, `ha_dashboard_find_card` |
173
+ | **Dashboards** | `ha_config_delete_dashboard_resource`, `ha_config_delete_dashboard`, `ha_config_get_dashboard`, `ha_config_list_dashboard_resources`, `ha_config_set_dashboard_resource`, `ha_config_set_dashboard` |
174
174
  | **Device Registry** | `ha_get_device`, `ha_remove_device`, `ha_update_device` |
175
175
  | **Entity Registry** | `ha_get_entity_exposure`, `ha_get_entity`, `ha_remove_entity`, `ha_set_entity` |
176
176
  | **Files** | `ha_delete_file`, `ha_list_files`, `ha_read_file`, `ha_write_file` |