ha-mcp-dev 7.4.1.dev484__py3-none-any.whl → 7.4.1.dev485__py3-none-any.whl

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.
ha_mcp/tools/backup.py CHANGED
@@ -52,6 +52,67 @@ def _get_backup_hint_text() -> str:
52
52
  return hints.get(hint, hints["normal"])
53
53
 
54
54
 
55
+ async def _get_local_backup_agent_id(
56
+ ws_client: HomeAssistantWebSocketClient,
57
+ ) -> str:
58
+ """Discover the local backup agent_id at call time.
59
+
60
+ HA Supervised registers ``hassio.local`` and HA Core registers
61
+ ``backup.local`` — both have ``name: "local"``. Hardcoding either breaks
62
+ the other deployment. We probe ``backup/agents/info`` and pick the agent
63
+ whose name is exactly ``"local"``, preferring ``hassio.local`` if both
64
+ happen to be registered.
65
+
66
+ Raises ToolError if no local agent is available.
67
+ """
68
+ response = await ws_client.send_command("backup/agents/info")
69
+ if not response.get("success"):
70
+ raise_tool_error(
71
+ create_error_response(
72
+ ErrorCode.SERVICE_CALL_FAILED,
73
+ "Failed to enumerate backup agents",
74
+ context={"details": response},
75
+ )
76
+ )
77
+
78
+ agents = response.get("result", {}).get("agents", [])
79
+ if not agents:
80
+ raise_tool_error(
81
+ create_error_response(
82
+ ErrorCode.SERVICE_CALL_FAILED,
83
+ "No backup agents registered with Home Assistant",
84
+ suggestions=[
85
+ "The HA backup integration may not be fully set up; "
86
+ "check the backup panel in Home Assistant",
87
+ ],
88
+ )
89
+ )
90
+
91
+ local_agents: list[str] = [
92
+ a["agent_id"]
93
+ for a in agents
94
+ if a.get("name") == "local" and a.get("agent_id")
95
+ ]
96
+ # Prefer hassio.local (Supervisor) over backup.local (Core) when both exist
97
+ for preferred in ("hassio.local", "backup.local"):
98
+ if preferred in local_agents:
99
+ return preferred
100
+ if local_agents:
101
+ return local_agents[0]
102
+
103
+ raise_tool_error(
104
+ create_error_response(
105
+ ErrorCode.SERVICE_CALL_FAILED,
106
+ "No local backup agent found",
107
+ context={"available_agents": [a.get("agent_id") for a in agents if a.get("agent_id")]},
108
+ suggestions=[
109
+ "Backup creation requires a local agent (hassio.local on "
110
+ "Supervised, backup.local on Core); none is registered",
111
+ ],
112
+ )
113
+ )
114
+
115
+
55
116
  async def _get_backup_password(
56
117
  ws_client: HomeAssistantWebSocketClient,
57
118
  ) -> str:
@@ -94,9 +155,14 @@ async def _poll_backup_completion(
94
155
  backup_job_id: str,
95
156
  max_wait_seconds: int,
96
157
  poll_interval: int,
158
+ agent_id: str,
97
159
  ) -> dict[str, Any]:
98
160
  """Poll backup/info until the named backup completes, fails, or times out.
99
161
 
162
+ ``agent_id`` is the local agent that owns this backup (e.g.
163
+ ``hassio.local`` on Supervised, ``backup.local`` on Core); used to look
164
+ up the per-agent size in the backup-info payload.
165
+
100
166
  Raises ToolError on backup failure or timeout.
101
167
  """
102
168
  waited = 0
@@ -134,7 +200,7 @@ async def _poll_backup_completion(
134
200
  "name": name,
135
201
  "date": created_backup.get("date"),
136
202
  "size_bytes": created_backup.get("agents", {})
137
- .get("hassio.local", {})
203
+ .get(agent_id, {})
138
204
  .get("size"),
139
205
  "status": "Backup completed successfully",
140
206
  "duration_seconds": waited,
@@ -192,19 +258,31 @@ async def create_backup(
192
258
  # Get backup password (raises ToolError on failure)
193
259
  password = await _get_backup_password(ws_client)
194
260
 
261
+ # Discover the local backup agent at call time. HA Core registers
262
+ # `backup.local`; HA Supervised registers `hassio.local`. Hardcoding
263
+ # either breaks the other deployment.
264
+ local_agent = await _get_local_backup_agent_id(ws_client)
265
+
195
266
  # Generate backup name if not provided
196
267
  if not name:
197
268
  now = datetime.now()
198
269
  name = f"MCP_Backup_{now.strftime('%Y-%m-%d_%H:%M:%S')}"
199
270
 
200
- # Create backup request
271
+ # Addons + addon folders are Supervisor concepts — HA Core errors
272
+ # with "Addons and folders are not supported by core backup" if we
273
+ # ask for them. Toggle off when we detect the Core local agent.
274
+ is_supervised = local_agent == "hassio.local"
275
+ logger.info(
276
+ f"Detected {'Supervised' if is_supervised else 'Core'} install "
277
+ f"via backup agent '{local_agent}'"
278
+ )
201
279
  backup_params = {
202
280
  "name": name,
203
281
  "password": password,
204
- "agent_ids": ["hassio.local"], # Local only
282
+ "agent_ids": [local_agent],
205
283
  "include_homeassistant": True,
206
284
  "include_database": False, # Fast backup
207
- "include_all_addons": True,
285
+ "include_all_addons": is_supervised,
208
286
  }
209
287
 
210
288
  # Send backup request
@@ -225,6 +303,7 @@ async def create_backup(
225
303
  backup_job_id,
226
304
  max_wait_seconds=_BACKUP_MAX_WAIT_S,
227
305
  poll_interval=_BACKUP_POLL_INTERVAL_S,
306
+ agent_id=local_agent,
228
307
  )
229
308
 
230
309
  except ToolError:
@@ -248,9 +327,13 @@ async def create_backup(
248
327
  async def _create_safety_backup(
249
328
  ws_client: HomeAssistantWebSocketClient,
250
329
  password: str | None,
330
+ agent_id: str,
251
331
  ) -> str | None:
252
332
  """Create a pre-restore safety backup.
253
333
 
334
+ ``agent_id`` is the local backup agent (Supervisor's ``hassio.local`` or
335
+ Core's ``backup.local``) discovered by the caller.
336
+
254
337
  Returns the safety backup ID, or None when password is None (backup intentionally
255
338
  skipped). Raises ToolError if backup creation fails.
256
339
  """
@@ -260,14 +343,16 @@ async def _create_safety_backup(
260
343
  now = datetime.now()
261
344
  safety_backup_name = f"PreRestore_Safety_{now.strftime('%Y-%m-%d_%H:%M:%S')}"
262
345
 
346
+ # include_all_addons is a Supervisor concept; HA Core rejects it.
347
+ is_supervised = agent_id == "hassio.local"
263
348
  safety_backup = await ws_client.send_command(
264
349
  "backup/generate",
265
350
  name=safety_backup_name,
266
351
  password=password,
267
- agent_ids=["hassio.local"],
352
+ agent_ids=[agent_id],
268
353
  include_homeassistant=True,
269
354
  include_database=True,
270
- include_all_addons=True,
355
+ include_all_addons=is_supervised,
271
356
  )
272
357
 
273
358
  if not safety_backup.get("success"):
@@ -330,6 +415,11 @@ async def restore_backup(
330
415
  suggestions=["Use ha_backup_list() to see available backups"],
331
416
  ))
332
417
 
418
+ # Discover the local backup agent (Supervisor's hassio.local on
419
+ # Supervised, backup.local on Core). Used for both the safety backup
420
+ # and the restore call below.
421
+ local_agent = await _get_local_backup_agent_id(ws_client)
422
+
333
423
  # Create safety backup BEFORE restoring
334
424
  logger.info("Creating safety backup before restore...")
335
425
  try:
@@ -339,12 +429,12 @@ async def restore_backup(
339
429
  logger.warning("No default password - proceeding without safety backup")
340
430
  password = None
341
431
 
342
- safety_backup_id = await _create_safety_backup(ws_client, password)
432
+ safety_backup_id = await _create_safety_backup(ws_client, password, local_agent)
343
433
 
344
434
  # Perform restore
345
435
  restore_params = {
346
436
  "backup_id": backup_id,
347
- "agent_id": "hassio.local",
437
+ "agent_id": local_agent,
348
438
  "restore_database": restore_database,
349
439
  "restore_homeassistant": True,
350
440
  "restore_addons": [], # Restore all addons from backup
@@ -409,7 +499,7 @@ def register_backup_tools(mcp: "FastMCP", client: HomeAssistantClient, **kwargs:
409
499
 
410
500
  **Password:** Uses Home Assistant's default backup password (if configured)
411
501
 
412
- **Storage:** Local only (hassio.local agent)
502
+ **Storage:** Local only (the local backup agent — `hassio.local` on HA Supervised, `backup.local` on HA Core)
413
503
 
414
504
  **Duration:** Typically takes several seconds to complete (without database)
415
505
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ha-mcp-dev
3
- Version: 7.4.1.dev484
3
+ Version: 7.4.1.dev485
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 @@ ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/h
37
37
  ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md,sha256=RVkY-J-ZrBDDD3INHwbbkkPocukDgNg8eOuApswRlqk,8014
38
38
  ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md,sha256=rrcM8KrJ0hZw1jd4PPsVnE4rTFnsA1uA7SgltVjewkw,17856
39
39
  ha_mcp/tools/__init__.py,sha256=79ML0aicKZ5WJQn47eTPaeQntJZe1Mzt30E31_v6tXU,334
40
- ha_mcp/tools/backup.py,sha256=Avjsural4Y3Nvi73yOT-1dyAarXELEuti4xK0j4bZhI,18628
40
+ ha_mcp/tools/backup.py,sha256=vLOhHeyGztcWTanlpQJHJ64kRO9mqO_fbHSUWQbCkaM,22311
41
41
  ha_mcp/tools/best_practice_checker.py,sha256=S0Hfe0aInzxE7-TfccLFOhzFK52uoBDMjVAj4y_lVBQ,25752
42
42
  ha_mcp/tools/device_control.py,sha256=zBH5fowdvGLwa6FiaiSOfuNIWGPyZAOT7pmmnyEqaaY,32493
43
43
  ha_mcp/tools/enhanced.py,sha256=plvrTJmuAJ-55M0yznEq1Vv5TFDl_2FgegTYK7RaLC8,6668
@@ -93,12 +93,12 @@ ha_mcp/utils/kill_signal_diagnostics.py,sha256=PyoRIm9xJ36iiRtTaVKxfXAKlylytSx0z
93
93
  ha_mcp/utils/operation_manager.py,sha256=1ETI_L2TFNhnJUUJwtuH4R0s6ZP3_rscIOfdehYSmkU,14266
94
94
  ha_mcp/utils/python_sandbox.py,sha256=lPWqbrqTrJJb4RIQmWKaoADeUeyIRCQ4SF0tw4pXvNs,18566
95
95
  ha_mcp/utils/usage_logger.py,sha256=YDUJqaz9oLQVOH8Lkhwki2sn8WzD63I2s6jqeLdix6c,10021
96
- ha_mcp_dev-7.4.1.dev484.dist-info/licenses/LICENSE,sha256=7rJXXKBJWgJF8595wk-YTxwVTEi1kQaIqyy9dh5o_oY,1062
96
+ ha_mcp_dev-7.4.1.dev485.dist-info/licenses/LICENSE,sha256=7rJXXKBJWgJF8595wk-YTxwVTEi1kQaIqyy9dh5o_oY,1062
97
97
  tests/__init__.py,sha256=YRpec-ZFYCJ48oD_7ZcNY7dB8avoTWOrZICjaM-BYJ0,39
98
98
  tests/test_constants.py,sha256=bnqo5-cmXcJvJPsA8RezDljGwlRSoXW4Ecwj0h1_INM,876
99
99
  tests/test_env_manager.py,sha256=fcaFelaWA5c52vvwJ1IBvoBb6PMhDNrwv4H38x3j9Do,11948
100
- ha_mcp_dev-7.4.1.dev484.dist-info/METADATA,sha256=l26QAmwLivep1jc8uQkxrSpyQ-Nv0gnbGrDn4mgyc-k,21269
101
- ha_mcp_dev-7.4.1.dev484.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
102
- ha_mcp_dev-7.4.1.dev484.dist-info/entry_points.txt,sha256=ckO8PIrfV4-YQEyjqgO8wIzcQiMFTTJNWKZLyRtFpms,292
103
- ha_mcp_dev-7.4.1.dev484.dist-info/top_level.txt,sha256=cqJLEmgh4gQBKg_vBqj0ahS4DCg4J0qBXYgZCDQ2IWs,13
104
- ha_mcp_dev-7.4.1.dev484.dist-info/RECORD,,
100
+ ha_mcp_dev-7.4.1.dev485.dist-info/METADATA,sha256=1V65aKWMGzt90vNEs2gOvxLBwrZ-SVLOVrEUVjOM_7g,21269
101
+ ha_mcp_dev-7.4.1.dev485.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
102
+ ha_mcp_dev-7.4.1.dev485.dist-info/entry_points.txt,sha256=ckO8PIrfV4-YQEyjqgO8wIzcQiMFTTJNWKZLyRtFpms,292
103
+ ha_mcp_dev-7.4.1.dev485.dist-info/top_level.txt,sha256=cqJLEmgh4gQBKg_vBqj0ahS4DCg4J0qBXYgZCDQ2IWs,13
104
+ ha_mcp_dev-7.4.1.dev485.dist-info/RECORD,,