ha-mcp-dev 7.5.0.dev592__tar.gz → 7.5.0.dev593__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 (124) hide show
  1. {ha_mcp_dev-7.5.0.dev592/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.5.0.dev593}/PKG-INFO +2 -2
  2. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/README.md +1 -1
  3. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/pyproject.toml +1 -1
  4. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/tools/backup.py +217 -48
  5. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593/src/ha_mcp_dev.egg-info}/PKG-INFO +2 -2
  6. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/LICENSE +0 -0
  7. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/MANIFEST.in +0 -0
  8. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/setup.cfg +0 -0
  9. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/__init__.py +0 -0
  10. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/__main__.py +0 -0
  11. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/_pypi_marker +0 -0
  12. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/_version.py +0 -0
  13. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/auth/__init__.py +0 -0
  14. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/auth/consent_form.py +0 -0
  15. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/auth/provider.py +0 -0
  16. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/backup_manager.py +0 -0
  17. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/client/__init__.py +0 -0
  18. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/client/rest_client.py +0 -0
  19. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/client/supervisor_client.py +0 -0
  20. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/client/websocket_client.py +0 -0
  21. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/client/websocket_listener.py +0 -0
  22. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/config.py +0 -0
  23. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/errors.py +0 -0
  24. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/policy/__init__.py +0 -0
  25. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/policy/approval_queue.py +0 -0
  26. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/policy/evaluator.py +0 -0
  27. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/policy/handlers.py +0 -0
  28. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/policy/middleware.py +0 -0
  29. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/policy/model.py +0 -0
  30. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/policy/persistence.py +0 -0
  31. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/policy/value_sources.py +0 -0
  32. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/py.typed +0 -0
  33. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
  34. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
  35. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
  36. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
  37. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
  38. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
  39. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
  40. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
  41. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
  42. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
  43. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
  44. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
  45. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
  46. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
  47. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
  48. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
  49. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
  50. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
  51. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
  52. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
  53. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
  54. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/yaml-only-integrations.md +0 -0
  55. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/server.py +0 -0
  56. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/settings_ui.py +0 -0
  57. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/smoke_test.py +0 -0
  58. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
  59. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/tools/__init__.py +0 -0
  60. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/tools/auto_backup.py +0 -0
  61. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/tools/best_practice_checker.py +0 -0
  62. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/tools/device_control.py +0 -0
  63. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/tools/enhanced.py +0 -0
  64. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/tools/helpers.py +0 -0
  65. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/tools/reference_validator.py +0 -0
  66. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/tools/registry.py +0 -0
  67. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/tools/smart_search.py +0 -0
  68. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/tools/tools_addons.py +0 -0
  69. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/tools/tools_areas.py +0 -0
  70. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/tools/tools_blueprints.py +0 -0
  71. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/tools/tools_bug_report.py +0 -0
  72. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/tools/tools_calendar.py +0 -0
  73. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/tools/tools_camera.py +0 -0
  74. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/tools/tools_categories.py +0 -0
  75. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/tools/tools_code.py +0 -0
  76. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/tools/tools_config_automations.py +0 -0
  77. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
  78. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/tools/tools_config_entry_flow.py +0 -0
  79. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
  80. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
  81. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
  82. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/tools/tools_energy.py +0 -0
  83. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/tools/tools_entities.py +0 -0
  84. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/tools/tools_filesystem.py +0 -0
  85. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/tools/tools_groups.py +0 -0
  86. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/tools/tools_hacs.py +0 -0
  87. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/tools/tools_history.py +0 -0
  88. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/tools/tools_integrations.py +0 -0
  89. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/tools/tools_labels.py +0 -0
  90. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/tools/tools_mcp_component.py +0 -0
  91. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/tools/tools_registry.py +0 -0
  92. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/tools/tools_resources.py +0 -0
  93. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/tools/tools_search.py +0 -0
  94. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/tools/tools_service.py +0 -0
  95. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/tools/tools_services.py +0 -0
  96. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/tools/tools_system.py +0 -0
  97. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/tools/tools_todo.py +0 -0
  98. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/tools/tools_traces.py +0 -0
  99. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/tools/tools_updates.py +0 -0
  100. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/tools/tools_utility.py +0 -0
  101. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
  102. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
  103. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/tools/tools_zones.py +0 -0
  104. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/tools/util_helpers.py +0 -0
  105. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/transforms/__init__.py +0 -0
  106. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/transforms/categorized_search.py +0 -0
  107. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
  108. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/utils/__init__.py +0 -0
  109. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/utils/config_hash.py +0 -0
  110. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/utils/data_paths.py +0 -0
  111. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/utils/domain_handlers.py +0 -0
  112. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/utils/fuzzy_search.py +0 -0
  113. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
  114. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/utils/operation_manager.py +0 -0
  115. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/utils/python_sandbox.py +0 -0
  116. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp/utils/usage_logger.py +0 -0
  117. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
  118. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
  119. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
  120. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
  121. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
  122. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/tests/__init__.py +0 -0
  123. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/tests/test_constants.py +0 -0
  124. {ha_mcp_dev-7.5.0.dev592 → ha_mcp_dev-7.5.0.dev593}/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.dev592
3
+ Version: 7.5.0.dev593
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
@@ -351,6 +351,7 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
351
351
  - **[@julienld](https://github.com/julienld)** — Project creator.
352
352
  - **[@sergeykad](https://github.com/sergeykad)** — Core maintainer.
353
353
  - **[@kingpanther13](https://github.com/kingpanther13)** — Core maintainer.
354
+ - **[@Patch76](https://github.com/Patch76)** — Core maintainer.
354
355
 
355
356
  ### Contributors
356
357
 
@@ -375,7 +376,6 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
375
376
  - **[@restriction](https://github.com/restriction)** — Responsible disclosure: python_transform sandbox missing call target validation.
376
377
  - **[@lcrostarosa](https://github.com/lcrostarosa)** — Diagnostic and health monitoring tools concept (#675), inspiring system/error logs, repairs, and ZHA radio metrics integration.
377
378
  - **[@roysha1](https://github.com/roysha1)** — Copilot CLI support in the installation wizard; replaced placeholder logo SVGs with real brand icons on the documentation site.
378
- - **[@Patch76](https://github.com/Patch76)** — `ha_remove_entity` tool, history/statistics pagination and validation, docs sync automation, docstring guidelines, dashboard tool consolidation.
379
379
  - **[@teancom](https://github.com/teancom)** — Fix add-on stats endpoint (`/addons/{slug}/stats`).
380
380
  - **[@TomasDJo](https://github.com/TomasDJo)** — Category support for automations, scripts, and scenes.
381
381
  - **[@bzelch](https://github.com/bzelch)** — `python_transform` support for automations and scripts.
@@ -321,6 +321,7 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
321
321
  - **[@julienld](https://github.com/julienld)** — Project creator.
322
322
  - **[@sergeykad](https://github.com/sergeykad)** — Core maintainer.
323
323
  - **[@kingpanther13](https://github.com/kingpanther13)** — Core maintainer.
324
+ - **[@Patch76](https://github.com/Patch76)** — Core maintainer.
324
325
 
325
326
  ### Contributors
326
327
 
@@ -345,7 +346,6 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
345
346
  - **[@restriction](https://github.com/restriction)** — Responsible disclosure: python_transform sandbox missing call target validation.
346
347
  - **[@lcrostarosa](https://github.com/lcrostarosa)** — Diagnostic and health monitoring tools concept (#675), inspiring system/error logs, repairs, and ZHA radio metrics integration.
347
348
  - **[@roysha1](https://github.com/roysha1)** — Copilot CLI support in the installation wizard; replaced placeholder logo SVGs with real brand icons on the documentation site.
348
- - **[@Patch76](https://github.com/Patch76)** — `ha_remove_entity` tool, history/statistics pagination and validation, docs sync automation, docstring guidelines, dashboard tool consolidation.
349
349
  - **[@teancom](https://github.com/teancom)** — Fix add-on stats endpoint (`/addons/{slug}/stats`).
350
350
  - **[@TomasDJo](https://github.com/TomasDJo)** — Category support for automations, scripts, and scenes.
351
351
  - **[@bzelch](https://github.com/bzelch)** — `python_transform` support for automations and scripts.
@@ -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.dev592"
7
+ version = "7.5.0.dev593"
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"
@@ -18,14 +18,14 @@ HA restore path. Each call must explicitly pick its scope.
18
18
 
19
19
  import asyncio
20
20
  import logging
21
- from datetime import datetime
21
+ from datetime import UTC, datetime, timedelta
22
22
  from typing import TYPE_CHECKING, Annotated, Any, Literal, cast
23
23
 
24
24
  from fastmcp.exceptions import ToolError
25
25
  from pydantic import Field
26
26
 
27
27
  from ..backup_manager import get_backup_manager
28
- from ..client.rest_client import HomeAssistantClient
28
+ from ..client.rest_client import HomeAssistantClient, HomeAssistantError
29
29
  from ..client.websocket_client import HomeAssistantWebSocketClient
30
30
  from ..config import get_global_settings
31
31
  from ..errors import ErrorCode, create_error_response
@@ -41,8 +41,15 @@ if TYPE_CHECKING:
41
41
 
42
42
  logger = logging.getLogger(__name__)
43
43
 
44
- _BACKUP_MAX_WAIT_S = 120
44
+ # Default poll window for full HA backups. The 120s prior default underfit
45
+ # slow HA instances (#1433: poll loop exited while HA was still in
46
+ # state="create_backup", the wrapper treated that as failure, retried, and
47
+ # produced duplicate backups). 300s covers the long tail without making the
48
+ # happy-path wait noticeable.
49
+ _BACKUP_MAX_WAIT_S = 300
45
50
  _BACKUP_POLL_INTERVAL_S = 2
51
+ # Clock-skew tolerance when filtering backup entries by date vs job-start.
52
+ _BACKUP_DATE_FILTER_TOLERANCE_S = 5
46
53
 
47
54
 
48
55
  def _get_backup_hint_text() -> str:
@@ -171,6 +178,99 @@ async def _get_backup_password(
171
178
  return cast(str, default_password)
172
179
 
173
180
 
181
+ def _parse_backup_date(raw: Any) -> datetime | None:
182
+ """Parse an HA backup `date` (ISO-8601, may use `Z` suffix) to a tz-aware
183
+ datetime, returning None on missing/malformed input. Naive timestamps are
184
+ treated as UTC."""
185
+ if not isinstance(raw, str) or not raw:
186
+ return None
187
+ try:
188
+ dt = datetime.fromisoformat(raw)
189
+ except ValueError:
190
+ return None
191
+ if dt.tzinfo is None:
192
+ dt = dt.replace(tzinfo=UTC)
193
+ return dt
194
+
195
+
196
+ def _build_success_response_if_found(
197
+ info_result: dict[str, Any],
198
+ *,
199
+ name: str,
200
+ backup_job_id: str,
201
+ agent_id: str,
202
+ duration_seconds: int,
203
+ job_start_ts: datetime,
204
+ ) -> dict[str, Any] | None:
205
+ """Return the canonical success-response dict, or None.
206
+
207
+ Single source of truth for "did this backup actually complete cleanly?".
208
+ Called from both the in-loop branch and the post-timeout final check.
209
+
210
+ Returns None unless ALL of:
211
+
212
+ * `result.state == "idle"` AND `result.last_action_event.state == "completed"`
213
+ — list-membership alone is insufficient because HA registers the entry in
214
+ `backups` before compression/encryption finish; claiming success on a
215
+ half-written entry would let callers immediately attempt restore on a
216
+ partial file. The state-gate enforces "fully finalized".
217
+ * The match resolves to *this* job, not a stale prior-run entry with the
218
+ same name (HA does not enforce unique backup names, and the post-timeout
219
+ window is exactly when collisions are likely after a retry). Filters to
220
+ entries with `name == name` AND `date >= job_start_ts - tolerance`,
221
+ then within that fresh set prefers a `last_action_event.backup_id`
222
+ match if HA exposes one, otherwise picks the newest by date.
223
+ """
224
+ result_block = info_result.get("result") or {}
225
+ state = result_block.get("state")
226
+ last_event = result_block.get("last_action_event") or {}
227
+ if state != "idle" or last_event.get("state") != "completed":
228
+ return None
229
+
230
+ backups = result_block.get("backups") or []
231
+
232
+ # Freshness gate applies uniformly — both the `last_action_event.backup_id`
233
+ # path and the name-only fallback are constrained to entries dated
234
+ # at-or-after the job start. Without this, a concurrent backup (e.g. UI-
235
+ # triggered) updating `last_action_event` between our last in-loop poll
236
+ # and the post-timeout lookup could let us match its entry as if it were
237
+ # ours.
238
+ tolerance = timedelta(seconds=_BACKUP_DATE_FILTER_TOLERANCE_S)
239
+ cutoff = job_start_ts - tolerance
240
+ fresh = [
241
+ b
242
+ for b in backups
243
+ if b.get("name") == name
244
+ and (entry_date := _parse_backup_date(b.get("date"))) is not None
245
+ and entry_date >= cutoff
246
+ ]
247
+ if not fresh:
248
+ return None
249
+
250
+ target_id = last_event.get("backup_id")
251
+ authoritative = (
252
+ next((b for b in fresh if b.get("backup_id") == target_id), None)
253
+ if target_id
254
+ else None
255
+ )
256
+ match = authoritative or max(
257
+ fresh,
258
+ key=lambda b: _parse_backup_date(b.get("date")) or job_start_ts,
259
+ )
260
+
261
+ return {
262
+ "success": True,
263
+ "backup_id": match.get("backup_id"),
264
+ "backup_job_id": backup_job_id,
265
+ "name": name,
266
+ "date": match.get("date"),
267
+ "size_bytes": (match.get("agents") or {}).get(agent_id, {}).get("size"),
268
+ "status": "Backup completed successfully",
269
+ "duration_seconds": duration_seconds,
270
+ "note": "Backup uses your Home Assistant's default backup password",
271
+ }
272
+
273
+
174
274
  async def _poll_backup_completion(
175
275
  ws_client: HomeAssistantWebSocketClient,
176
276
  name: str,
@@ -185,8 +285,18 @@ async def _poll_backup_completion(
185
285
  ``hassio.local`` on Supervised, ``backup.local`` on Core); used to look
186
286
  up the per-agent size in the backup-info payload.
187
287
 
188
- Raises ToolError on backup failure or timeout.
288
+ On timeout, performs one final ``backup/info`` lookup before raising. If
289
+ that lookup confirms `state=idle` + `event_state=completed` AND finds a
290
+ backup belonging to *this* job (by ``last_action_event.backup_id`` or
291
+ fresh date-window name-match), returns the success response with a
292
+ ``warnings`` entry noting the late detection (#1433). Otherwise raises
293
+ ``TIMEOUT_OPERATION`` — and if the entry exists but state still indicates
294
+ creation in progress, surfaces ``likely_in_progress=true`` in the error
295
+ context so callers can back off retries instead of compounding duplicates.
296
+
297
+ Raises ToolError on backup failure or final timeout with no completion.
189
298
  """
299
+ job_start_ts = datetime.now(UTC)
190
300
  waited = 0
191
301
 
192
302
  while waited < max_wait_seconds:
@@ -194,61 +304,120 @@ async def _poll_backup_completion(
194
304
  waited += poll_interval
195
305
 
196
306
  info_result = await ws_client.send_command("backup/info")
197
- if info_result.get("success"):
198
- state = info_result.get("result", {}).get("state")
199
- last_event = info_result.get("result", {}).get("last_action_event", {})
200
- event_state = last_event.get("state")
201
-
307
+ if not info_result.get("success"):
202
308
  logger.debug(
203
- f"Backup state: {state}, event_state: {event_state}, waited: {waited}s"
309
+ f"backup/info returned success=False at waited={waited}s; retrying"
310
+ )
311
+ continue
312
+
313
+ result_block = info_result.get("result") or {}
314
+ last_event = result_block.get("last_action_event") or {}
315
+ event_state = last_event.get("state")
316
+ logger.debug(
317
+ f"Backup state: {result_block.get('state')}, "
318
+ f"event_state: {event_state}, waited: {waited}s"
319
+ )
320
+
321
+ if event_state == "failed":
322
+ raise_tool_error(
323
+ create_error_response(
324
+ ErrorCode.SERVICE_CALL_FAILED,
325
+ "Backup creation failed",
326
+ context={"backup_job_id": backup_job_id},
327
+ )
204
328
  )
205
329
 
206
- if state == "idle" and event_state == "completed":
207
- backups = info_result.get("result", {}).get("backups", [])
208
- created_backup = None
209
- for backup in backups:
210
- if backup.get("name") == name:
211
- created_backup = backup
212
- break
213
-
214
- if created_backup:
215
- logger.info(
216
- f"Backup completed successfully: {created_backup.get('backup_id')}"
217
- )
218
- return {
219
- "success": True,
220
- "backup_id": created_backup.get("backup_id"),
221
- "backup_job_id": backup_job_id,
222
- "name": name,
223
- "date": created_backup.get("date"),
224
- "size_bytes": created_backup.get("agents", {})
225
- .get(agent_id, {})
226
- .get("size"),
227
- "status": "Backup completed successfully",
228
- "duration_seconds": waited,
229
- "note": "Backup uses your Home Assistant's default backup password",
230
- }
231
- else:
232
- logger.warning(
233
- "Backup completed but not found in backup list yet, waiting..."
234
- )
235
- continue
330
+ response = _build_success_response_if_found(
331
+ info_result,
332
+ name=name,
333
+ backup_job_id=backup_job_id,
334
+ agent_id=agent_id,
335
+ duration_seconds=waited,
336
+ job_start_ts=job_start_ts,
337
+ )
338
+ if response is not None:
339
+ logger.info(f"Backup completed successfully: {response['backup_id']}")
340
+ return response
341
+ # state=idle+completed observed but entry not yet (or not freshly) in
342
+ # the list — same bug class as #1433 one tick earlier; continue polling.
343
+
344
+ logger.info(
345
+ f"Backup did not complete within {max_wait_seconds}s; "
346
+ "performing final backup-list lookup before raising timeout"
347
+ )
348
+ # send_command raises on HA-side failure rather than returning
349
+ # success=false. Treat any `HomeAssistantError` (incl. AuthError,
350
+ # ConnectionError, CommandError) during this best-effort verification
351
+ # as "couldn't verify"; surface the failure in the error context via
352
+ # `verification_error` so the auth-case stops being invisible behind a
353
+ # misleading TIMEOUT_OPERATION. Programming errors (AttributeError,
354
+ # TypeError, KeyError) intentionally propagate.
355
+ verification_error: str | None = None
356
+ likely_in_progress = False
357
+ final_info: dict[str, Any] | None = None
358
+ try:
359
+ final_info = await ws_client.send_command("backup/info")
360
+ except HomeAssistantError as e:
361
+ verification_error = repr(e)
362
+ logger.warning(
363
+ f"Post-timeout backup/info lookup failed ({e!r}); "
364
+ "falling through to TIMEOUT_OPERATION"
365
+ )
236
366
 
237
- elif event_state == "failed":
238
- raise_tool_error(
239
- create_error_response(
240
- ErrorCode.SERVICE_CALL_FAILED,
241
- "Backup creation failed",
242
- context={"backup_job_id": backup_job_id},
243
- )
367
+ if final_info is not None and final_info.get("success"):
368
+ response = _build_success_response_if_found(
369
+ final_info,
370
+ name=name,
371
+ backup_job_id=backup_job_id,
372
+ agent_id=agent_id,
373
+ duration_seconds=max_wait_seconds,
374
+ job_start_ts=job_start_ts,
375
+ )
376
+ if response is not None:
377
+ response["warnings"] = [
378
+ f"Backup completion observed only after the {max_wait_seconds}s "
379
+ "poll window — the operation succeeded but took longer than "
380
+ "expected. Increase max_wait_seconds if this recurs.",
381
+ ]
382
+ logger.info(
383
+ f"Backup found in post-timeout list lookup: {response['backup_id']}"
384
+ )
385
+ return response
386
+ # Helper returned None. Three cases distinguishable from the raw
387
+ # final_info: (a) backup failed in the gap between last in-loop poll
388
+ # and the final lookup → raise SERVICE_CALL_FAILED, the failure mode
389
+ # is known and unambiguous; (b) state still indicates creation in
390
+ # progress → surface `likely_in_progress` so callers back off retries
391
+ # rather than compounding duplicates; (c) state idle+completed but no
392
+ # fresh matching entry → genuine TIMEOUT_OPERATION, nothing extra to
393
+ # add.
394
+ result_block = final_info.get("result") or {}
395
+ last_event = result_block.get("last_action_event") or {}
396
+ state = result_block.get("state")
397
+ event_state = last_event.get("state")
398
+ if event_state == "failed":
399
+ raise_tool_error(
400
+ create_error_response(
401
+ ErrorCode.SERVICE_CALL_FAILED,
402
+ "Backup creation failed (observed at post-timeout lookup)",
403
+ context={"backup_job_id": backup_job_id, "name": name},
244
404
  )
405
+ )
406
+ if state != "idle":
407
+ likely_in_progress = True
245
408
 
246
409
  logger.warning(f"Backup did not complete within {max_wait_seconds} seconds")
410
+ error_context: dict[str, Any] = {"backup_job_id": backup_job_id, "name": name}
411
+ if verification_error is not None:
412
+ error_context["verification_error"] = verification_error
413
+ if likely_in_progress:
414
+ error_context["likely_in_progress"] = True
415
+
247
416
  raise_tool_error(
248
417
  create_error_response(
249
418
  ErrorCode.TIMEOUT_OPERATION,
250
419
  f"Backup creation timed out after {max_wait_seconds} seconds",
251
- context={"backup_job_id": backup_job_id, "name": name},
420
+ context=error_context,
252
421
  suggestions=[
253
422
  "Backup may still be in progress. Check Home Assistant backup status."
254
423
  ],
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ha-mcp-dev
3
- Version: 7.5.0.dev592
3
+ Version: 7.5.0.dev593
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
@@ -351,6 +351,7 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
351
351
  - **[@julienld](https://github.com/julienld)** — Project creator.
352
352
  - **[@sergeykad](https://github.com/sergeykad)** — Core maintainer.
353
353
  - **[@kingpanther13](https://github.com/kingpanther13)** — Core maintainer.
354
+ - **[@Patch76](https://github.com/Patch76)** — Core maintainer.
354
355
 
355
356
  ### Contributors
356
357
 
@@ -375,7 +376,6 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
375
376
  - **[@restriction](https://github.com/restriction)** — Responsible disclosure: python_transform sandbox missing call target validation.
376
377
  - **[@lcrostarosa](https://github.com/lcrostarosa)** — Diagnostic and health monitoring tools concept (#675), inspiring system/error logs, repairs, and ZHA radio metrics integration.
377
378
  - **[@roysha1](https://github.com/roysha1)** — Copilot CLI support in the installation wizard; replaced placeholder logo SVGs with real brand icons on the documentation site.
378
- - **[@Patch76](https://github.com/Patch76)** — `ha_remove_entity` tool, history/statistics pagination and validation, docs sync automation, docstring guidelines, dashboard tool consolidation.
379
379
  - **[@teancom](https://github.com/teancom)** — Fix add-on stats endpoint (`/addons/{slug}/stats`).
380
380
  - **[@TomasDJo](https://github.com/TomasDJo)** — Category support for automations, scripts, and scenes.
381
381
  - **[@bzelch](https://github.com/bzelch)** — `python_transform` support for automations and scripts.