ha-mcp-dev 7.6.0.dev604__tar.gz → 7.6.0.dev606__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.6.0.dev604/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.6.0.dev606}/PKG-INFO +1 -1
  2. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/pyproject.toml +1 -1
  3. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/tools/tools_filesystem.py +72 -19
  4. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/tools/tools_mcp_component.py +41 -7
  5. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/tools/tools_yaml_config.py +19 -9
  6. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
  7. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/LICENSE +0 -0
  8. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/MANIFEST.in +0 -0
  9. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/README.md +0 -0
  10. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/setup.cfg +0 -0
  11. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/__init__.py +0 -0
  12. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/__main__.py +0 -0
  13. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/_pypi_marker +0 -0
  14. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/_version.py +0 -0
  15. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/auth/__init__.py +0 -0
  16. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/auth/consent_form.py +0 -0
  17. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/auth/provider.py +0 -0
  18. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/backup_manager.py +0 -0
  19. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/client/__init__.py +0 -0
  20. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/client/rest_client.py +0 -0
  21. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/client/supervisor_client.py +0 -0
  22. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/client/websocket_client.py +0 -0
  23. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/client/websocket_listener.py +0 -0
  24. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/config.py +0 -0
  25. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/errors.py +0 -0
  26. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/policy/__init__.py +0 -0
  27. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/policy/approval_queue.py +0 -0
  28. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/policy/evaluator.py +0 -0
  29. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/policy/handlers.py +0 -0
  30. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/policy/middleware.py +0 -0
  31. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/policy/model.py +0 -0
  32. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/policy/persistence.py +0 -0
  33. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/policy/value_sources.py +0 -0
  34. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/py.typed +0 -0
  35. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
  36. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
  37. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
  38. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
  39. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
  40. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
  41. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
  42. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
  43. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
  44. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
  45. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
  46. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
  47. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
  48. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
  49. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
  50. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
  51. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
  52. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
  53. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
  54. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
  55. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
  56. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/yaml-only-integrations.md +0 -0
  57. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/server.py +0 -0
  58. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/settings_ui.py +0 -0
  59. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/smoke_test.py +0 -0
  60. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
  61. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/tools/__init__.py +0 -0
  62. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/tools/auto_backup.py +0 -0
  63. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/tools/backup.py +0 -0
  64. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/tools/best_practice_checker.py +0 -0
  65. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/tools/device_control.py +0 -0
  66. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/tools/enhanced.py +0 -0
  67. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/tools/helpers.py +0 -0
  68. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/tools/reference_validator.py +0 -0
  69. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/tools/registry.py +0 -0
  70. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/tools/smart_search.py +0 -0
  71. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/tools/tools_addons.py +0 -0
  72. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/tools/tools_areas.py +0 -0
  73. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/tools/tools_blueprints.py +0 -0
  74. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/tools/tools_bug_report.py +0 -0
  75. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/tools/tools_calendar.py +0 -0
  76. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/tools/tools_camera.py +0 -0
  77. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/tools/tools_categories.py +0 -0
  78. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/tools/tools_code.py +0 -0
  79. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/tools/tools_config_automations.py +0 -0
  80. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
  81. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/tools/tools_config_entry_flow.py +0 -0
  82. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
  83. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
  84. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
  85. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/tools/tools_energy.py +0 -0
  86. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/tools/tools_entities.py +0 -0
  87. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/tools/tools_groups.py +0 -0
  88. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/tools/tools_hacs.py +0 -0
  89. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/tools/tools_history.py +0 -0
  90. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/tools/tools_integrations.py +0 -0
  91. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/tools/tools_labels.py +0 -0
  92. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/tools/tools_registry.py +0 -0
  93. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/tools/tools_resources.py +0 -0
  94. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/tools/tools_search.py +0 -0
  95. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/tools/tools_service.py +0 -0
  96. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/tools/tools_services.py +0 -0
  97. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/tools/tools_system.py +0 -0
  98. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/tools/tools_todo.py +0 -0
  99. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/tools/tools_traces.py +0 -0
  100. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/tools/tools_updates.py +0 -0
  101. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/tools/tools_utility.py +0 -0
  102. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
  103. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/tools/tools_zones.py +0 -0
  104. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/tools/util_helpers.py +0 -0
  105. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/transforms/__init__.py +0 -0
  106. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/transforms/categorized_search.py +0 -0
  107. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
  108. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/utils/__init__.py +0 -0
  109. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/utils/config_hash.py +0 -0
  110. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/utils/data_paths.py +0 -0
  111. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/utils/domain_handlers.py +0 -0
  112. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/utils/fuzzy_search.py +0 -0
  113. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
  114. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/utils/operation_manager.py +0 -0
  115. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/utils/python_sandbox.py +0 -0
  116. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp/utils/usage_logger.py +0 -0
  117. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
  118. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
  119. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
  120. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
  121. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
  122. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/tests/__init__.py +0 -0
  123. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/tests/test_constants.py +0 -0
  124. {ha_mcp_dev-7.6.0.dev604 → ha_mcp_dev-7.6.0.dev606}/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.6.0.dev604
3
+ Version: 7.6.0.dev606
4
4
  Summary: Home Assistant MCP Server - Complete control of Home Assistant through MCP
5
5
  Author-email: Julien <github@qc-h.net>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ha-mcp-dev"
7
- version = "7.6.0.dev604"
7
+ version = "7.6.0.dev606"
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"
@@ -17,7 +17,7 @@ import asyncio
17
17
  import json
18
18
  import logging
19
19
  import weakref
20
- from typing import Annotated, Any
20
+ from typing import Annotated, Any, NoReturn
21
21
 
22
22
  from fastmcp.exceptions import ToolError
23
23
  from fastmcp.tools import tool
@@ -47,6 +47,28 @@ MCP_TOOLS_DOMAIN = "ha_mcp_tools"
47
47
  # subsequent call comes back unauthorized (covers token rotation).
48
48
  CALLER_TOKEN_FIELD = "_ha_mcp_token"
49
49
  CALLER_TOKEN_BOOTSTRAP_SERVICE = "get_caller_token"
50
+
51
+ # Minimum version of the ha_mcp_tools custom component that this ha-mcp
52
+ # release expects. Bumps in lockstep with ``manifest.json`` whenever a
53
+ # server-side behavior change requires it. Older components (no
54
+ # ``version`` in the get_caller_token response, or a version below this)
55
+ # get an actionable "update via HACS" error.
56
+ MIN_COMPONENT_VERSION = "0.5.1"
57
+
58
+
59
+ def _version_tuple(version: str) -> tuple[int, ...]:
60
+ """Parse ``'0.5.1'`` → ``(0, 5, 1)`` for tuple-comparison.
61
+
62
+ Raises ``ValueError`` on any non-numeric segment. Coercing a bad
63
+ segment to ``0`` would not actually achieve the "fail closed"
64
+ intent: a malformed high-order segment like ``"1.x.0"`` would
65
+ still parse to ``(1, 0, 0)`` and pass a ``>= (0, 5, 1)`` gate.
66
+ Caller routes the ValueError through ``_raise_component_too_old``
67
+ so the actionable update prompt fires.
68
+ """
69
+ return tuple(int(segment) for segment in version.split("."))
70
+
71
+
50
72
  # Weak-keyed by client object to support multi-client setups and self-evict
51
73
  # when a client is garbage-collected (avoids id() reuse if a freed client's
52
74
  # address gets recycled before the unauthorized-retry fires).
@@ -81,29 +103,47 @@ async def _is_bootstrap_service_registered(client: Any) -> bool:
81
103
  return False
82
104
 
83
105
 
106
+ def _raise_component_too_old(detail: str) -> NoReturn:
107
+ """Single actionable 'update via HACS' error path."""
108
+ raise_tool_error(
109
+ create_error_response(
110
+ ErrorCode.COMPONENT_NOT_INSTALLED,
111
+ f"The installed ha_mcp_tools custom component is too old: {detail}. "
112
+ f"This ha-mcp release requires >= {MIN_COMPONENT_VERSION}. "
113
+ "Update via HACS and restart Home Assistant.",
114
+ suggestions=[
115
+ "HACS → Integrations → HA MCP Tools → Update",
116
+ "Restart Home Assistant after update completes",
117
+ "Then retry the operation",
118
+ ],
119
+ )
120
+ )
121
+
122
+
84
123
  async def _fetch_caller_token(client: Any) -> str:
85
124
  """Call the bootstrap service and cache the returned token.
86
125
 
87
- Old custom-component versions (<0.5.0) don't register get_caller_token.
88
- We detect that explicitly and surface an actionable "update component"
89
- error rather than a generic "no usable token" string, which would
90
- otherwise be the symptom for both (a) old component installed and
91
- (b) a race condition during integration setup.
126
+ Two version gates:
127
+
128
+ 1. ``_is_bootstrap_service_registered`` pre-0.5.0 components don't
129
+ ship ``get_caller_token`` at all. Surface an actionable "update"
130
+ error instead of letting HA return an opaque 400 to the caller.
131
+ 2. ``MIN_COMPONENT_VERSION`` — even when the bootstrap service is
132
+ present, the response now carries the component's manifest
133
+ version. ha-mcp releases that depend on newer custom-component
134
+ behavior (e.g. a new accepted yaml_path key, a new schema field)
135
+ bump ``MIN_COMPONENT_VERSION`` together with the manifest, and
136
+ this check rejects 0.5.0+ components that are still behind that
137
+ bar with the same actionable update prompt.
138
+
139
+ Components that pre-date the version-reporting field (returned no
140
+ ``version`` in the response) are treated as "too old" for the same
141
+ reason: the absence of the field IS the signal that the component
142
+ doesn't yet know how to report its capabilities to ha-mcp.
92
143
  """
93
144
  if not await _is_bootstrap_service_registered(client):
94
- raise_tool_error(
95
- create_error_response(
96
- ErrorCode.COMPONENT_NOT_INSTALLED,
97
- "The installed ha_mcp_tools custom component is too old "
98
- "(pre-0.5.0) — it does not register the get_caller_token "
99
- "bootstrap service that this ha-mcp version requires. "
100
- "Update via HACS and restart Home Assistant.",
101
- suggestions=[
102
- "HACS → Integrations → HA MCP Tools → Update",
103
- "Restart Home Assistant after update completes",
104
- "Then retry the operation",
105
- ],
106
- )
145
+ _raise_component_too_old(
146
+ "the get_caller_token bootstrap service is not registered (pre-0.5.0)"
107
147
  )
108
148
  result = await client.call_service(
109
149
  MCP_TOOLS_DOMAIN,
@@ -125,6 +165,19 @@ async def _fetch_caller_token(client: Any) -> str:
125
165
  ],
126
166
  )
127
167
  )
168
+ raw_version: Any = unwrapped.get("version") if isinstance(unwrapped, dict) else None
169
+ if not isinstance(raw_version, str) or not raw_version:
170
+ _raise_component_too_old(
171
+ "the get_caller_token response did not include a version "
172
+ f"field (pre-{MIN_COMPONENT_VERSION})"
173
+ )
174
+ version: str = raw_version
175
+ try:
176
+ parsed = _version_tuple(version)
177
+ except ValueError:
178
+ _raise_component_too_old(f"malformed version: {version!r}")
179
+ if parsed < _version_tuple(MIN_COMPONENT_VERSION):
180
+ _raise_component_too_old(f"reported version is {version}")
128
181
  _CALLER_TOKEN_CACHE[client] = token
129
182
  return token
130
183
 
@@ -8,6 +8,7 @@ that are not available through standard Home Assistant APIs.
8
8
  Feature Flag: Set HAMCP_ENABLE_CUSTOM_COMPONENT_INTEGRATION=true to enable this tool.
9
9
  """
10
10
 
11
+ import asyncio
11
12
  import logging
12
13
  from typing import Annotated, Any
13
14
 
@@ -15,6 +16,7 @@ from fastmcp.exceptions import ToolError
15
16
  from fastmcp.tools import tool
16
17
  from pydantic import Field
17
18
 
19
+ from ..client.rest_client import HomeAssistantCommandError
18
20
  from ..errors import ErrorCode, create_error_response
19
21
  from .helpers import (
20
22
  exception_to_structured_error,
@@ -246,22 +248,54 @@ class McpComponentTools:
246
248
  repo_id = await self._resolve_repo_id(ws_client, existing_repo)
247
249
 
248
250
  logger.info(f"Installing {MCP_TOOLS_REPO} (ID: {repo_id})")
249
- download_response = await ws_client.send_command(
250
- "hacs/repository/download",
251
- repository=repo_id,
252
- )
253
-
254
- if not download_response.get("success"):
251
+ # HACS' download often returns a generic "Command failed:
252
+ # Unknown error" on transient GitHub hiccups (rate-limit,
253
+ # tarball stream interruption). Retry the download with
254
+ # exponential backoff so a one-shot transient doesn't
255
+ # surface as an installation failure to the caller.
256
+ # ``send_command`` raises ``HomeAssistantCommandError`` on
257
+ # ``success: False`` responses, so the retry catches that
258
+ # specific class — programming bugs / connection errors
259
+ # propagate normally.
260
+ max_attempts = 3
261
+ backoff_seconds = 2.0
262
+ last_error: HomeAssistantCommandError | None = None
263
+ download_response: dict[str, Any] | None = None
264
+ for attempt in range(1, max_attempts + 1):
265
+ try:
266
+ download_response = await ws_client.send_command(
267
+ "hacs/repository/download",
268
+ repository=repo_id,
269
+ )
270
+ last_error = None
271
+ break
272
+ except HomeAssistantCommandError as e:
273
+ last_error = e
274
+ if attempt < max_attempts:
275
+ wait_for = backoff_seconds * (2 ** (attempt - 1))
276
+ logger.warning(
277
+ "hacs/repository/download attempt %d/%d failed (%s); "
278
+ "retrying in %.1fs",
279
+ attempt,
280
+ max_attempts,
281
+ e,
282
+ wait_for,
283
+ )
284
+ await asyncio.sleep(wait_for)
285
+ if last_error is not None:
255
286
  raise_tool_error(
256
287
  create_error_response(
257
288
  ErrorCode.SERVICE_CALL_FAILED,
258
- f"Failed to download repository: {download_response}",
289
+ f"Failed to download repository after {max_attempts} "
290
+ f"attempts: {last_error}",
259
291
  suggestions=[
260
292
  "Check HACS logs for errors",
261
293
  "Verify GitHub is accessible",
294
+ "HACS may be rate-limited; wait a minute and retry",
262
295
  ],
263
296
  )
264
297
  )
298
+ assert download_response is not None # narrowing for type checker
265
299
 
266
300
  result: dict[str, Any] = {
267
301
  "success": True,
@@ -108,9 +108,13 @@ class YamlConfigTools:
108
108
  "For YAML-mode dashboards, "
109
109
  "use the dotted form 'lovelace.dashboards.<url_path>' where "
110
110
  "<url_path> is lowercase, hyphenated, and not a reserved HA "
111
- "route. No other dotted paths are supported. Not for template "
112
- "sensors (use ha_config_set_helper), automations, scripts, "
113
- "scenes, or input_* helpers those have dedicated tools."
111
+ "route. No other multi-segment paths are supported. "
112
+ "'automation', 'script', and 'scene' are accepted only when "
113
+ "file is under packages/*.yaml; in configuration.yaml use "
114
+ "the dedicated storage-mode tools "
115
+ "(ha_config_set_automation, ha_config_set_script, "
116
+ "ha_config_set_scene). Not for template sensors or "
117
+ "input_* helpers — those have dedicated tools."
114
118
  ),
115
119
  ),
116
120
  ],
@@ -162,9 +166,9 @@ class YamlConfigTools:
162
166
 
163
167
  - Template sensors (state-based or trigger-based) ->
164
168
  ha_config_set_helper(helper_type='template')
165
- - Automations -> ha_config_set_automation
166
- - Scripts -> ha_config_set_script
167
- - Scenes -> ha_config_set_scene
169
+ - Automations (storage-mode) -> ha_config_set_automation
170
+ - Scripts (storage-mode) -> ha_config_set_script
171
+ - Scenes (storage-mode) -> ha_config_set_scene
168
172
  - All 27 helper types (input_*, counter, timer, schedule, zone, person,
169
173
  tag, group, min_max, threshold, derivative, statistics, utility_meter,
170
174
  trend, filter, switch_as_x, etc.) -> ha_config_set_helper
@@ -174,10 +178,16 @@ class YamlConfigTools:
174
178
  for integrations with significant YAML-only configuration (knx
175
179
  entities in package files), and for registering YAML-mode dashboards via
176
180
  ``lovelace.dashboards.<url_path>`` (no other ``lovelace.*`` keys).
181
+ Also accepts ``automation``, ``script``, and ``scene`` keys when
182
+ ``file`` is a ``packages/*.yaml`` — for git-managed YAML configs
183
+ that track these alongside templates and other YAML items. Writes
184
+ to ``configuration.yaml`` for those three keys remain rejected so
185
+ storage-mode and YAML-mode collections don't collide; use the
186
+ dedicated storage-mode tools instead.
177
187
  Check ``post_action`` in the response: most keys need a full HA
178
- restart; template, mqtt, and group support reload. Preserves YAML
179
- comments and HA tags (``!include``, ``!secret``) on round-trip;
180
- ``replace`` swaps the subtree as-is.
188
+ restart; template, mqtt, group, automation, script, and scene
189
+ support reload. Preserves YAML comments and HA tags (``!include``,
190
+ ``!secret``) on round-trip; ``replace`` swaps the subtree as-is.
181
191
 
182
192
  For detailed routing guidance, use ha_get_skill_guide.
183
193
  """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ha-mcp-dev
3
- Version: 7.6.0.dev604
3
+ Version: 7.6.0.dev606
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