ha-mcp-dev 8.4.2.dev2533__tar.gz → 8.4.2.dev2534__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 (304) hide show
  1. {ha_mcp_dev-8.4.2.dev2533/src/ha_mcp_dev.egg-info → ha_mcp_dev-8.4.2.dev2534}/PKG-INFO +1 -1
  2. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/pyproject.toml +1 -1
  3. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/bulk_selector.py +20 -11
  4. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/component_api.py +7 -0
  5. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/component_devices.py +9 -2
  6. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/smart_search/_entities.py +9 -12
  7. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/smart_search/_overview.py +11 -12
  8. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_entities.py +5 -1
  9. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_registry.py +36 -7
  10. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_search.py +42 -14
  11. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_voice_assistant.py +5 -1
  12. ha_mcp_dev-8.4.2.dev2534/src/ha_mcp/utils/device_registry_semantics.py +171 -0
  13. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/visibility/resolver.py +85 -26
  14. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
  15. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp_dev.egg-info/SOURCES.txt +1 -0
  16. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/LICENSE +0 -0
  17. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/MANIFEST.in +0 -0
  18. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/README.md +0 -0
  19. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/setup.cfg +0 -0
  20. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/__init__.py +0 -0
  21. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/__main__.py +0 -0
  22. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_pypi_marker +0 -0
  23. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_rich_compat.py +0 -0
  24. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/__init__.py +0 -0
  25. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/requirements.txt +0 -0
  26. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/LICENSE +0 -0
  27. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/MANIFEST.sha256 +0 -0
  28. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/VENDORED +0 -0
  29. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/__init__.py +0 -0
  30. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/__main__.py +0 -0
  31. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/asyncio/__init__.py +0 -0
  32. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/asyncio/client.py +0 -0
  33. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/asyncio/connection.py +0 -0
  34. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/asyncio/messages.py +0 -0
  35. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/asyncio/router.py +0 -0
  36. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/asyncio/server.py +0 -0
  37. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/auth.py +0 -0
  38. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/cli.py +0 -0
  39. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/client.py +0 -0
  40. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/connection.py +0 -0
  41. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/datastructures.py +0 -0
  42. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/exceptions.py +0 -0
  43. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/extensions/__init__.py +0 -0
  44. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/extensions/base.py +0 -0
  45. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/extensions/permessage_deflate.py +0 -0
  46. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/frames.py +0 -0
  47. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/headers.py +0 -0
  48. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/http11.py +0 -0
  49. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/imports.py +0 -0
  50. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/legacy/__init__.py +0 -0
  51. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/legacy/auth.py +0 -0
  52. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/legacy/client.py +0 -0
  53. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/legacy/exceptions.py +0 -0
  54. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/legacy/framing.py +0 -0
  55. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/legacy/handshake.py +0 -0
  56. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/legacy/http.py +0 -0
  57. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/legacy/protocol.py +0 -0
  58. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/legacy/server.py +0 -0
  59. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/protocol.py +0 -0
  60. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/proxy.py +0 -0
  61. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/py.typed +0 -0
  62. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/server.py +0 -0
  63. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/streams.py +0 -0
  64. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/sync/__init__.py +0 -0
  65. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/sync/client.py +0 -0
  66. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/sync/connection.py +0 -0
  67. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/sync/messages.py +0 -0
  68. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/sync/router.py +0 -0
  69. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/sync/server.py +0 -0
  70. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/sync/utils.py +0 -0
  71. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/trio/__init__.py +0 -0
  72. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/trio/client.py +0 -0
  73. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/trio/connection.py +0 -0
  74. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/trio/messages.py +0 -0
  75. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/trio/router.py +0 -0
  76. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/trio/server.py +0 -0
  77. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/trio/utils.py +0 -0
  78. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/typing.py +0 -0
  79. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/uri.py +0 -0
  80. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/utils.py +0 -0
  81. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_vendor/websockets/version.py +0 -0
  82. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/_version.py +0 -0
  83. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/auth/__init__.py +0 -0
  84. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/auth/consent_form.py +0 -0
  85. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/auth/oidc_compat.py +0 -0
  86. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/auth/provider.py +0 -0
  87. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/backup_manager.py +0 -0
  88. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/browser_landing.py +0 -0
  89. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/client/__init__.py +0 -0
  90. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/client/rest_client.py +0 -0
  91. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/client/supervisor_client.py +0 -0
  92. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/client/websocket_client.py +0 -0
  93. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/client/websocket_listener.py +0 -0
  94. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/config.py +0 -0
  95. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/dashboard_screenshot/__init__.py +0 -0
  96. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/dashboard_screenshot/capture.py +0 -0
  97. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/dashboard_screenshot/content.py +0 -0
  98. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/dashboard_screenshot/paths.py +0 -0
  99. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/dashboard_screenshot/provision.py +0 -0
  100. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/dashboard_screenshot/theme_guard.py +0 -0
  101. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/errors.py +0 -0
  102. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/hacs_auto_refresh.py +0 -0
  103. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/llm_exposure.py +0 -0
  104. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/log_filters.py +0 -0
  105. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/policy/__init__.py +0 -0
  106. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/policy/approval_queue.py +0 -0
  107. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/policy/editing.py +0 -0
  108. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/policy/evaluator.py +0 -0
  109. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/policy/handlers.py +0 -0
  110. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/policy/middleware.py +0 -0
  111. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/policy/model.py +0 -0
  112. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/policy/persistence.py +0 -0
  113. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/policy/value_sources.py +0 -0
  114. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/py.typed +0 -0
  115. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/read_only.py +0 -0
  116. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/redaction.py +0 -0
  117. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/renamed_tools.py +0 -0
  118. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
  119. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
  120. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
  121. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
  122. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
  123. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
  124. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
  125. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
  126. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
  127. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
  128. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/evals/area-target-over-entity-list/case.yaml +0 -0
  129. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/evals/arrive-home-automation/case.yaml +0 -0
  130. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/evals/assist-shortcut-card/case.yaml +0 -0
  131. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/evals/avg-temperature/case.yaml +0 -0
  132. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/evals/backup-deletion-confirmation/case.yaml +0 -0
  133. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/evals/blueprint-authoring/case.yaml +0 -0
  134. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/evals/button-event-entity/case.yaml +0 -0
  135. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/evals/device-id-trigger-migration/case.yaml +0 -0
  136. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/evals/helper-flow-fields-not-platform-yaml/case.yaml +0 -0
  137. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/evals/media-player-song/case.yaml +0 -0
  138. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/evals/motion-light-automation-mode/case.yaml +0 -0
  139. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/evals/rename-entity-impact-analysis/case.yaml +0 -0
  140. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/evals/template-attributes-yaml-exception/case.yaml +0 -0
  141. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/evals/timer-finished-automation/case.yaml +0 -0
  142. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/evals/vacuum-last-clean/case.yaml +0 -0
  143. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/scripts/check_eval_cases.py +0 -0
  144. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
  145. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/appdaemon.md +0 -0
  146. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
  147. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/backups.md +0 -0
  148. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/blueprint-guide.md +0 -0
  149. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
  150. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
  151. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
  152. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
  153. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
  154. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
  155. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
  156. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/scenes.md +0 -0
  157. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
  158. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/yaml-only-integrations.md +0 -0
  159. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/server.py +0 -0
  160. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/settings_ui/__init__.py +0 -0
  161. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/settings_ui/_handlers_advanced.py +0 -0
  162. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/settings_ui/_handlers_backups.py +0 -0
  163. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/settings_ui/_handlers_fs.py +0 -0
  164. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/settings_ui/_handlers_server.py +0 -0
  165. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/settings_ui/_handlers_theme.py +0 -0
  166. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/settings_ui/_handlers_tools.py +0 -0
  167. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/settings_ui/_i18n.py +0 -0
  168. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/settings_ui/_locale_policy.py +0 -0
  169. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/settings_ui/_persistence.py +0 -0
  170. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/settings_ui/_supervisor.py +0 -0
  171. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/settings_ui/_theme.py +0 -0
  172. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/settings_ui/_tools_meta.py +0 -0
  173. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/settings_ui/locales/README.md +0 -0
  174. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/settings_ui/locales/cs.json +0 -0
  175. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/settings_ui/locales/de.json +0 -0
  176. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/settings_ui/locales/en.json +0 -0
  177. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/settings_ui/locales/eo.json +0 -0
  178. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/settings_ui/locales/es.json +0 -0
  179. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/settings_ui/locales/fr.json +0 -0
  180. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/settings_ui/locales/it.json +0 -0
  181. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/settings_ui/locales/ko.json +0 -0
  182. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/settings_ui/locales/nl.json +0 -0
  183. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/settings_ui/locales/pl.json +0 -0
  184. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/settings_ui/locales/ru.json +0 -0
  185. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/settings_ui/locales/sv.json +0 -0
  186. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/settings_ui/locales/tlh.json +0 -0
  187. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/settings_ui/locales/zh-Hans.json +0 -0
  188. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/settings_ui/settings.css +0 -0
  189. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/settings_ui/settings.html +0 -0
  190. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/settings_ui/settings.js +0 -0
  191. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/smoke_test.py +0 -0
  192. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
  193. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/strict_bps.py +0 -0
  194. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/__init__.py +0 -0
  195. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/auto_backup.py +0 -0
  196. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/backup.py +0 -0
  197. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/best_practice_checker.py +0 -0
  198. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/best_practice_result.py +0 -0
  199. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/best_practice_variables.py +0 -0
  200. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/component_config_entries.py +0 -0
  201. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/component_config_reads.py +0 -0
  202. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/component_registries.py +0 -0
  203. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/component_registry_lookup.py +0 -0
  204. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/config_entry_flow.py +0 -0
  205. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/config_entry_flow_form.py +0 -0
  206. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/config_entry_flow_menu.py +0 -0
  207. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/config_entry_flow_walker.py +0 -0
  208. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/config_entry_identity.py +0 -0
  209. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/config_entry_reconfigure.py +0 -0
  210. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/config_entry_reload_watch.py +0 -0
  211. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/device_control.py +0 -0
  212. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/error_log_parsing.py +0 -0
  213. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/hacs_registration.py +0 -0
  214. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/helpers.py +0 -0
  215. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/integration_reconfigure.py +0 -0
  216. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/log_common.py +0 -0
  217. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/log_sources.py +0 -0
  218. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/log_sources_supervisor.py +0 -0
  219. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/radio/__init__.py +0 -0
  220. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/radio/base.py +0 -0
  221. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/radio/matter.py +0 -0
  222. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/radio/thread.py +0 -0
  223. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/radio/zigbee.py +0 -0
  224. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/radio/zwave.py +0 -0
  225. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/reference_validator.py +0 -0
  226. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/registry.py +0 -0
  227. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/renamed_tool_middleware.py +0 -0
  228. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/smart_search/__init__.py +0 -0
  229. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/smart_search/_base.py +0 -0
  230. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/smart_search/_config.py +0 -0
  231. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/smart_search/_deep.py +0 -0
  232. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/smart_search/_fetch.py +0 -0
  233. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/smart_search/_graph.py +0 -0
  234. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/smart_search/_scenes.py +0 -0
  235. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/smart_search/_scoring.py +0 -0
  236. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tool_search_hint_middleware.py +0 -0
  237. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_addons.py +0 -0
  238. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_areas.py +0 -0
  239. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_assist_pipeline.py +0 -0
  240. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_blueprints.py +0 -0
  241. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_bug_report.py +0 -0
  242. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_calendar.py +0 -0
  243. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_camera.py +0 -0
  244. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_categories.py +0 -0
  245. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_code.py +0 -0
  246. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_config_automations.py +0 -0
  247. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
  248. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
  249. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
  250. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
  251. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_dashboard_screenshot.py +0 -0
  252. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_dev.py +0 -0
  253. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_energy.py +0 -0
  254. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_filesystem.py +0 -0
  255. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_groups.py +0 -0
  256. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_hacs.py +0 -0
  257. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_history.py +0 -0
  258. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_integrations.py +0 -0
  259. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_labels.py +0 -0
  260. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_logs.py +0 -0
  261. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_radio.py +0 -0
  262. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_resources.py +0 -0
  263. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_security_policy.py +0 -0
  264. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_service.py +0 -0
  265. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_services.py +0 -0
  266. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_system.py +0 -0
  267. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_themes.py +0 -0
  268. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_todo.py +0 -0
  269. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_traces.py +0 -0
  270. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_updates.py +0 -0
  271. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_utility.py +0 -0
  272. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
  273. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_yaml_read.py +0 -0
  274. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/tools_zones.py +0 -0
  275. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/util_helpers.py +0 -0
  276. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/tools/validation_middleware.py +0 -0
  277. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/transforms/__init__.py +0 -0
  278. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/transforms/categorized_search.py +0 -0
  279. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
  280. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/transport_security.py +0 -0
  281. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/update_check.py +0 -0
  282. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/utils/__init__.py +0 -0
  283. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/utils/config_hash.py +0 -0
  284. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/utils/config_write_lock.py +0 -0
  285. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/utils/data_paths.py +0 -0
  286. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/utils/domain_handlers.py +0 -0
  287. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/utils/entity_membership.py +0 -0
  288. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/utils/fuzzy_search.py +0 -0
  289. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
  290. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/utils/operation_manager.py +0 -0
  291. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/utils/python_sandbox.py +0 -0
  292. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/utils/skill_loader.py +0 -0
  293. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/utils/usage_logger.py +0 -0
  294. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/visibility/__init__.py +0 -0
  295. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/visibility/enforcement.py +0 -0
  296. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/visibility/model.py +0 -0
  297. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp/visibility/persistence.py +0 -0
  298. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
  299. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
  300. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
  301. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
  302. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/tests/__init__.py +0 -0
  303. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/tests/test_constants.py +0 -0
  304. {ha_mcp_dev-8.4.2.dev2533 → ha_mcp_dev-8.4.2.dev2534}/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.2.dev2533
3
+ Version: 8.4.2.dev2534
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.2.dev2533"
7
+ version = "8.4.2.dev2534"
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
@@ -13,6 +13,11 @@ from typing import Annotated, Any, NamedTuple, NotRequired, TypedDict
13
13
  from pydantic import ConfigDict, Field
14
14
 
15
15
  from ..client.rest_client import HomeAssistantClient
16
+ from ..utils.device_registry_semantics import (
17
+ DeviceRegistrySnapshot,
18
+ build_device_registry_snapshot,
19
+ effective_entity_area_id,
20
+ )
16
21
  from ..utils.domain_handlers import get_domain_handler
17
22
  from ..utils.entity_membership import normalize_member_entity_ids
18
23
  from ..visibility.resolver import VisibilityDataUnavailable, load_hidden_set
@@ -259,8 +264,10 @@ def _registry_rows(result: Any, label: str) -> list[dict[str, Any]]:
259
264
  return rows
260
265
 
261
266
 
262
- def _validate_device_registry_rows(device_rows: list[dict[str, Any]]) -> None:
263
- """Fail closed on a device row missing ``id``.
267
+ def _validate_device_registry_rows(
268
+ device_rows: list[dict[str, Any]],
269
+ ) -> DeviceRegistrySnapshot:
270
+ """Return a canonical snapshot or fail closed on ambiguous device evidence.
264
271
 
265
272
  ``visibility.resolver._parse_device_registry`` silently skips any device
266
273
  entry without an ``id`` even when the hidden-set resolver runs
@@ -272,11 +279,18 @@ def _validate_device_registry_rows(device_rows: list[dict[str, Any]]) -> None:
272
279
  resolver. Infrastructure-class (HA's own registry data is malformed),
273
280
  not the caller's selector to fix.
274
281
  """
275
- if any(not row.get("id") for row in device_rows):
282
+ if any(not isinstance(row.get("id"), str) or not row["id"] for row in device_rows):
276
283
  raise BulkSelectorInfrastructureError(
277
284
  "Home Assistant device registry returned a malformed entry",
278
285
  cause=InfrastructureErrorCause.MALFORMED_DEVICE_REGISTRY,
279
286
  )
287
+ snapshot = build_device_registry_snapshot(device_rows)
288
+ if snapshot.conflicting_ids:
289
+ raise BulkSelectorInfrastructureError(
290
+ "Home Assistant device registry returned a conflicting device identity",
291
+ cause=InfrastructureErrorCause.MALFORMED_DEVICE_REGISTRY,
292
+ )
293
+ return snapshot
280
294
 
281
295
 
282
296
  def _string_list(selector: Mapping[str, Any], key: str) -> list[str]:
@@ -305,10 +319,7 @@ def _entity_area_id(
305
319
  entry = entity_registry.get(entity_id)
306
320
  if not entry:
307
321
  return None
308
- if area_id := entry.get("area_id"):
309
- return str(area_id)
310
- device_id = entry.get("device_id")
311
- return device_areas.get(str(device_id)) if device_id else None
322
+ return effective_entity_area_id(entry, device_areas)
312
323
 
313
324
 
314
325
  def _expand_entity(
@@ -686,7 +697,7 @@ async def resolve_bulk_selector(
686
697
  )
687
698
  entity_rows = _registry_rows(topology.entities, "entity registry")
688
699
  device_rows = _registry_rows(topology.devices, "device registry")
689
- _validate_device_registry_rows(device_rows)
700
+ device_snapshot = _validate_device_registry_rows(device_rows)
690
701
  selected_areas = _select_area_ids(
691
702
  _registry_rows(topology.areas, "area registry"),
692
703
  _registry_rows(topology.floors, "floor registry"),
@@ -704,9 +715,7 @@ async def resolve_bulk_selector(
704
715
  for row in entity_rows
705
716
  if isinstance(row.get("entity_id"), str)
706
717
  }
707
- device_areas = {
708
- str(row["id"]): row.get("area_id") for row in device_rows if row.get("id")
709
- }
718
+ device_areas = device_snapshot.effective_area_by_id
710
719
  hidden, visibility_warnings = await _load_hidden_entities(
711
720
  client, topology.entities, topology.states, topology.devices, entity_registry
712
721
  )
@@ -56,6 +56,13 @@ UNKNOWN_COMMAND_CODE = "unknown_command"
56
56
  # routing gate can't trust command payloads shaped for a different generation.
57
57
  SUPPORTED_SCHEMA_VERSION = 1
58
58
 
59
+ # Additive semantic capability shared by every component path whose result is
60
+ # enriched from the device registry. The pre-2.1.3 ``device_list``/``device_get``
61
+ # commands (and search/overview/entity enrichment built on the same registry)
62
+ # predate Core 2026.9 child devices. Because component and server releases update
63
+ # independently, command presence alone cannot authorize the newer semantics.
64
+ DEVICE_REGISTRY_CHILD_SEMANTICS = "device_registry_child_semantics"
65
+
59
66
  # Negative (None) caps entries expire after this many seconds of monotonic
60
67
  # time, so a component installed / upgraded mid-session is re-probed and
61
68
  # adopted instead of being pinned to "absent" for the whole process lifetime
@@ -44,6 +44,7 @@ from ..client.rest_client import (
44
44
  )
45
45
  from ..client.websocket_client import get_websocket_client
46
46
  from .component_api import (
47
+ DEVICE_REGISTRY_CHILD_SEMANTICS,
47
48
  component_supports,
48
49
  get_component_caps,
49
50
  invalidate_caps,
@@ -85,7 +86,10 @@ async def fetch_device_via_component(
85
86
  only the device (``_resolve_ieee`` / capture / remove) are unchanged.
86
87
  """
87
88
  caps = await get_component_caps(client)
88
- if not component_supports(caps, "device_get"):
89
+ if not (
90
+ component_supports(caps, "device_get")
91
+ and component_supports(caps, DEVICE_REGISTRY_CHILD_SEMANTICS)
92
+ ):
89
93
  return None
90
94
  kwargs: dict[str, Any] = {"device_id": device_id}
91
95
  if include_entities:
@@ -167,7 +171,10 @@ async def fetch_device_list_via_component(client: Any) -> dict[str, Any] | None:
167
171
  fallback as :func:`fetch_device_via_component`.
168
172
  """
169
173
  caps = await get_component_caps(client)
170
- if not component_supports(caps, "device_list"):
174
+ if not (
175
+ component_supports(caps, "device_list")
176
+ and component_supports(caps, DEVICE_REGISTRY_CHILD_SEMANTICS)
177
+ ):
171
178
  return None
172
179
  try:
173
180
  ws = await get_websocket_client(
@@ -5,6 +5,10 @@ import logging
5
5
  from collections.abc import Mapping
6
6
  from typing import Any
7
7
 
8
+ from ...utils.device_registry_semantics import (
9
+ build_device_registry_snapshot,
10
+ effective_entity_area_id,
11
+ )
8
12
  from ...utils.entity_membership import normalize_member_entity_ids
9
13
  from ...utils.fuzzy_search import calculate_partial_ratio, calculate_ratio
10
14
  from ...visibility.resolver import (
@@ -651,13 +655,9 @@ class EntitySearchMixin(_SearchBase):
651
655
  def _parse_device_area_map(
652
656
  cls, result: Any, warnings: list[str] | None = None
653
657
  ) -> dict[str, str | None]:
654
- """Parse the device registry into ``device_id -> area_id``."""
655
- device_area_map: dict[str, str | None] = {}
656
- for device in cls._extract_registry_list(result, "device registry", warnings):
657
- device_id = device.get("id", "")
658
- if device_id:
659
- device_area_map[device_id] = device.get("area_id")
660
- return device_area_map
658
+ """Parse devices into IDs mapped to direct-or-parent effective areas."""
659
+ device_rows = cls._extract_registry_list(result, "device registry", warnings)
660
+ return build_device_registry_snapshot(list(device_rows)).effective_area_by_id
661
661
 
662
662
  @staticmethod
663
663
  def _match_exact_registry_ids(
@@ -875,7 +875,7 @@ class EntitySearchMixin(_SearchBase):
875
875
  include_hidden: bool,
876
876
  visibility_hidden: set[str],
877
877
  ) -> tuple[dict[str, str], set[str]]:
878
- """Map entity_id -> resolved area_id (entity area > device area).
878
+ """Map entity IDs to direct area, then device direct-or-parent effective area.
879
879
 
880
880
  Hidden entities are filtered only when include_hidden is False;
881
881
  otherwise they pass through and downstream applies the score penalty so
@@ -892,10 +892,7 @@ class EntitySearchMixin(_SearchBase):
892
892
  continue
893
893
  if is_hidden:
894
894
  hidden_entity_ids.add(entity_id)
895
- area_id = reg_info.get("area_id")
896
- device_id = reg_info.get("device_id")
897
- if not area_id and device_id:
898
- area_id = device_area_map.get(device_id)
895
+ area_id = effective_entity_area_id(reg_info, device_area_map)
899
896
  if area_id:
900
897
  entity_area_resolved[entity_id] = area_id
901
898
  return entity_area_resolved, hidden_entity_ids
@@ -6,6 +6,10 @@ import random
6
6
  from itertools import islice
7
7
  from typing import Any
8
8
 
9
+ from ...utils.device_registry_semantics import (
10
+ build_device_registry_snapshot,
11
+ effective_entity_area_id,
12
+ )
9
13
  from ...visibility.resolver import load_hidden_set
10
14
  from ..helpers import exception_to_structured_error
11
15
  from ._base import _SearchBase
@@ -237,23 +241,18 @@ class SystemOverviewMixin(_SearchBase):
237
241
  entity_registry: list[dict[str, Any]],
238
242
  device_registry: list[dict[str, Any]],
239
243
  ) -> dict[str, str | None]:
240
- """Map entity_id -> area_id. Priority: entity direct area_id > device area_id."""
241
- device_area_map: dict[str, str | None] = {}
242
- for device in device_registry:
243
- device_id = device.get("id", "")
244
- if device_id:
245
- device_area_map[device_id] = device.get("area_id")
244
+ """Map entity IDs to direct area, then device direct-or-parent effective area."""
245
+ device_area_map = build_device_registry_snapshot(
246
+ list(device_registry)
247
+ ).effective_area_by_id
246
248
 
247
249
  entity_area_map: dict[str, str | None] = {}
248
250
  for entry in entity_registry:
249
251
  entity_id = entry.get("entity_id")
250
- area_id = entry.get("area_id")
251
- if not area_id:
252
- device_id = entry.get("device_id")
253
- if device_id:
254
- area_id = device_area_map.get(device_id)
255
252
  if entity_id:
256
- entity_area_map[entity_id] = area_id
253
+ entity_area_map[entity_id] = effective_entity_area_id(
254
+ entry, device_area_map
255
+ )
257
256
  return entity_area_map
258
257
 
259
258
  @staticmethod
@@ -22,6 +22,7 @@ from ..client.websocket_client import get_websocket_client
22
22
  from ..errors import ErrorCode, create_error_response
23
23
  from .auto_backup import with_auto_backup
24
24
  from .component_api import (
25
+ DEVICE_REGISTRY_CHILD_SEMANTICS,
25
26
  component_supports,
26
27
  get_component_caps,
27
28
  invalidate_caps,
@@ -118,7 +119,10 @@ async def fetch_entity_enrichment_via_component(
118
119
  if not entity_ids:
119
120
  return None
120
121
  caps = await get_component_caps(client)
121
- if not component_supports(caps, "entity_enrich"):
122
+ if not (
123
+ component_supports(caps, "entity_enrich")
124
+ and component_supports(caps, DEVICE_REGISTRY_CHILD_SEMANTICS)
125
+ ):
122
126
  return None
123
127
  ids = list(entity_ids)
124
128
  chunks = [
@@ -16,6 +16,10 @@ from pydantic import Field
16
16
 
17
17
  from ..client.rest_client import HomeAssistantAPIError, HomeAssistantConnectionError
18
18
  from ..errors import ErrorCode, create_error_response
19
+ from ..utils.device_registry_semantics import (
20
+ EFFECTIVE_AREA_MARKER,
21
+ annotate_device_rows_with_effective_area,
22
+ )
19
23
  from .auto_backup import with_auto_backup
20
24
  from .component_devices import (
21
25
  fetch_device_list_via_component,
@@ -137,7 +141,7 @@ def _get_device_info(device: dict[str, Any]) -> dict[str, Any]:
137
141
  "manufacturer": device.get("manufacturer"),
138
142
  "model": device.get("model"),
139
143
  "sw_version": device.get("sw_version"),
140
- "area_id": device.get("area_id"),
144
+ "area_id": device.get(EFFECTIVE_AREA_MARKER, device.get("area_id")),
141
145
  "integration_type": integration_type,
142
146
  "integration_sources": integration_sources,
143
147
  "via_device_id": device.get("via_device_id"),
@@ -158,6 +162,25 @@ def _get_device_info(device: dict[str, Any]) -> dict[str, Any]:
158
162
  return device_info
159
163
 
160
164
 
165
+ def _device_config_entries(device: dict[str, Any]) -> list[str]:
166
+ """Return the exact owning config-entry IDs for a main or child row.
167
+
168
+ Ordinary ``DeviceEntry`` rows carry ``config_entries`` while Core 2026.9
169
+ ``ChildDeviceEntry`` rows carry one singular ``config_entry_id``. Never mix,
170
+ coerce, or repair malformed ownership evidence: an explicitly present plural
171
+ field is authoritative and must be a list of non-empty strings.
172
+ """
173
+ if "config_entries" in device:
174
+ raw = device.get("config_entries")
175
+ if not isinstance(raw, list) or not all(
176
+ isinstance(entry_id, str) and entry_id for entry_id in raw
177
+ ):
178
+ return []
179
+ return list(raw)
180
+ entry_id = device.get("config_entry_id")
181
+ return [entry_id] if isinstance(entry_id, str) and entry_id else []
182
+
183
+
161
184
  def _build_entity_maps(
162
185
  all_entities: list[dict[str, Any]], need_full: bool
163
186
  ) -> tuple[dict[str, str], dict[str, list[dict[str, Any]]]]:
@@ -251,8 +274,8 @@ async def _fetch_device_rows(client: Any) -> list[dict[str, Any]]:
251
274
  """
252
275
  result = await fetch_device_list_via_component(client)
253
276
  if result is not None:
254
- return list(result.get("devices", []))
255
- return await _legacy_device_rows(client)
277
+ return annotate_device_rows_with_effective_area(list(result.get("devices", [])))
278
+ return annotate_device_rows_with_effective_area(await _legacy_device_rows(client))
256
279
 
257
280
 
258
281
  # HA core replies with this error code (ERR_NOT_FOUND) from
@@ -342,6 +365,9 @@ async def _single_device_and_entities(
342
365
  device = result.get("device")
343
366
  entities = result.get("entities")
344
367
  if isinstance(device, dict) and isinstance(entities, list):
368
+ device = dict(device)
369
+ if "effective_area_id" in result:
370
+ device[EFFECTIVE_AREA_MARKER] = result.get("effective_area_id")
345
371
  _, device_to_entities = _build_entity_maps(entities, need_full=True)
346
372
  return [device], device_to_entities
347
373
  # Either an authoritative not-found (device is None) — fall through so the
@@ -350,7 +376,9 @@ async def _single_device_and_entities(
350
376
  # entity registries.
351
377
  # Device registry first, then entity registry — the legacy wire order,
352
378
  # which the #1297 error-contract test pins with an ordered mock.
353
- all_devices = await _legacy_device_rows(client)
379
+ all_devices = annotate_device_rows_with_effective_area(
380
+ await _legacy_device_rows(client)
381
+ )
354
382
  all_entities = await _fetch_entity_rows(client)
355
383
  _, device_to_entities = _build_entity_maps(all_entities, need_full=True)
356
384
  return all_devices, device_to_entities
@@ -547,7 +575,7 @@ async def _get_single_device_result(
547
575
  device_info["serial_number"] = device.get("serial_number")
548
576
  device_info["disabled_by"] = device.get("disabled_by")
549
577
  device_info["labels"] = device.get("labels", [])
550
- device_info["config_entries"] = device.get("config_entries", [])
578
+ device_info["config_entries"] = _device_config_entries(device)
551
579
  device_info["connections"] = device.get("connections", [])
552
580
  device_info["identifiers"] = device.get("identifiers", [])
553
581
 
@@ -597,7 +625,8 @@ def _filter_devices(
597
625
  named_types = ["zigbee2mqtt", "zha", "zwave_js"]
598
626
  matched: list[dict[str, Any]] = []
599
627
  for device in all_devices:
600
- if area_id and device.get("area_id") != area_id:
628
+ effective_area = device.get(EFFECTIVE_AREA_MARKER, device.get("area_id"))
629
+ if area_id and effective_area != area_id:
601
630
  continue
602
631
  device_man = (device.get("manufacturer") or "").lower()
603
632
  if manufacturer_lower and manufacturer_lower not in device_man:
@@ -1134,7 +1163,7 @@ class RegistryTools:
1134
1163
  )
1135
1164
  device = await _lookup_device_for_remove(self._client, device_id)
1136
1165
 
1137
- config_entries = device.get("config_entries", [])
1166
+ config_entries = _device_config_entries(device)
1138
1167
  device_name = device.get("name_by_user") or device.get("name")
1139
1168
  if not config_entries:
1140
1169
  raise_tool_error(
@@ -25,6 +25,10 @@ from ..client.websocket_client import get_websocket_client
25
25
  from ..config import get_global_settings
26
26
  from ..errors import create_validation_error
27
27
  from ..transforms.categorized_search import DEFAULT_PINNED_TOOLS
28
+ from ..utils.device_registry_semantics import (
29
+ build_device_registry_snapshot,
30
+ effective_entity_area_id,
31
+ )
28
32
  from ..utils.entity_membership import normalize_member_entity_ids
29
33
  from ..utils.fuzzy_search import apply_hidden_penalty
30
34
  from ..visibility.model import VisibilityWire, wire_has_allowlist_dimensions
@@ -34,6 +38,7 @@ from ..visibility.resolver import (
34
38
  visibility_state_and_wire,
35
39
  )
36
40
  from .component_api import (
41
+ DEVICE_REGISTRY_CHILD_SEMANTICS,
37
42
  component_supports,
38
43
  get_component_caps,
39
44
  invalidate_caps,
@@ -869,6 +874,15 @@ def _ws_result_map(resp: Any) -> dict[str, dict[str, Any]]:
869
874
  return {}
870
875
 
871
876
 
877
+ def _ws_registry_rows(resp: Any) -> list[Any]:
878
+ """Return rows from a successful registry-list response, else an empty list."""
879
+ if isinstance(resp, dict) and resp.get("success"):
880
+ result = resp.get("result")
881
+ if isinstance(result, list):
882
+ return result
883
+ return []
884
+
885
+
872
886
  def _ws_registry_index(resp: Any, key: str) -> dict[str, dict[str, Any]]:
873
887
  """Index a ``config/*_registry/list`` reply by its id field (area_id/floor_id/…).
874
888
 
@@ -877,10 +891,9 @@ def _ws_registry_index(resp: Any, key: str) -> dict[str, dict[str, Any]]:
877
891
  empty rather than raising.
878
892
  """
879
893
  out: dict[str, dict[str, Any]] = {}
880
- if isinstance(resp, dict) and resp.get("success"):
881
- for item in resp.get("result") or []:
882
- if isinstance(item, dict) and item.get(key):
883
- out[item[key]] = item
894
+ for item in _ws_registry_rows(resp):
895
+ if isinstance(item, dict) and item.get(key):
896
+ out[item[key]] = item
884
897
  return out
885
898
 
886
899
 
@@ -902,13 +915,15 @@ def _entity_enrichment_fields(
902
915
  floors: dict[str, dict[str, Any]],
903
916
  labels: dict[str, dict[str, Any]],
904
917
  devices: dict[str, dict[str, Any]],
918
+ device_areas: dict[str, str | None],
905
919
  requested: tuple[str, ...],
906
920
  ) -> dict[str, Any]:
907
921
  """Compute the requested enrichment fields for one entity from registry data.
908
922
 
909
923
  Mirrors the component's ``_registry_enrichment`` so the legacy and
910
924
  component-served ``result_fields`` values agree: device-inherited area/labels
911
- (the entity's own value wins, else the device's), area→floor resolution, and
925
+ (the entity's own value wins, else the device's direct-or-parent effective
926
+ area), area→floor resolution, and
912
927
  label id→name (falling back to the id when a label has no name). ``aliases``
913
928
  pass through from the registry entry. Only the requested keys are returned.
914
929
 
@@ -919,13 +934,11 @@ def _entity_enrichment_fields(
919
934
  the sentinel stands for is already matched via the friendly name).
920
935
  """
921
936
  aliases = sorted(a for a in (entry.get("aliases") or []) if isinstance(a, str))
922
- area_id = entry.get("area_id")
937
+ area_id = effective_entity_area_id(entry, device_areas)
923
938
  label_ids = set(entry.get("labels") or [])
924
939
  device_id = entry.get("device_id")
925
940
  device = devices.get(device_id) if device_id else None
926
941
  if device:
927
- if area_id is None:
928
- area_id = device.get("area_id")
929
942
  label_ids |= set(device.get("labels") or [])
930
943
  area = areas.get(area_id) if area_id else None
931
944
  area_name = area.get("name") if area else None
@@ -2424,9 +2437,13 @@ class SearchTools:
2424
2437
  and _component_serves_search_types(req)
2425
2438
  ):
2426
2439
  caps = await get_component_caps(self._client)
2427
- if component_supports(caps, "search") and (
2428
- not _requested_membership(parsed_result_fields)
2429
- or component_supports(caps, "search_entity_membership")
2440
+ if (
2441
+ component_supports(caps, "search")
2442
+ and component_supports(caps, DEVICE_REGISTRY_CHILD_SEMANTICS)
2443
+ and (
2444
+ not _requested_membership(parsed_result_fields)
2445
+ or component_supports(caps, "search_entity_membership")
2446
+ )
2430
2447
  ):
2431
2448
  (
2432
2449
  route_component,
@@ -3153,10 +3170,19 @@ class SearchTools:
3153
3170
  areas = _ws_registry_index(names[0], "area_id") if need_names else {}
3154
3171
  floors = _ws_registry_index(names[1], "floor_id") if need_names else {}
3155
3172
  labels = _ws_registry_index(names[2], "label_id") if need_names else {}
3156
- devices = _ws_registry_index(names[3], "id") if need_names else {}
3173
+ device_rows = _ws_registry_rows(names[3]) if need_names else []
3174
+ device_snapshot = build_device_registry_snapshot(device_rows)
3175
+ devices = device_snapshot.by_id
3176
+ device_areas = device_snapshot.effective_area_by_id
3157
3177
  enrichment = {
3158
3178
  eid: _entity_enrichment_fields(
3159
- entries.get(eid) or {}, areas, floors, labels, devices, requested
3179
+ entries.get(eid) or {},
3180
+ areas,
3181
+ floors,
3182
+ labels,
3183
+ devices,
3184
+ device_areas,
3185
+ requested,
3160
3186
  )
3161
3187
  for eid in entity_ids
3162
3188
  }
@@ -4379,7 +4405,9 @@ class SearchTools:
4379
4405
  applied by ``ha_get_overview`` after this, identically on both paths.
4380
4406
  """
4381
4407
  caps = await get_component_caps(self._client)
4382
- if component_supports(caps, "overview"):
4408
+ if component_supports(caps, "overview") and component_supports(
4409
+ caps, DEVICE_REGISTRY_CHILD_SEMANTICS
4410
+ ):
4383
4411
  component_result = await self._overview_via_component(inputs)
4384
4412
  if component_result is not None:
4385
4413
  return component_result
@@ -24,6 +24,7 @@ from ..client.rest_client import (
24
24
  from ..client.websocket_client import get_websocket_client
25
25
  from ..errors import ErrorCode, create_error_response
26
26
  from .component_api import (
27
+ DEVICE_REGISTRY_CHILD_SEMANTICS,
27
28
  component_supports,
28
29
  get_component_caps,
29
30
  invalidate_caps,
@@ -89,7 +90,10 @@ class VoiceAssistantTools:
89
90
  ``component_devices.fetch_device_via_component``.
90
91
  """
91
92
  caps = await get_component_caps(self._client)
92
- if not component_supports(caps, "exposure"):
93
+ if not (
94
+ component_supports(caps, "exposure")
95
+ and component_supports(caps, DEVICE_REGISTRY_CHILD_SEMANTICS)
96
+ ):
93
97
  return None
94
98
  kwargs: dict[str, Any] = {}
95
99
  if entity_id is not None: