ha-mcp-dev 7.2.0.dev331__tar.gz → 7.2.0.dev332__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.dev331/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.2.0.dev332}/PKG-INFO +3 -3
  2. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/README.md +2 -2
  3. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/pyproject.toml +1 -1
  4. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/tools/backup.py +24 -23
  5. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/tools/device_control.py +22 -20
  6. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/tools/tools_entities.py +26 -51
  7. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332/src/ha_mcp_dev.egg-info}/PKG-INFO +3 -3
  8. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/LICENSE +0 -0
  9. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/MANIFEST.in +0 -0
  10. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/setup.cfg +0 -0
  11. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/__init__.py +0 -0
  12. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/__main__.py +0 -0
  13. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/_pypi_marker +0 -0
  14. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/auth/__init__.py +0 -0
  15. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/auth/consent_form.py +0 -0
  16. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/auth/provider.py +0 -0
  17. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/client/__init__.py +0 -0
  18. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/client/rest_client.py +0 -0
  19. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/client/websocket_client.py +0 -0
  20. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/client/websocket_listener.py +0 -0
  21. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/config.py +0 -0
  22. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/errors.py +0 -0
  23. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/py.typed +0 -0
  24. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
  25. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
  26. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
  27. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
  28. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
  29. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
  30. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
  31. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
  32. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
  33. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
  34. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
  35. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
  36. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
  37. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
  38. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
  39. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
  40. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
  41. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
  42. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
  43. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
  44. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/server.py +0 -0
  45. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/smoke_test.py +0 -0
  46. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/tools/__init__.py +0 -0
  47. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/tools/best_practice_checker.py +0 -0
  48. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/tools/enhanced.py +0 -0
  49. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/tools/helpers.py +0 -0
  50. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/tools/registry.py +0 -0
  51. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/tools/smart_search.py +0 -0
  52. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/tools/tools_addons.py +0 -0
  53. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/tools/tools_areas.py +0 -0
  54. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/tools/tools_blueprints.py +0 -0
  55. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/tools/tools_bug_report.py +0 -0
  56. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/tools/tools_calendar.py +0 -0
  57. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/tools/tools_camera.py +0 -0
  58. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/tools/tools_categories.py +0 -0
  59. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/tools/tools_config_automations.py +0 -0
  60. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
  61. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/tools/tools_config_entry_flow.py +0 -0
  62. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
  63. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
  64. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/tools/tools_filesystem.py +0 -0
  65. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/tools/tools_groups.py +0 -0
  66. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/tools/tools_hacs.py +0 -0
  67. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/tools/tools_history.py +0 -0
  68. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/tools/tools_integrations.py +0 -0
  69. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/tools/tools_labels.py +0 -0
  70. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/tools/tools_mcp_component.py +0 -0
  71. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/tools/tools_registry.py +0 -0
  72. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/tools/tools_resources.py +0 -0
  73. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/tools/tools_search.py +0 -0
  74. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/tools/tools_service.py +0 -0
  75. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/tools/tools_services.py +0 -0
  76. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/tools/tools_system.py +0 -0
  77. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/tools/tools_todo.py +0 -0
  78. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/tools/tools_traces.py +0 -0
  79. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/tools/tools_updates.py +0 -0
  80. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/tools/tools_utility.py +0 -0
  81. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
  82. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
  83. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/tools/tools_zones.py +0 -0
  84. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/tools/util_helpers.py +0 -0
  85. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/transforms/__init__.py +0 -0
  86. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/transforms/categorized_search.py +0 -0
  87. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/utils/__init__.py +0 -0
  88. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/utils/domain_handlers.py +0 -0
  89. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/utils/fuzzy_search.py +0 -0
  90. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/utils/operation_manager.py +0 -0
  91. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/utils/python_sandbox.py +0 -0
  92. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp/utils/usage_logger.py +0 -0
  93. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
  94. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
  95. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
  96. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
  97. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
  98. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/tests/__init__.py +0 -0
  99. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/tests/test_constants.py +0 -0
  100. {ha_mcp_dev-7.2.0.dev331 → ha_mcp_dev-7.2.0.dev332}/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.dev331
3
+ Version: 7.2.0.dev332
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-93-blue" alt="95+ Tools">
40
+ <img src="https://img.shields.io/badge/tools-91-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 (93 tools)</b></summary>
163
+ <summary><b>Complete Tool List (91 tools)</b></summary>
164
164
 
165
165
  | Category | Tools |
166
166
  |----------|-------|
@@ -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-93-blue" alt="95+ Tools">
11
+ <img src="https://img.shields.io/badge/tools-91-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 (93 tools)</b></summary>
134
+ <summary><b>Complete Tool List (91 tools)</b></summary>
135
135
 
136
136
  | Category | Tools |
137
137
  |----------|-------|
@@ -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.dev331"
7
+ version = "7.2.0.dev332"
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"
@@ -51,7 +51,7 @@ def _get_backup_hint_text() -> str:
51
51
 
52
52
  async def _get_backup_password(
53
53
  ws_client: HomeAssistantWebSocketClient,
54
- ) -> tuple[str | None, dict[str, Any] | None]:
54
+ ) -> str:
55
55
  """
56
56
  Retrieve default backup password from Home Assistant configuration.
57
57
 
@@ -59,27 +59,30 @@ async def _get_backup_password(
59
59
  ws_client: Connected WebSocket client
60
60
 
61
61
  Returns:
62
- Tuple of (password, error_dict). If retrieval fails, password is None.
62
+ The backup password string.
63
+
64
+ Raises:
65
+ ToolError: If backup config cannot be retrieved or no password is configured.
63
66
  """
64
67
  backup_config = await ws_client.send_command("backup/config/info")
65
68
  if not backup_config.get("success"):
66
- return None, {
67
- "success": False,
68
- "error": "Failed to retrieve backup configuration",
69
- "details": backup_config,
70
- }
69
+ raise_tool_error(create_error_response(
70
+ ErrorCode.SERVICE_CALL_FAILED,
71
+ "Failed to retrieve backup configuration",
72
+ context={"details": backup_config},
73
+ ))
71
74
 
72
75
  config_data = backup_config.get("result", {}).get("config", {})
73
76
  default_password = config_data.get("create_backup", {}).get("password")
74
77
 
75
78
  if not default_password:
76
- return None, {
77
- "success": False,
78
- "error": "No default backup password configured in Home Assistant",
79
- "suggestion": "Configure automatic backups in Home Assistant settings to set a default password",
80
- }
79
+ raise_tool_error(create_error_response(
80
+ ErrorCode.SERVICE_CALL_FAILED,
81
+ "No default backup password configured in Home Assistant",
82
+ suggestions=["Configure automatic backups in Home Assistant settings to set a default password"],
83
+ ))
81
84
 
82
- return default_password, None
85
+ return cast(str, default_password)
83
86
 
84
87
 
85
88
  async def create_backup(
@@ -107,13 +110,8 @@ async def create_backup(
107
110
  ))
108
111
  ws_client = cast(HomeAssistantWebSocketClient, ws_client)
109
112
 
110
- # Get backup password
111
- password, error = await _get_backup_password(ws_client)
112
- if error:
113
- raise_tool_error(create_error_response(
114
- ErrorCode.SERVICE_CALL_FAILED,
115
- error.get("error", "Failed to retrieve backup password"),
116
- ))
113
+ # Get backup password (raises ToolError on failure)
114
+ password = await _get_backup_password(ws_client)
117
115
 
118
116
  # Generate backup name if not provided
119
117
  if not name:
@@ -283,12 +281,15 @@ async def restore_backup(
283
281
  safety_backup_name = f"PreRestore_Safety_{now.strftime('%Y-%m-%d_%H:%M:%S')}"
284
282
 
285
283
  # Get backup password
286
- password, error = await _get_backup_password(ws_client)
287
- if error:
284
+ try:
285
+ password = await _get_backup_password(ws_client)
286
+ except ToolError:
288
287
  # Password error - log warning but continue (restore might still work)
289
288
  logger.warning("No default password - proceeding without safety backup")
289
+ password = None
290
290
  safety_backup_id = None
291
- else:
291
+
292
+ if password is not None:
292
293
  safety_backup = await ws_client.send_command(
293
294
  "backup/generate",
294
295
  name=safety_backup_name,
@@ -421,38 +421,40 @@ class DeviceControlTools:
421
421
  }
422
422
 
423
423
  elif operation.status.value == "failed":
424
- return {
425
- "operation_id": operation_id,
426
- "status": "failed",
427
- "success": False,
428
- "entity_id": operation.entity_id,
429
- "action": operation.action,
430
- "error": operation.error_message,
431
- "duration_ms": operation.duration_ms,
432
- "suggestions": [
424
+ raise_tool_error(create_error_response(
425
+ ErrorCode.SERVICE_CALL_FAILED,
426
+ operation.error_message or "Device operation failed",
427
+ context={
428
+ "operation_id": operation_id,
429
+ "entity_id": operation.entity_id,
430
+ "action": operation.action,
431
+ "duration_ms": operation.duration_ms,
432
+ },
433
+ suggestions=[
433
434
  "Check if device is available and responding",
434
435
  "Verify device supports the requested action",
435
436
  "Check Home Assistant logs for error details",
436
437
  "Try a simpler action like toggle",
437
438
  ],
438
- }
439
+ ))
439
440
 
440
441
  elif operation.status.value == "timeout":
441
- return {
442
- "operation_id": operation_id,
443
- "status": "timeout",
444
- "success": False,
445
- "entity_id": operation.entity_id,
446
- "action": operation.action,
447
- "error": f"Operation timed out after {operation.timeout_ms}ms",
448
- "elapsed_ms": operation.elapsed_ms,
449
- "suggestions": [
442
+ raise_tool_error(create_error_response(
443
+ ErrorCode.TIMEOUT_OPERATION,
444
+ f"Operation timed out after {operation.timeout_ms}ms",
445
+ context={
446
+ "operation_id": operation_id,
447
+ "entity_id": operation.entity_id,
448
+ "action": operation.action,
449
+ "elapsed_ms": operation.elapsed_ms,
450
+ },
451
+ suggestions=[
450
452
  "Device may be slow to respond or offline",
451
453
  "Check device connectivity",
452
454
  "Try increasing timeout for slow devices",
453
455
  "Verify device is powered on",
454
456
  ],
455
- }
457
+ ))
456
458
 
457
459
  else: # pending
458
460
  return {
@@ -240,21 +240,20 @@ def register_entity_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
240
240
  else str(error)
241
241
  )
242
242
  failed = dict.fromkeys(assistants, should_expose)
243
- response: dict[str, Any] = {
244
- "success": False,
245
- "error": {
246
- "code": ErrorCode.SERVICE_CALL_FAILED.value,
247
- "message": f"Exposure failed: {error_msg}",
248
- "suggestion": "Check Home Assistant connection and entity availability",
249
- },
243
+ context: dict[str, Any] = {
250
244
  "entity_id": entity_id,
251
245
  "exposure_succeeded": succeeded,
252
246
  "exposure_failed": failed,
253
247
  }
254
248
  if has_registry_updates:
255
- response["partial"] = True
256
- response["entity_entry"] = _format_entity_entry(entity_entry)
257
- return response
249
+ context["partial"] = True
250
+ context["entity_entry"] = _format_entity_entry(entity_entry)
251
+ raise_tool_error(create_error_response(
252
+ ErrorCode.SERVICE_CALL_FAILED,
253
+ f"Exposure failed: {error_msg}",
254
+ context=context,
255
+ suggestions=["Check Home Assistant connection and entity availability"],
256
+ ))
258
257
 
259
258
  # Track successful exposures
260
259
  for a in assistants:
@@ -272,21 +271,15 @@ def register_entity_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
272
271
  if get_result.get("success"):
273
272
  entity_entry = get_result.get("result", {})
274
273
  else:
275
- # Return plain dict so caller can inspect exposure_succeeded
276
- return {
277
- "success": False,
278
- "error": {
279
- "code": ErrorCode.ENTITY_NOT_FOUND.value,
280
- "message": f"Entity '{entity_id}' not found in registry after applying exposure changes",
281
- "suggestion": "Use ha_search_entities() to verify the entity exists",
282
- },
283
- "entity_id": entity_id,
284
- "suggestions": [
274
+ raise_tool_error(create_error_response(
275
+ ErrorCode.ENTITY_NOT_FOUND,
276
+ f"Entity '{entity_id}' not found in registry after applying exposure changes",
277
+ context={"entity_id": entity_id, "exposure_succeeded": exposure_result},
278
+ suggestions=[
285
279
  "Verify the entity_id exists using ha_search_entities()",
286
280
  "The entity's exposure settings were likely changed, but its current state could not be confirmed.",
287
281
  ],
288
- "exposure_succeeded": exposure_result,
289
- }
282
+ ))
290
283
 
291
284
  response_data: dict[str, Any] = {
292
285
  "success": True,
@@ -831,15 +824,10 @@ def register_entity_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
831
824
  if isinstance(error, dict)
832
825
  else str(error)
833
826
  )
834
- return {
835
- "success": False,
836
- "entity_id": eid,
837
- "error": error_msg,
838
- }
827
+ raise ValueError(error_msg)
839
828
 
840
829
  entry = result.get("result", {})
841
830
  return {
842
- "success": True,
843
831
  "entity_id": entry.get("entity_id"),
844
832
  "name": entry.get("name"),
845
833
  "original_name": entry.get("original_name"),
@@ -861,21 +849,13 @@ def register_entity_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
861
849
  if not is_bulk:
862
850
  eid = entity_ids[0]
863
851
  logger.info(f"Getting entity registry entry for {eid}")
864
- result = await _fetch_entity(eid)
865
-
866
- if result.get("success"):
867
- return {
868
- "success": True,
869
- "entity_id": eid,
870
- "entity_entry": {
871
- k: v for k, v in result.items() if k not in ("success",)
872
- },
873
- }
874
- else:
852
+ try:
853
+ result = await _fetch_entity(eid)
854
+ except ValueError as e:
875
855
  raise_tool_error(
876
856
  create_error_response(
877
857
  ErrorCode.SERVICE_CALL_FAILED,
878
- f"Entity not found: {result.get('error', 'Unknown error')}",
858
+ f"Entity not found: {e}",
879
859
  context={"entity_id": eid},
880
860
  suggestions=[
881
861
  "Use ha_search_entities() to find valid entity IDs",
@@ -883,6 +863,11 @@ def register_entity_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
883
863
  ],
884
864
  )
885
865
  )
866
+ return {
867
+ "success": True,
868
+ "entity_id": eid,
869
+ "entity_entry": result,
870
+ }
886
871
 
887
872
  # Bulk case - fetch all entities
888
873
  logger.info(
@@ -904,18 +889,8 @@ def register_entity_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
904
889
  "error": str(fetch_result),
905
890
  }
906
891
  )
907
- continue
908
- if fetch_result.get("success"):
909
- entity_entries.append(
910
- {k: v for k, v in fetch_result.items() if k not in ("success",)}
911
- )
912
892
  else:
913
- errors.append(
914
- {
915
- "entity_id": eid,
916
- "error": fetch_result.get("error", "Unknown error"),
917
- }
918
- )
893
+ entity_entries.append(fetch_result)
919
894
 
920
895
  response: dict[str, Any] = {
921
896
  "success": True,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ha-mcp-dev
3
- Version: 7.2.0.dev331
3
+ Version: 7.2.0.dev332
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-93-blue" alt="95+ Tools">
40
+ <img src="https://img.shields.io/badge/tools-91-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 (93 tools)</b></summary>
163
+ <summary><b>Complete Tool List (91 tools)</b></summary>
164
164
 
165
165
  | Category | Tools |
166
166
  |----------|-------|