ha-mcp-dev 8.4.0.dev2482__tar.gz → 8.4.0.dev2485__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 (303) hide show
  1. {ha_mcp_dev-8.4.0.dev2482/src/ha_mcp_dev.egg-info → ha_mcp_dev-8.4.0.dev2485}/PKG-INFO +1 -1
  2. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/pyproject.toml +1 -1
  3. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/__init__.py +2 -0
  4. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/errors.py +30 -0
  5. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/server.py +13 -0
  6. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/device_control.py +84 -24
  7. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/tools_service.py +205 -12
  8. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/util_helpers.py +24 -0
  9. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/transforms/categorized_search.py +4 -2
  10. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
  11. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/LICENSE +0 -0
  12. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/MANIFEST.in +0 -0
  13. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/README.md +0 -0
  14. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/setup.cfg +0 -0
  15. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/__main__.py +0 -0
  16. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/_pypi_marker +0 -0
  17. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/_rich_compat.py +0 -0
  18. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/_vendor/__init__.py +0 -0
  19. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/_vendor/requirements.txt +0 -0
  20. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/_vendor/websockets/LICENSE +0 -0
  21. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/_vendor/websockets/MANIFEST.sha256 +0 -0
  22. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/_vendor/websockets/VENDORED +0 -0
  23. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/_vendor/websockets/__init__.py +0 -0
  24. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/_vendor/websockets/__main__.py +0 -0
  25. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/_vendor/websockets/asyncio/__init__.py +0 -0
  26. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/_vendor/websockets/asyncio/client.py +0 -0
  27. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/_vendor/websockets/asyncio/connection.py +0 -0
  28. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/_vendor/websockets/asyncio/messages.py +0 -0
  29. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/_vendor/websockets/asyncio/router.py +0 -0
  30. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/_vendor/websockets/asyncio/server.py +0 -0
  31. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/_vendor/websockets/auth.py +0 -0
  32. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/_vendor/websockets/cli.py +0 -0
  33. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/_vendor/websockets/client.py +0 -0
  34. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/_vendor/websockets/connection.py +0 -0
  35. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/_vendor/websockets/datastructures.py +0 -0
  36. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/_vendor/websockets/exceptions.py +0 -0
  37. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/_vendor/websockets/extensions/__init__.py +0 -0
  38. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/_vendor/websockets/extensions/base.py +0 -0
  39. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/_vendor/websockets/extensions/permessage_deflate.py +0 -0
  40. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/_vendor/websockets/frames.py +0 -0
  41. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/_vendor/websockets/headers.py +0 -0
  42. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/_vendor/websockets/http11.py +0 -0
  43. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/_vendor/websockets/imports.py +0 -0
  44. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/_vendor/websockets/legacy/__init__.py +0 -0
  45. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/_vendor/websockets/legacy/auth.py +0 -0
  46. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/_vendor/websockets/legacy/client.py +0 -0
  47. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/_vendor/websockets/legacy/exceptions.py +0 -0
  48. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/_vendor/websockets/legacy/framing.py +0 -0
  49. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/_vendor/websockets/legacy/handshake.py +0 -0
  50. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/_vendor/websockets/legacy/http.py +0 -0
  51. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/_vendor/websockets/legacy/protocol.py +0 -0
  52. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/_vendor/websockets/legacy/server.py +0 -0
  53. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/_vendor/websockets/protocol.py +0 -0
  54. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/_vendor/websockets/proxy.py +0 -0
  55. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/_vendor/websockets/py.typed +0 -0
  56. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/_vendor/websockets/server.py +0 -0
  57. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/_vendor/websockets/streams.py +0 -0
  58. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/_vendor/websockets/sync/__init__.py +0 -0
  59. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/_vendor/websockets/sync/client.py +0 -0
  60. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/_vendor/websockets/sync/connection.py +0 -0
  61. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/_vendor/websockets/sync/messages.py +0 -0
  62. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/_vendor/websockets/sync/router.py +0 -0
  63. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/_vendor/websockets/sync/server.py +0 -0
  64. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/_vendor/websockets/sync/utils.py +0 -0
  65. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/_vendor/websockets/trio/__init__.py +0 -0
  66. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/_vendor/websockets/trio/client.py +0 -0
  67. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/_vendor/websockets/trio/connection.py +0 -0
  68. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/_vendor/websockets/trio/messages.py +0 -0
  69. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/_vendor/websockets/trio/router.py +0 -0
  70. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/_vendor/websockets/trio/server.py +0 -0
  71. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/_vendor/websockets/trio/utils.py +0 -0
  72. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/_vendor/websockets/typing.py +0 -0
  73. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/_vendor/websockets/uri.py +0 -0
  74. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/_vendor/websockets/utils.py +0 -0
  75. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/_vendor/websockets/version.py +0 -0
  76. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/_version.py +0 -0
  77. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/auth/__init__.py +0 -0
  78. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/auth/consent_form.py +0 -0
  79. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/auth/oidc_compat.py +0 -0
  80. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/auth/provider.py +0 -0
  81. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/backup_manager.py +0 -0
  82. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/browser_landing.py +0 -0
  83. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/client/__init__.py +0 -0
  84. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/client/rest_client.py +0 -0
  85. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/client/supervisor_client.py +0 -0
  86. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/client/websocket_client.py +0 -0
  87. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/client/websocket_listener.py +0 -0
  88. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/config.py +0 -0
  89. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/dashboard_screenshot/__init__.py +0 -0
  90. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/dashboard_screenshot/capture.py +0 -0
  91. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/dashboard_screenshot/content.py +0 -0
  92. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/dashboard_screenshot/paths.py +0 -0
  93. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/dashboard_screenshot/provision.py +0 -0
  94. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/dashboard_screenshot/theme_guard.py +0 -0
  95. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/hacs_auto_refresh.py +0 -0
  96. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/llm_exposure.py +0 -0
  97. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/log_filters.py +0 -0
  98. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/policy/__init__.py +0 -0
  99. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/policy/approval_queue.py +0 -0
  100. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/policy/editing.py +0 -0
  101. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/policy/evaluator.py +0 -0
  102. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/policy/handlers.py +0 -0
  103. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/policy/middleware.py +0 -0
  104. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/policy/model.py +0 -0
  105. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/policy/persistence.py +0 -0
  106. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/policy/value_sources.py +0 -0
  107. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/py.typed +0 -0
  108. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/read_only.py +0 -0
  109. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/redaction.py +0 -0
  110. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/renamed_tools.py +0 -0
  111. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
  112. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
  113. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
  114. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
  115. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
  116. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
  117. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
  118. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
  119. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
  120. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
  121. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/resources/skills-vendor/evals/area-target-over-entity-list/case.yaml +0 -0
  122. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/resources/skills-vendor/evals/arrive-home-automation/case.yaml +0 -0
  123. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/resources/skills-vendor/evals/assist-shortcut-card/case.yaml +0 -0
  124. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/resources/skills-vendor/evals/avg-temperature/case.yaml +0 -0
  125. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/resources/skills-vendor/evals/backup-deletion-confirmation/case.yaml +0 -0
  126. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/resources/skills-vendor/evals/blueprint-authoring/case.yaml +0 -0
  127. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/resources/skills-vendor/evals/button-event-entity/case.yaml +0 -0
  128. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/resources/skills-vendor/evals/device-id-trigger-migration/case.yaml +0 -0
  129. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/resources/skills-vendor/evals/helper-flow-fields-not-platform-yaml/case.yaml +0 -0
  130. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/resources/skills-vendor/evals/media-player-song/case.yaml +0 -0
  131. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/resources/skills-vendor/evals/motion-light-automation-mode/case.yaml +0 -0
  132. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/resources/skills-vendor/evals/rename-entity-impact-analysis/case.yaml +0 -0
  133. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/resources/skills-vendor/evals/template-attributes-yaml-exception/case.yaml +0 -0
  134. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/resources/skills-vendor/evals/timer-finished-automation/case.yaml +0 -0
  135. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/resources/skills-vendor/evals/vacuum-last-clean/case.yaml +0 -0
  136. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/resources/skills-vendor/scripts/check_eval_cases.py +0 -0
  137. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
  138. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/appdaemon.md +0 -0
  139. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
  140. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/backups.md +0 -0
  141. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/blueprint-guide.md +0 -0
  142. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
  143. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
  144. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
  145. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
  146. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
  147. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
  148. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
  149. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/scenes.md +0 -0
  150. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
  151. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/yaml-only-integrations.md +0 -0
  152. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/settings_ui/__init__.py +0 -0
  153. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/settings_ui/_handlers_advanced.py +0 -0
  154. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/settings_ui/_handlers_backups.py +0 -0
  155. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/settings_ui/_handlers_fs.py +0 -0
  156. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/settings_ui/_handlers_server.py +0 -0
  157. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/settings_ui/_handlers_theme.py +0 -0
  158. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/settings_ui/_handlers_tools.py +0 -0
  159. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/settings_ui/_i18n.py +0 -0
  160. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/settings_ui/_locale_policy.py +0 -0
  161. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/settings_ui/_persistence.py +0 -0
  162. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/settings_ui/_supervisor.py +0 -0
  163. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/settings_ui/_theme.py +0 -0
  164. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/settings_ui/_tools_meta.py +0 -0
  165. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/settings_ui/locales/README.md +0 -0
  166. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/settings_ui/locales/cs.json +0 -0
  167. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/settings_ui/locales/de.json +0 -0
  168. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/settings_ui/locales/en.json +0 -0
  169. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/settings_ui/locales/eo.json +0 -0
  170. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/settings_ui/locales/es.json +0 -0
  171. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/settings_ui/locales/fr.json +0 -0
  172. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/settings_ui/locales/it.json +0 -0
  173. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/settings_ui/locales/ko.json +0 -0
  174. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/settings_ui/locales/nl.json +0 -0
  175. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/settings_ui/locales/pl.json +0 -0
  176. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/settings_ui/locales/ru.json +0 -0
  177. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/settings_ui/locales/sv.json +0 -0
  178. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/settings_ui/locales/tlh.json +0 -0
  179. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/settings_ui/locales/zh-Hans.json +0 -0
  180. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/settings_ui/settings.css +0 -0
  181. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/settings_ui/settings.html +0 -0
  182. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/settings_ui/settings.js +0 -0
  183. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/smoke_test.py +0 -0
  184. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
  185. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/strict_bps.py +0 -0
  186. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/__init__.py +0 -0
  187. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/auto_backup.py +0 -0
  188. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/backup.py +0 -0
  189. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/best_practice_checker.py +0 -0
  190. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/best_practice_result.py +0 -0
  191. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/best_practice_variables.py +0 -0
  192. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/bulk_selector.py +0 -0
  193. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/component_api.py +0 -0
  194. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/component_config_entries.py +0 -0
  195. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/component_config_reads.py +0 -0
  196. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/component_devices.py +0 -0
  197. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/component_registries.py +0 -0
  198. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/component_registry_lookup.py +0 -0
  199. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/config_entry_flow.py +0 -0
  200. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/config_entry_flow_form.py +0 -0
  201. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/config_entry_flow_menu.py +0 -0
  202. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/config_entry_flow_walker.py +0 -0
  203. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/config_entry_identity.py +0 -0
  204. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/config_entry_reconfigure.py +0 -0
  205. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/config_entry_reload_watch.py +0 -0
  206. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/error_log_parsing.py +0 -0
  207. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/hacs_registration.py +0 -0
  208. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/helpers.py +0 -0
  209. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/integration_reconfigure.py +0 -0
  210. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/log_common.py +0 -0
  211. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/log_sources.py +0 -0
  212. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/log_sources_supervisor.py +0 -0
  213. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/radio/__init__.py +0 -0
  214. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/radio/base.py +0 -0
  215. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/radio/matter.py +0 -0
  216. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/radio/thread.py +0 -0
  217. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/radio/zigbee.py +0 -0
  218. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/radio/zwave.py +0 -0
  219. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/reference_validator.py +0 -0
  220. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/registry.py +0 -0
  221. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/renamed_tool_middleware.py +0 -0
  222. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/smart_search/__init__.py +0 -0
  223. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/smart_search/_base.py +0 -0
  224. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/smart_search/_config.py +0 -0
  225. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/smart_search/_deep.py +0 -0
  226. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/smart_search/_entities.py +0 -0
  227. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/smart_search/_fetch.py +0 -0
  228. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/smart_search/_graph.py +0 -0
  229. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/smart_search/_overview.py +0 -0
  230. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/smart_search/_scenes.py +0 -0
  231. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/smart_search/_scoring.py +0 -0
  232. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/tool_search_hint_middleware.py +0 -0
  233. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/tools_addons.py +0 -0
  234. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/tools_areas.py +0 -0
  235. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/tools_assist_pipeline.py +0 -0
  236. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/tools_blueprints.py +0 -0
  237. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/tools_bug_report.py +0 -0
  238. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/tools_calendar.py +0 -0
  239. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/tools_camera.py +0 -0
  240. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/tools_categories.py +0 -0
  241. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/tools_code.py +0 -0
  242. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/tools_config_automations.py +0 -0
  243. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
  244. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
  245. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
  246. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
  247. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/tools_dashboard_screenshot.py +0 -0
  248. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/tools_dev.py +0 -0
  249. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/tools_energy.py +0 -0
  250. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/tools_entities.py +0 -0
  251. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/tools_filesystem.py +0 -0
  252. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/tools_groups.py +0 -0
  253. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/tools_hacs.py +0 -0
  254. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/tools_history.py +0 -0
  255. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/tools_integrations.py +0 -0
  256. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/tools_labels.py +0 -0
  257. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/tools_logs.py +0 -0
  258. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/tools_radio.py +0 -0
  259. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/tools_registry.py +0 -0
  260. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/tools_resources.py +0 -0
  261. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/tools_search.py +0 -0
  262. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/tools_security_policy.py +0 -0
  263. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/tools_services.py +0 -0
  264. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/tools_system.py +0 -0
  265. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/tools_themes.py +0 -0
  266. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/tools_todo.py +0 -0
  267. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/tools_traces.py +0 -0
  268. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/tools_updates.py +0 -0
  269. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/tools_utility.py +0 -0
  270. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
  271. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
  272. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/tools_yaml_read.py +0 -0
  273. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/tools_zones.py +0 -0
  274. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/tools/validation_middleware.py +0 -0
  275. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/transforms/__init__.py +0 -0
  276. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
  277. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/transport_security.py +0 -0
  278. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/update_check.py +0 -0
  279. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/utils/__init__.py +0 -0
  280. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/utils/config_hash.py +0 -0
  281. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/utils/config_write_lock.py +0 -0
  282. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/utils/data_paths.py +0 -0
  283. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/utils/domain_handlers.py +0 -0
  284. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/utils/entity_membership.py +0 -0
  285. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/utils/fuzzy_search.py +0 -0
  286. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
  287. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/utils/operation_manager.py +0 -0
  288. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/utils/python_sandbox.py +0 -0
  289. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/utils/skill_loader.py +0 -0
  290. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/utils/usage_logger.py +0 -0
  291. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/visibility/__init__.py +0 -0
  292. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/visibility/enforcement.py +0 -0
  293. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/visibility/model.py +0 -0
  294. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/visibility/persistence.py +0 -0
  295. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp/visibility/resolver.py +0 -0
  296. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
  297. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
  298. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
  299. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
  300. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
  301. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/tests/__init__.py +0 -0
  302. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/tests/test_constants.py +0 -0
  303. {ha_mcp_dev-8.4.0.dev2482 → ha_mcp_dev-8.4.0.dev2485}/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: 8.4.0.dev2482
3
+ Version: 8.4.0.dev2485
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 = "8.4.0.dev2482"
7
+ version = "8.4.0.dev2485"
8
8
  description = "Home Assistant MCP Server - Complete control of Home Assistant through MCP"
9
9
  readme = "README.md"
10
10
  # Upper bound widened to <3.15 so the package installs on Home Assistant's
@@ -24,6 +24,7 @@ from .errors import (
24
24
  create_config_error,
25
25
  create_connection_error,
26
26
  create_entity_not_found_error,
27
+ create_entity_unavailable_after_dispatch_error,
27
28
  create_error_response,
28
29
  create_service_error,
29
30
  create_timeout_error,
@@ -45,6 +46,7 @@ __all__ = [
45
46
  "create_connection_error",
46
47
  "create_auth_error",
47
48
  "create_entity_not_found_error",
49
+ "create_entity_unavailable_after_dispatch_error",
48
50
  "create_service_error",
49
51
  "create_validation_error",
50
52
  "create_config_error",
@@ -405,6 +405,36 @@ def create_entity_not_found_error(
405
405
  )
406
406
 
407
407
 
408
+ def create_entity_unavailable_after_dispatch_error(entity_id: str) -> dict[str, Any]:
409
+ """Create an ENTITY_UNAVAILABLE error for a target whose command already dispatched.
410
+
411
+ Distinct from a bare "unavailable" report: by the time this fires, the
412
+ confirmation wait has already lapsed, which only happens AFTER the
413
+ service call reached Home Assistant. The caller must not read this as
414
+ "the command never went out" and blindly retry a non-idempotent service
415
+ (e.g. ``toggle``) — that would double-apply a write that may have already
416
+ landed. ``context["dispatched"]`` makes that explicit for a caller that
417
+ only inspects structured fields rather than the message.
418
+ """
419
+ return create_error_response(
420
+ ErrorCode.ENTITY_UNAVAILABLE,
421
+ f"Entity '{entity_id}' exists but is currently unavailable",
422
+ details=(
423
+ f"The service call for '{entity_id}' was already dispatched to Home "
424
+ "Assistant -- it may have taken effect even though the entity's "
425
+ "state could not be confirmed within the timeout."
426
+ ),
427
+ suggestions=[
428
+ "The command was already sent -- check the entity's current state "
429
+ + "with ha_get_state before retrying",
430
+ "Avoid re-issuing a non-idempotent service (e.g. toggle) that could "
431
+ + "double-apply if the original command did land",
432
+ "Check if the device is powered on and connected",
433
+ ],
434
+ context={"entity_id": entity_id, "dispatched": True},
435
+ )
436
+
437
+
408
438
  def create_service_error(
409
439
  domain: str,
410
440
  service: str,
@@ -655,6 +655,19 @@ class HomeAssistantSmartMCPServer:
655
655
  "ssh samba grafana influxdb deconz motioneye compile validate upload "
656
656
  "deploy firmware ota flash yaml device logs flows events stats"
657
657
  ),
658
+ # #2322: the Energy Dashboard's tariffs live in .storage/energy, not
659
+ # in the state machine, so an agent hunting for electricity prices
660
+ # searches entities, finds none, and invents input_number helpers.
661
+ # The docstring says "cost tariffs" but never "price", "peak" or
662
+ # "kWh" — the words agents actually query with — and the title reads
663
+ # write-only ("Manage ..."), so lead the boost with the read verbs.
664
+ "ha_manage_energy_prefs": (
665
+ "read get inspect energy dashboard preferences prefs "
666
+ "electricity price prices pricing tariff tariffs rate rates "
667
+ "cost costs kwh peak off-peak offpeak contract utility bill "
668
+ "grid solar battery gas water consumption "
669
+ "number_energy_price entity_energy_price stat_energy_from"
670
+ ),
658
671
  # Old tool names from before #1134 consolidation. BM25 retrieval
659
672
  # on agents that still know the previous catalog ("call
660
673
  # ha_list_resources", "use ha_get_skill_home_assistant_best_practices")
@@ -23,7 +23,11 @@ from ..client.rest_client import (
23
23
  from ..client.websocket_client import get_websocket_client
24
24
  from ..client.websocket_listener import start_websocket_listener
25
25
  from ..config import get_global_settings
26
- from ..errors import ErrorCode, create_error_response
26
+ from ..errors import (
27
+ ErrorCode,
28
+ create_entity_unavailable_after_dispatch_error,
29
+ create_error_response,
30
+ )
27
31
  from ..utils.domain_handlers import get_domain_handler
28
32
  from ..utils.operation_manager import (
29
33
  fail_pending_operation,
@@ -43,7 +47,7 @@ from .helpers import (
43
47
  safe_progress,
44
48
  )
45
49
  from .tools_service import BulkControlOperation
46
- from .util_helpers import _SERVICE_TO_STATE
50
+ from .util_helpers import _SERVICE_TO_STATE, is_single_entity_target
47
51
 
48
52
  logger = logging.getLogger(__name__)
49
53
 
@@ -1016,9 +1020,18 @@ class DeviceControlTools:
1016
1020
  by ``_build_bulk_response`` exactly as the legacy dispatch result is; an op
1017
1021
  whose ``async_call`` raised under the batch (``error`` set / not dispatched)
1018
1022
  maps to a structured ``SERVICE_CALL_FAILED`` error and is NOT re-dispatched
1019
- (D9). When ``validate_first`` (the default) is set and the target's captured
1020
- pre-state is null (the entity does not exist), the op maps to a structured
1021
- ``ENTITY_NOT_FOUND`` failure — parity with the legacy per-op validation.
1023
+ (D9). When ``validate_first`` (the default) is set and ``entity_id`` is a
1024
+ single literal entity ID (see ``is_single_entity_target`` — a comma-joined
1025
+ multi-target or magic broadcast target like ``"all"`` is skipped entirely,
1026
+ since its transition row proves nothing about whether the possibly-
1027
+ successful dispatch landed), a captured pre-state that is null (the entity
1028
+ does not exist) maps to a structured ``ENTITY_NOT_FOUND`` failure — parity
1029
+ with the legacy per-op validation. A captured pre-state of ``"unavailable"``
1030
+ maps to a distinct ``ENTITY_UNAVAILABLE`` failure, but only once BOTH the op
1031
+ is unconfirmed AND the post-dispatch state still shows unavailable — unlike
1032
+ the not-found branch, an unavailable target can legitimately reconnect and
1033
+ confirm mid-dispatch, so this check is doubly gated rather than symmetric
1034
+ with it.
1022
1035
  """
1023
1036
  service_call = {
1024
1037
  "domain": row["domain"],
@@ -1039,31 +1052,78 @@ class DeviceControlTools:
1039
1052
  # exist (HA no-ops the dispatch, then the wait stalls to partial). The legacy
1040
1053
  # path returns a structured ENTITY_NOT_FOUND per-op failure — match it from
1041
1054
  # the pre-state already in the transition (no extra hops), rather than
1042
- # counting a phantom entity as a successful command. Reachability is
1043
- # drift-bounded: a missing transition row from a length-drifted batch now routes
1044
- # to the ambiguous path (I2) before reaching here, so a null old_state here
1045
- # means a genuinely absent entity, not a dropped row.
1046
- if op.get("validate_first", True) and row.get("entity_ids"):
1047
- old_state = next(
1055
+ # counting a phantom entity as a successful command. I2 (length-drift)
1056
+ # guards the BATCH-level op-count, not this: an individual op can still
1057
+ # legitimately carry an EMPTY transitions list (dispatched, but post-
1058
+ # dispatch formatting raised — see _dispatched_unconfirmed_bulk_result) at
1059
+ # the correct array length, and that proves nothing about existence. So
1060
+ # look for a transition row at all first; only a present row with a null
1061
+ # old_state means a genuinely absent entity.
1062
+ if (
1063
+ op.get("validate_first", True)
1064
+ and row.get("entity_ids")
1065
+ and is_single_entity_target(entity_id)
1066
+ ):
1067
+ transition = next(
1048
1068
  (
1049
- t.get("old_state")
1069
+ t
1050
1070
  for t in transitions
1051
1071
  if isinstance(t, dict) and t.get("entity_id") == entity_id
1052
1072
  ),
1053
1073
  None,
1054
1074
  )
1055
- if old_state is None:
1056
- err = create_error_response(
1057
- ErrorCode.ENTITY_NOT_FOUND,
1058
- f"Entity not found: {entity_id}",
1059
- suggestions=[
1060
- "Use ha_search to find the correct entity",
1061
- "Check the entity is not disabled in Home Assistant",
1062
- ],
1063
- context={"entity_id": entity_id, "action": action},
1064
- )
1065
- err["service_call"] = service_call
1066
- return err
1075
+ if transition is not None:
1076
+ old_state = transition.get("old_state")
1077
+ if old_state is None:
1078
+ err = create_error_response(
1079
+ ErrorCode.ENTITY_NOT_FOUND,
1080
+ f"Entity not found: {entity_id}",
1081
+ suggestions=[
1082
+ "Use ha_search to find the correct entity",
1083
+ "Check the entity is not disabled in Home Assistant",
1084
+ ],
1085
+ context={"entity_id": entity_id, "action": action},
1086
+ )
1087
+ err["service_call"] = service_call
1088
+ return err
1089
+ if (
1090
+ isinstance(old_state, dict)
1091
+ and old_state.get("state") == "unavailable"
1092
+ ):
1093
+ new_state = transition.get("new_state")
1094
+ if new_state is None and not component_op.get("confirmed"):
1095
+ # Removed during the dispatch -- a more specific outcome
1096
+ # than "still unavailable", which would falsely assert it
1097
+ # still exists.
1098
+ err = create_error_response(
1099
+ ErrorCode.ENTITY_NOT_FOUND,
1100
+ f"Entity not found: {entity_id}",
1101
+ suggestions=[
1102
+ "Use ha_search to find the correct entity",
1103
+ "Check the entity is not disabled in Home Assistant",
1104
+ ],
1105
+ context={"entity_id": entity_id, "action": action},
1106
+ )
1107
+ err["service_call"] = service_call
1108
+ return err
1109
+ if (
1110
+ not component_op.get("confirmed")
1111
+ and isinstance(new_state, dict)
1112
+ and new_state.get("state") == "unavailable"
1113
+ ):
1114
+ # Gated on NOT confirmed AND still unavailable per the
1115
+ # best-available post-dispatch read: unlike a nonexistent
1116
+ # entity, an "unavailable" one legitimately stays in the
1117
+ # component's confirmation wait and can reconnect and
1118
+ # transition mid-dispatch (see _confirmable_entity_ids) —
1119
+ # a confirmed op, or one whose new_state shows it
1120
+ # reconnected to some OTHER state (just not the expected
1121
+ # hint), must never be reported as this failure just
1122
+ # because its PRE-state was unavailable.
1123
+ err = create_entity_unavailable_after_dispatch_error(entity_id)
1124
+ err["action"] = action
1125
+ err["service_call"] = service_call
1126
+ return err
1067
1127
  final_state = next(
1068
1128
  (
1069
1129
  t["new_state"].get("state")
@@ -14,6 +14,7 @@ from fastmcp.tools import tool
14
14
  from pydantic import ConfigDict, Field, SkipValidation, TypeAdapter, ValidationError
15
15
 
16
16
  from ..client.rest_client import (
17
+ HomeAssistantAPIError,
17
18
  HomeAssistantClient,
18
19
  HomeAssistantCommandError,
19
20
  HomeAssistantCommandNotSent,
@@ -23,6 +24,8 @@ from ..client.websocket_client import get_websocket_client
23
24
  from ..errors import (
24
25
  ErrorCode,
25
26
  create_connection_error,
27
+ create_entity_not_found_error,
28
+ create_entity_unavailable_after_dispatch_error,
26
29
  create_error_response,
27
30
  create_validation_error,
28
31
  )
@@ -53,6 +56,7 @@ from .util_helpers import (
53
56
  BLOCKED_WS_WRITE_COMMANDS,
54
57
  JSON_STRING_COERCION,
55
58
  compact_service_result,
59
+ is_single_entity_target,
56
60
  parse_json_param,
57
61
  parse_string_list_param,
58
62
  project_entity_record,
@@ -924,8 +928,8 @@ class ServiceTools:
924
928
  response["warnings"].append(_COMPONENT_RESPONSE_UNCONFIRMED_WARNING)
925
929
  return response
926
930
 
927
- async def _capture_initial_state(self, entity_id: str | None) -> str | None:
928
- """Capture the current state of an entity before a service call.
931
+ async def _validate_entity_before_wait(self, entity_id: str | None) -> str | None:
932
+ """Fetch entity_id's pre-call state, failing fast on a settled bad target.
929
933
 
930
934
  ``entity_id`` stays optional in the signature to match the caller's
931
935
  own ``str | None`` (a service call may target zero entities); the
@@ -936,18 +940,54 @@ class ServiceTools:
936
940
  not in a form the type checker can see through. Narrowing here
937
941
  instead of trusting the caller keeps ``get_entity_state`` (which
938
942
  genuinely requires a ``str``) honestly typed.
943
+
944
+ A clean 404 (the entity does not exist) settles the question with
945
+ certainty before any service dispatch or 10s state-change wait, so
946
+ raise immediately instead of silently degrading into a wait that can
947
+ never confirm. Any other fetch failure is NOT conclusive -- it
948
+ returns ``None`` and lets the call proceed as before, since the
949
+ entity may well exist and this could be a transient blip.
950
+
951
+ Deliberately NOT fast-failed here: a fetched ``"unavailable"``
952
+ state. Unlike a 404, it does not prove the target can never respond
953
+ -- dispatching the service is exactly what can reconnect it (e.g. a
954
+ device that wakes on the very command being sent), and failing here
955
+ would mean that command never reaches Home Assistant at all. The
956
+ entity's initial state is still returned so ``_verify_state_change``
957
+ can classify a genuine confirmation lapse as ENTITY_UNAVAILABLE only
958
+ if a live re-check shows it is STILL unavailable afterward -- mirrors
959
+ the component path's post-dispatch-state check.
960
+
961
+ A comma-separated ``entity_id`` ("light.a,light.b") or a Home
962
+ Assistant magic broadcast target (``entity_id="all"`` /
963
+ ``ENTITY_MATCH_ALL``, or ``"none"``) is a valid target for the
964
+ service-call payload, but the single-entity ``/api/states/<id>``
965
+ endpoint has no way to resolve either -- a 404 there proves nothing
966
+ about whether the underlying call can succeed, so the fast-fail is
967
+ skipped entirely for one (see ``is_single_entity_target``; matches
968
+ the component route's own comma exclusion in
969
+ ``_maybe_component_call_service``).
939
970
  """
940
971
  if entity_id is None:
941
972
  return None
973
+ is_single_target = is_single_entity_target(entity_id)
942
974
  try:
943
975
  state_data = await self._client.get_entity_state(entity_id)
944
- return state_data.get("state") if state_data else None
976
+ except HomeAssistantAPIError as e:
977
+ if is_single_target and e.status_code == 404:
978
+ raise_tool_error(create_entity_not_found_error(entity_id))
979
+ logger.debug(
980
+ f"Could not fetch initial state for {entity_id}: {e} — state verification may be degraded"
981
+ )
982
+ return None
945
983
  except Exception as e:
946
984
  logger.debug(
947
985
  f"Could not fetch initial state for {entity_id}: {e} — state verification may be degraded"
948
986
  )
949
987
  return None
950
988
 
989
+ return state_data.get("state") if state_data else None
990
+
951
991
  async def _verify_state_change(
952
992
  self,
953
993
  entity_id: str,
@@ -955,7 +995,17 @@ class ServiceTools:
955
995
  initial_state: str | None,
956
996
  response: dict[str, Any],
957
997
  ) -> None:
958
- """Wait for and verify entity state change after a service call, updating response in place."""
998
+ """Wait for and verify entity state change after a service call, updating response in place.
999
+
1000
+ An entity that was ``"unavailable"`` at dispatch time was NOT failed
1001
+ fast (see ``_validate_entity_before_wait``) -- it may have reconnected
1002
+ once the command actually reached it. Only once the wait genuinely
1003
+ lapses does a fresh live re-check decide whether it's still
1004
+ unavailable (report the precise ``ENTITY_UNAVAILABLE``) or reconnected
1005
+ to some state other than the hint (leave the existing generic
1006
+ partial/timeout wording) -- mirrors the component path's
1007
+ post-dispatch-state check.
1008
+ """
959
1009
  try:
960
1010
  expected = _SERVICE_TO_STATE.get(service)
961
1011
  new_state = await wait_for_state_change(
@@ -967,15 +1017,58 @@ class ServiceTools:
967
1017
  )
968
1018
  if new_state:
969
1019
  response["verified_state"] = new_state.get("state")
970
- else:
971
- response.setdefault("warnings", []).append(
972
- "Service executed but state change could not be verified within timeout."
1020
+ return
1021
+ if initial_state == "unavailable" and await self._still_unavailable(
1022
+ entity_id
1023
+ ):
1024
+ raise_tool_error(
1025
+ create_entity_unavailable_after_dispatch_error(entity_id)
973
1026
  )
1027
+ response.setdefault("warnings", []).append(
1028
+ "Service executed but state change could not be verified within timeout."
1029
+ )
1030
+ except ToolError:
1031
+ raise
974
1032
  except Exception as e:
975
1033
  response.setdefault("warnings", []).append(
976
1034
  f"Service executed but state verification failed: {e}"
977
1035
  )
978
1036
 
1037
+ async def _still_unavailable(self, entity_id: str) -> bool:
1038
+ """Live re-check: True only when a successful fetch explicitly shows
1039
+ ``"unavailable"``.
1040
+
1041
+ A 404 here means the entity was REMOVED since the initial fetch — a
1042
+ more specific outcome than "still unavailable", so it raises
1043
+ ENTITY_NOT_FOUND directly rather than being folded into it. Any other
1044
+ fetch failure (network blip, an expired token surfacing as
1045
+ ``HomeAssistantAuthError`` -- a sibling of ``HomeAssistantAPIError``,
1046
+ not a subclass, so it lands in the generic branch below) or a
1047
+ response with no usable ``state`` is inconclusive, not proof either
1048
+ way, so it returns ``False`` and lets the caller fall through to the
1049
+ generic verification-failed wording instead of confidently asserting
1050
+ an unproven "still unavailable". Logged at WARNING (matching the
1051
+ sibling re-check in ``_validate_entity_before_wait`` and
1052
+ ``wait_for_state_change`` in util_helpers.py) so an operational
1053
+ failure here -- an expired token, in particular -- leaves a
1054
+ server-side trace instead of silently reading as a generic timeout.
1055
+ """
1056
+ try:
1057
+ current = await self._client.get_entity_state(entity_id)
1058
+ except HomeAssistantAPIError as e:
1059
+ if e.status_code == 404:
1060
+ raise_tool_error(create_entity_not_found_error(entity_id))
1061
+ logger.warning(
1062
+ f"Post-timeout unavailable re-check for {entity_id} failed: {e} — treating as inconclusive"
1063
+ )
1064
+ return False
1065
+ except Exception as e:
1066
+ logger.warning(
1067
+ f"Post-timeout unavailable re-check for {entity_id} failed: {e} — treating as inconclusive"
1068
+ )
1069
+ return False
1070
+ return isinstance(current, dict) and current.get("state") == "unavailable"
1071
+
979
1072
  @staticmethod
980
1073
  def _split_return_response_envelope(
981
1074
  result: Any, *, return_response: bool
@@ -1465,6 +1558,81 @@ class ServiceTools:
1465
1558
  return new_state.get("state")
1466
1559
  return None
1467
1560
 
1561
+ @staticmethod
1562
+ def _raise_for_missing_or_unavailable_target(
1563
+ transitions: list[Any], entity_id: str
1564
+ ) -> None:
1565
+ """Report why a confirmation never landed, once it's certain it never will.
1566
+
1567
+ Only reached when the component result was NOT confirmed. No-op
1568
+ (returns) unless ``entity_id`` is a single literal entity ID (see
1569
+ ``is_single_entity_target``): a comma-joined multi-target or a Home
1570
+ Assistant magic broadcast target (``"all"`` / ``"none"``) is never a
1571
+ literal entity in the state machine, so its transition row carries a
1572
+ null ``old_state`` unconditionally -- that proves nothing about
1573
+ whether the (possibly successful) broadcast landed, unlike a genuine
1574
+ single-entity miss.
1575
+
1576
+ For a real single-entity target: the transition's ``old_state`` is
1577
+ ``None`` for an entity absent from the state machine at pre-dispatch
1578
+ time — the component excludes it from the wait entirely (it
1579
+ structurally cannot ever confirm), so an unconfirmed result with a
1580
+ null ``old_state`` is unambiguous: raise ENTITY_NOT_FOUND immediately.
1581
+ A real state dict whose ``state`` is ``"unavailable"`` stayed IN the
1582
+ component's confirmation wait (unlike a nonexistent target, it can
1583
+ legitimately reconnect and transition mid-dispatch — the caller only
1584
+ reaches this branch after that chance has already lapsed); what
1585
+ happened to it by ``new_state`` (the best-available POST-dispatch
1586
+ read: ``_post_state`` re-reads live if the listener's exact
1587
+ expected-state event never arrived) then decides the outcome:
1588
+
1589
+ * ``new_state`` is ``None`` — the entity was REMOVED during the
1590
+ dispatch, a more specific outcome than "still unavailable" (which
1591
+ would falsely assert it still exists), so raise ENTITY_NOT_FOUND.
1592
+ * ``new_state`` still shows ``"unavailable"`` — raise
1593
+ ENTITY_UNAVAILABLE.
1594
+ * ``new_state`` shows anything else — the target reconnected but
1595
+ settled on some state other than the hint (e.g. a lock that
1596
+ reached "locking", not yet "locked"), demonstrably no longer
1597
+ unavailable even though this op never confirmed. Reporting it as
1598
+ ENTITY_UNAVAILABLE from the stale pre-state alone would discard
1599
+ that real result, so no-op instead.
1600
+
1601
+ No-op also for any other unconfirmed target — a real, available
1602
+ entity whose confirming event simply never arrived, OR no transition
1603
+ row at all (``_dispatched_unconfirmed_result``'s empty
1604
+ ``transitions`` after a post-dispatch formatting failure — the write
1605
+ already landed, so an absent row proves nothing about existence) —
1606
+ leaving the caller's existing generic partial/timeout wording for
1607
+ those genuine-ambiguity cases.
1608
+ """
1609
+ if not is_single_entity_target(entity_id):
1610
+ return
1611
+ transition = next(
1612
+ (
1613
+ t
1614
+ for t in transitions
1615
+ if isinstance(t, dict) and t.get("entity_id") == entity_id
1616
+ ),
1617
+ None,
1618
+ )
1619
+ if transition is None:
1620
+ return
1621
+ old_state = transition.get("old_state")
1622
+ if old_state is None:
1623
+ raise_tool_error(create_entity_not_found_error(entity_id))
1624
+ if not (
1625
+ isinstance(old_state, dict) and old_state.get("state") == "unavailable"
1626
+ ):
1627
+ return
1628
+ new_state = transition.get("new_state")
1629
+ if new_state is None:
1630
+ # Removed during the dispatch -- a more specific outcome than
1631
+ # "still unavailable", which would falsely assert it still exists.
1632
+ raise_tool_error(create_entity_not_found_error(entity_id))
1633
+ if isinstance(new_state, dict) and new_state.get("state") == "unavailable":
1634
+ raise_tool_error(create_entity_unavailable_after_dispatch_error(entity_id))
1635
+
1468
1636
  def _build_component_call_response(
1469
1637
  self,
1470
1638
  component_result: dict[str, Any],
@@ -1492,6 +1660,21 @@ class ServiceTools:
1492
1660
  timeout path produces (``_build_timeout_response``).
1493
1661
  """
1494
1662
  transitions = component_result.get("transitions") or []
1663
+ # A nonexistent target's pre-dispatch read excludes it from the wait
1664
+ # entirely, so it can never reach ``confirmed``; an "unavailable" one
1665
+ # stays eligible and CAN reach it (a genuine reconnect-and-confirm) —
1666
+ # only an UNCONFIRMED result is checked here, before building a generic
1667
+ # partial/timeout response, using the same ``old_state``/``new_state``
1668
+ # signal ``device_control.py``'s bulk-op mapping already reads from
1669
+ # this identical transitions shape. A genuine confirmation lapse on a
1670
+ # real, available target (old_state present, not "unavailable") falls
1671
+ # through unchanged to the existing partial-success wording below.
1672
+ if (
1673
+ should_wait
1674
+ and entity_id is not None
1675
+ and not component_result.get("confirmed")
1676
+ ):
1677
+ self._raise_for_missing_or_unavailable_target(transitions, entity_id)
1495
1678
  # The transition new_states are State.as_dict() records — the same shape the
1496
1679
  # legacy REST POST returns — so the existing projection helpers apply
1497
1680
  # unchanged (compact filters to the target, drops metadata / heavy lists).
@@ -1538,9 +1721,13 @@ class ServiceTools:
1538
1721
  )
1539
1722
  if should_wait:
1540
1723
  if component_result.get("partial"):
1541
- # Dispatched, but the confirming state_changed did not arrive within
1542
- # the wait — the same partial-success contract the legacy timeout
1543
- # path reports (success stays True; verification is never a failure).
1724
+ # Reached only when _raise_for_missing_or_unavailable_target above
1725
+ # did NOT raise -- i.e. a genuine confirmation lapse on a real,
1726
+ # available target whose confirming state_changed simply never
1727
+ # arrived within the wait. success stays True here (the same
1728
+ # partial-success contract the legacy timeout path reports);
1729
+ # unlike this generic case, a target proven nonexistent or still
1730
+ # unavailable is a raised ToolError, not a partial success.
1544
1731
  response["partial"] = True
1545
1732
  response.setdefault("warnings", []).append(
1546
1733
  "Service executed but state change could not be verified "
@@ -1819,10 +2006,16 @@ class ServiceTools:
1819
2006
  return component_response
1820
2007
 
1821
2008
  # Legacy REST path (component absent, or it never dispatched): capture
1822
- # initial state before the call for the WS-subscribe verification.
2009
+ # initial state before the call for the WS-subscribe verification. A
2010
+ # settled 404 (single-entity target definitely does not exist) raises
2011
+ # here, BEFORE the dispatch below, so a known-invalid call never
2012
+ # reaches the service POST or the 10s confirmation wait. An
2013
+ # "unavailable" target is NOT fast-failed here -- dispatching is
2014
+ # exactly what might reconnect it; see _verify_state_change for its
2015
+ # post-dispatch classification instead.
1823
2016
  initial_state = None
1824
2017
  if should_wait:
1825
- initial_state = await self._capture_initial_state(entity_id)
2018
+ initial_state = await self._validate_entity_before_wait(entity_id)
1826
2019
 
1827
2020
  result = await self._client.call_service(
1828
2021
  domain, service, service_data, return_response=return_response_bool
@@ -53,6 +53,30 @@ _SERVICE_TO_STATE: dict[str, str] = {
53
53
  }
54
54
 
55
55
 
56
+ def is_single_entity_target(entity_id: str | None) -> bool:
57
+ """True when ``entity_id`` looks like exactly one real, literal entity ID.
58
+
59
+ False for two shapes a not-found/unavailable lookup miss cannot settle
60
+ anything about:
61
+
62
+ * A comma-separated multi-target list ("light.a,light.b") — a valid
63
+ service-call payload the single-entity ``/api/states/<id>`` endpoint
64
+ has no syntax for, so its 404 on the literal joined string proves
65
+ nothing about the individual targets.
66
+ * A Home Assistant magic broadcast target — ``ENTITY_MATCH_ALL`` ("all")
67
+ or ``ENTITY_MATCH_NONE`` ("none"), both defined in
68
+ ``homeassistant/const.py`` and neither a literal entity in the state
69
+ machine, so a states-endpoint miss for either is expected and proves
70
+ nothing about whether the underlying service call can succeed.
71
+
72
+ A normal entity ID always has a ``domain.object_id`` shape (contains a
73
+ dot); the absence of one is the general, defensive signal used here
74
+ rather than hardcoding just "all"/"none" by name, so any other future
75
+ magic target is covered the same way.
76
+ """
77
+ return entity_id is not None and "," not in entity_id and "." in entity_id
78
+
79
+
56
80
  def websocket_error_message(error: Any) -> str:
57
81
  """Extract a readable message from a Home Assistant websocket error."""
58
82
  if isinstance(error, dict):
@@ -100,8 +100,10 @@ class SearchKeywordsTransform(Transform):
100
100
  a narrower one so BM25 ranks the tool *lower* for broad queries.
101
101
 
102
102
  The original description is preserved unless an override is applied.
103
- Only active when added to the transform pipeline (i.e., behind
104
- the ``enable_tool_search`` toggle).
103
+
104
+ Added to the transform pipeline unconditionally (#940), so ``keywords``
105
+ reach every client regardless of ``enable_tool_search``; only
106
+ ``overrides`` are gated behind that toggle.
105
107
  """
106
108
 
107
109
  def __init__(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ha-mcp-dev
3
- Version: 8.4.0.dev2482
3
+ Version: 8.4.0.dev2485
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