ha-mcp-dev 8.3.0.dev2377__tar.gz → 8.3.0.dev2382__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 (276) hide show
  1. {ha_mcp_dev-8.3.0.dev2377/src/ha_mcp_dev.egg-info → ha_mcp_dev-8.3.0.dev2382}/PKG-INFO +1 -1
  2. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/pyproject.toml +1 -1
  3. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/device_control.py +243 -31
  4. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/smart_search/_base.py +13 -1
  5. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/smart_search/_entities.py +232 -40
  6. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_search.py +19 -4
  7. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_service.py +129 -8
  8. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/utils/operation_manager.py +3 -3
  9. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
  10. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/LICENSE +0 -0
  11. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/MANIFEST.in +0 -0
  12. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/README.md +0 -0
  13. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/setup.cfg +0 -0
  14. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/__init__.py +0 -0
  15. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/__main__.py +0 -0
  16. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_pypi_marker +0 -0
  17. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_rich_compat.py +0 -0
  18. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/__init__.py +0 -0
  19. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/requirements.txt +0 -0
  20. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/LICENSE +0 -0
  21. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/MANIFEST.sha256 +0 -0
  22. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/VENDORED +0 -0
  23. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/__init__.py +0 -0
  24. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/__main__.py +0 -0
  25. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/asyncio/__init__.py +0 -0
  26. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/asyncio/client.py +0 -0
  27. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/asyncio/connection.py +0 -0
  28. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/asyncio/messages.py +0 -0
  29. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/asyncio/router.py +0 -0
  30. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/asyncio/server.py +0 -0
  31. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/auth.py +0 -0
  32. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/cli.py +0 -0
  33. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/client.py +0 -0
  34. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/connection.py +0 -0
  35. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/datastructures.py +0 -0
  36. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/exceptions.py +0 -0
  37. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/extensions/__init__.py +0 -0
  38. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/extensions/base.py +0 -0
  39. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/extensions/permessage_deflate.py +0 -0
  40. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/frames.py +0 -0
  41. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/headers.py +0 -0
  42. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/http11.py +0 -0
  43. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/imports.py +0 -0
  44. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/legacy/__init__.py +0 -0
  45. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/legacy/auth.py +0 -0
  46. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/legacy/client.py +0 -0
  47. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/legacy/exceptions.py +0 -0
  48. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/legacy/framing.py +0 -0
  49. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/legacy/handshake.py +0 -0
  50. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/legacy/http.py +0 -0
  51. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/legacy/protocol.py +0 -0
  52. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/legacy/server.py +0 -0
  53. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/protocol.py +0 -0
  54. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/proxy.py +0 -0
  55. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/py.typed +0 -0
  56. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/server.py +0 -0
  57. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/streams.py +0 -0
  58. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/sync/__init__.py +0 -0
  59. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/sync/client.py +0 -0
  60. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/sync/connection.py +0 -0
  61. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/sync/messages.py +0 -0
  62. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/sync/router.py +0 -0
  63. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/sync/server.py +0 -0
  64. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/sync/utils.py +0 -0
  65. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/trio/__init__.py +0 -0
  66. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/trio/client.py +0 -0
  67. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/trio/connection.py +0 -0
  68. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/trio/messages.py +0 -0
  69. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/trio/router.py +0 -0
  70. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/trio/server.py +0 -0
  71. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/trio/utils.py +0 -0
  72. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/typing.py +0 -0
  73. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/uri.py +0 -0
  74. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/utils.py +0 -0
  75. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_vendor/websockets/version.py +0 -0
  76. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/_version.py +0 -0
  77. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/auth/__init__.py +0 -0
  78. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/auth/consent_form.py +0 -0
  79. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/auth/oidc_compat.py +0 -0
  80. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/auth/provider.py +0 -0
  81. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/backup_manager.py +0 -0
  82. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/browser_landing.py +0 -0
  83. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/client/__init__.py +0 -0
  84. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/client/rest_client.py +0 -0
  85. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/client/supervisor_client.py +0 -0
  86. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/client/websocket_client.py +0 -0
  87. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/client/websocket_listener.py +0 -0
  88. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/config.py +0 -0
  89. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/dashboard_screenshot/__init__.py +0 -0
  90. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/dashboard_screenshot/capture.py +0 -0
  91. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/dashboard_screenshot/content.py +0 -0
  92. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/dashboard_screenshot/paths.py +0 -0
  93. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/dashboard_screenshot/provision.py +0 -0
  94. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/dashboard_screenshot/theme_guard.py +0 -0
  95. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/errors.py +0 -0
  96. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/hacs_auto_refresh.py +0 -0
  97. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/llm_exposure.py +0 -0
  98. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/policy/__init__.py +0 -0
  99. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/policy/approval_queue.py +0 -0
  100. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/policy/editing.py +0 -0
  101. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/policy/evaluator.py +0 -0
  102. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/policy/handlers.py +0 -0
  103. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/policy/middleware.py +0 -0
  104. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/policy/model.py +0 -0
  105. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/policy/persistence.py +0 -0
  106. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/policy/value_sources.py +0 -0
  107. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/py.typed +0 -0
  108. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/read_only.py +0 -0
  109. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/redaction.py +0 -0
  110. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/renamed_tools.py +0 -0
  111. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
  112. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
  113. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
  114. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
  115. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
  116. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
  117. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
  118. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
  119. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
  120. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
  121. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
  122. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
  123. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/appdaemon.md +0 -0
  124. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
  125. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/blueprint-guide.md +0 -0
  126. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
  127. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
  128. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
  129. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
  130. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
  131. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
  132. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
  133. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/scenes.md +0 -0
  134. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
  135. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/yaml-only-integrations.md +0 -0
  136. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/server.py +0 -0
  137. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/__init__.py +0 -0
  138. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/_handlers_advanced.py +0 -0
  139. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/_handlers_backups.py +0 -0
  140. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/_handlers_fs.py +0 -0
  141. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/_handlers_server.py +0 -0
  142. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/_handlers_theme.py +0 -0
  143. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/_handlers_tools.py +0 -0
  144. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/_i18n.py +0 -0
  145. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/_persistence.py +0 -0
  146. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/_supervisor.py +0 -0
  147. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/_theme.py +0 -0
  148. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/_tools_meta.py +0 -0
  149. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/locales/README.md +0 -0
  150. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/locales/cs.json +0 -0
  151. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/locales/de.json +0 -0
  152. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/locales/en.json +0 -0
  153. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/locales/eo.json +0 -0
  154. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/locales/es.json +0 -0
  155. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/locales/fr.json +0 -0
  156. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/locales/it.json +0 -0
  157. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/locales/nl.json +0 -0
  158. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/locales/pl.json +0 -0
  159. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/locales/ru.json +0 -0
  160. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/locales/sv.json +0 -0
  161. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/locales/zh-Hans.json +0 -0
  162. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/settings.css +0 -0
  163. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/settings.html +0 -0
  164. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/settings_ui/settings.js +0 -0
  165. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/smoke_test.py +0 -0
  166. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
  167. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/strict_bps.py +0 -0
  168. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/__init__.py +0 -0
  169. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/auto_backup.py +0 -0
  170. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/backup.py +0 -0
  171. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/best_practice_checker.py +0 -0
  172. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/best_practice_result.py +0 -0
  173. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/best_practice_variables.py +0 -0
  174. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/component_api.py +0 -0
  175. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/component_config_entries.py +0 -0
  176. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/component_config_reads.py +0 -0
  177. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/component_devices.py +0 -0
  178. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/component_registries.py +0 -0
  179. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/component_registry_lookup.py +0 -0
  180. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/config_entry_flow.py +0 -0
  181. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/config_entry_flow_form.py +0 -0
  182. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/config_entry_flow_menu.py +0 -0
  183. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/config_entry_flow_walker.py +0 -0
  184. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/config_entry_identity.py +0 -0
  185. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/config_entry_reconfigure.py +0 -0
  186. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/config_entry_reload_watch.py +0 -0
  187. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/error_log_parsing.py +0 -0
  188. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/hacs_registration.py +0 -0
  189. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/helpers.py +0 -0
  190. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/integration_reconfigure.py +0 -0
  191. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/radio/__init__.py +0 -0
  192. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/radio/base.py +0 -0
  193. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/radio/matter.py +0 -0
  194. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/radio/thread.py +0 -0
  195. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/radio/zigbee.py +0 -0
  196. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/radio/zwave.py +0 -0
  197. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/reference_validator.py +0 -0
  198. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/registry.py +0 -0
  199. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/renamed_tool_middleware.py +0 -0
  200. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/smart_search/__init__.py +0 -0
  201. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/smart_search/_config.py +0 -0
  202. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/smart_search/_deep.py +0 -0
  203. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/smart_search/_fetch.py +0 -0
  204. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/smart_search/_overview.py +0 -0
  205. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/smart_search/_scenes.py +0 -0
  206. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/smart_search/_scoring.py +0 -0
  207. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tool_search_hint_middleware.py +0 -0
  208. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_addons.py +0 -0
  209. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_areas.py +0 -0
  210. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_assist_pipeline.py +0 -0
  211. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_blueprints.py +0 -0
  212. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_bug_report.py +0 -0
  213. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_calendar.py +0 -0
  214. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_camera.py +0 -0
  215. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_categories.py +0 -0
  216. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_code.py +0 -0
  217. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_config_automations.py +0 -0
  218. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
  219. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
  220. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
  221. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
  222. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_dashboard_screenshot.py +0 -0
  223. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_dev.py +0 -0
  224. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_energy.py +0 -0
  225. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_entities.py +0 -0
  226. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_filesystem.py +0 -0
  227. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_groups.py +0 -0
  228. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_hacs.py +0 -0
  229. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_history.py +0 -0
  230. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_integrations.py +0 -0
  231. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_labels.py +0 -0
  232. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_radio.py +0 -0
  233. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_registry.py +0 -0
  234. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_resources.py +0 -0
  235. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_security_policy.py +0 -0
  236. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_services.py +0 -0
  237. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_system.py +0 -0
  238. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_themes.py +0 -0
  239. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_todo.py +0 -0
  240. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_traces.py +0 -0
  241. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_updates.py +0 -0
  242. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_utility.py +0 -0
  243. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
  244. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
  245. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_yaml_read.py +0 -0
  246. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/tools_zones.py +0 -0
  247. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/util_helpers.py +0 -0
  248. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/tools/validation_middleware.py +0 -0
  249. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/transforms/__init__.py +0 -0
  250. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/transforms/categorized_search.py +0 -0
  251. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
  252. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/transport_security.py +0 -0
  253. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/update_check.py +0 -0
  254. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/utils/__init__.py +0 -0
  255. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/utils/config_hash.py +0 -0
  256. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/utils/config_write_lock.py +0 -0
  257. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/utils/data_paths.py +0 -0
  258. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/utils/domain_handlers.py +0 -0
  259. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/utils/fuzzy_search.py +0 -0
  260. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
  261. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/utils/python_sandbox.py +0 -0
  262. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/utils/skill_loader.py +0 -0
  263. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/utils/usage_logger.py +0 -0
  264. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/visibility/__init__.py +0 -0
  265. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/visibility/enforcement.py +0 -0
  266. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/visibility/model.py +0 -0
  267. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/visibility/persistence.py +0 -0
  268. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp/visibility/resolver.py +0 -0
  269. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
  270. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
  271. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
  272. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
  273. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
  274. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/tests/__init__.py +0 -0
  275. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/tests/test_constants.py +0 -0
  276. {ha_mcp_dev-8.3.0.dev2377 → ha_mcp_dev-8.3.0.dev2382}/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.3.0.dev2377
3
+ Version: 8.3.0.dev2382
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.3.0.dev2377"
7
+ version = "8.3.0.dev2382"
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
@@ -8,6 +8,7 @@ and async operation verification through WebSocket monitoring.
8
8
  import asyncio
9
9
  import json
10
10
  import logging
11
+ import math
11
12
  import time
12
13
  from typing import Any, ClassVar
13
14
 
@@ -41,10 +42,23 @@ from .helpers import (
41
42
  safe_info,
42
43
  safe_progress,
43
44
  )
45
+ from .tools_service import BulkControlOperation
44
46
  from .util_helpers import _SERVICE_TO_STATE
45
47
 
46
48
  logger = logging.getLogger(__name__)
47
49
 
50
+ # The bulk row contract is declared once, on the advertised transport schema.
51
+ # Reading its keys back here keeps the runtime batch validator from drifting
52
+ # out of sync with what models are told they may send.
53
+ _BULK_OPERATION_KEYS: frozenset[str] = frozenset(BulkControlOperation.__annotations__)
54
+
55
+ # The row shape models get wrong most often is a service-style row, so this
56
+ # leads every suggestion list a rejected bulk batch produces.
57
+ _BULK_ROW_SHAPE_SUGGESTION = (
58
+ "Use entity_id and action (e.g. action='off', not service='turn_off'); "
59
+ "send no domain or service keys and put action data in parameters"
60
+ )
61
+
48
62
  # The ha_mcp_tools/bulk_call_service WS command (Phase 3, D5a): the BATCH write
49
63
  # capability. When the component advertises ``bulk_call_service`` the consumer
50
64
  # resolves every op's domain/service server-side (D6), sends one register-before-fire
@@ -88,7 +102,7 @@ class DeviceControlTools:
88
102
  entity_id: str,
89
103
  action: str,
90
104
  parameters: dict[str, Any] | None = None,
91
- timeout_seconds: int = 10,
105
+ timeout_seconds: float = 10,
92
106
  validate_first: bool = True,
93
107
  ) -> dict[str, Any]:
94
108
  """
@@ -346,8 +360,18 @@ class DeviceControlTools:
346
360
  "set": "set_temperature",
347
361
  }
348
362
 
363
+ # Silent allowlist: _add_domain_params copies only these keys into the
364
+ # service call and drops everything else without an error, and a domain
365
+ # absent from this table loses every parameter. Extend it when adding
366
+ # support for a new parameter, or the parameter vanishes at dispatch.
349
367
  _DOMAIN_PARAMS: ClassVar[dict[str, list[str]]] = {
350
- "light": ["brightness", "color_temp_kelvin", "rgb_color", "effect"],
368
+ "light": [
369
+ "brightness",
370
+ "brightness_pct",
371
+ "color_temp_kelvin",
372
+ "rgb_color",
373
+ "effect",
374
+ ],
351
375
  "climate": ["temperature", "target_temp_high", "target_temp_low", "hvac_mode"],
352
376
  "cover": ["position", "tilt_position"],
353
377
  "media_player": ["volume_level", "media_content_id", "media_content_type"],
@@ -462,15 +486,15 @@ class DeviceControlTools:
462
486
  return expected if expected else None
463
487
 
464
488
  async def get_device_operation_status(
465
- self, operation_id: str, timeout_seconds: int = 10
489
+ self, operation_id: str, timeout_seconds: float = 10
466
490
  ) -> dict[str, Any]:
467
491
  """Check status of a device operation, waiting up to ``timeout_seconds`` for completion.
468
492
 
469
493
  Polls the in-memory operation registry (mutated by the WebSocket
470
- listener as state changes arrive) every 0.2s while the operation is
471
- pending, up to ``timeout_seconds``. Returns the final structured status
472
- — completed/failed/timeout/pending — produced by
473
- ``control_device_smart``.
494
+ listener as state changes arrive) while the operation is pending, at
495
+ 0.2s intervals except for a final shorter poll that lands exactly on
496
+ the deadline. Returns the final structured status —
497
+ completed/failed/timeout/pending — produced by ``control_device_smart``.
474
498
  """
475
499
  operation = get_operation_from_memory(operation_id)
476
500
 
@@ -495,9 +519,10 @@ class DeviceControlTools:
495
519
  if operation.status.value == "pending" and timeout_seconds > 0:
496
520
  deadline = time.monotonic() + timeout_seconds
497
521
  while operation.status.value == "pending":
498
- if time.monotonic() >= deadline:
522
+ remaining = deadline - time.monotonic()
523
+ if remaining <= 0:
499
524
  break
500
- await asyncio.sleep(0.2)
525
+ await asyncio.sleep(min(0.2, remaining))
501
526
  refreshed = get_operation_from_memory(operation_id)
502
527
  if refreshed is None:
503
528
  raise_tool_error(
@@ -597,21 +622,128 @@ class DeviceControlTools:
597
622
  }
598
623
 
599
624
  @staticmethod
625
+ def _normalize_bulk_parameters(
626
+ value: Any,
627
+ ) -> tuple[dict[str, Any] | None, ErrorCode | None, str]:
628
+ """Normalize a present ``parameters`` value and identify invalid ones.
629
+
630
+ Returns ``(parameters, error_code, detail)``. ``detail`` carries the
631
+ decoder's own reason and offset for a JSON failure — without it the
632
+ caller can only say "invalid JSON", which does not tell the sender
633
+ where its string went wrong. It is empty for every other outcome.
634
+ """
635
+ if isinstance(value, str):
636
+ try:
637
+ value = json.loads(value)
638
+ except json.JSONDecodeError as exc:
639
+ return (
640
+ None,
641
+ ErrorCode.VALIDATION_INVALID_JSON,
642
+ f"{exc.msg} at position {exc.pos}",
643
+ )
644
+ except RecursionError:
645
+ # json.loads recurses per nesting level; a pathologically
646
+ # nested string must skip the row, not abort the batch.
647
+ return (
648
+ None,
649
+ ErrorCode.VALIDATION_INVALID_JSON,
650
+ "JSON nested too deeply",
651
+ )
652
+ if not isinstance(value, dict):
653
+ # A present-but-null value is malformed, same as timeout_seconds
654
+ # and validate_first; an absent key never reaches this helper.
655
+ return None, ErrorCode.VALIDATION_INVALID_PARAMETER, ""
656
+ return value, None, ""
657
+
658
+ @staticmethod
659
+ def _normalize_bulk_timeout(value: Any) -> float | None:
660
+ """Normalize a non-negative finite timeout, or ``None`` when invalid.
661
+
662
+ Bools are rejected before ``float()`` sees them: ``float(True)`` is 1.0,
663
+ which would silently accept ``timeout_seconds: true`` as a one-second
664
+ wait. Strings are rejected for the same reason — the advertised schema
665
+ is ``strict``, so coercing ``"10"`` here would silently accept what the
666
+ schema tells the model is invalid. ``None`` is invalid here too — the
667
+ caller only calls this when the key is present, and a present-but-null
668
+ timeout is a malformed row.
669
+ """
670
+ if isinstance(value, (bool, str)) or value is None:
671
+ return None
672
+ try:
673
+ timeout = float(value)
674
+ except (TypeError, ValueError, OverflowError):
675
+ # OverflowError: an int too large for float must skip the row,
676
+ # not abort the batch through the generic exception handler.
677
+ return None
678
+ return timeout if math.isfinite(timeout) and timeout >= 0 else None
679
+
680
+ @staticmethod
681
+ def _normalized_bulk_operation(
682
+ operation: dict[str, Any],
683
+ parameters: dict[str, Any] | None,
684
+ timeout: float | None,
685
+ ) -> dict[str, Any]:
686
+ """Return one normalized valid bulk operation."""
687
+ normalized = dict(operation)
688
+ if parameters is not None:
689
+ normalized["parameters"] = parameters
690
+ if timeout is not None:
691
+ normalized["timeout_seconds"] = timeout
692
+ return normalized
693
+
694
+ @staticmethod
695
+ def _skip_bulk_operation(
696
+ skipped_operations: list[dict[str, Any]],
697
+ op: Any,
698
+ code: ErrorCode,
699
+ error: str,
700
+ context: dict[str, Any],
701
+ suggestions: list[str] | None = None,
702
+ ) -> None:
703
+ """Log one rejected bulk row and record its structured skip entry.
704
+
705
+ ``create_error_response`` lifts every ``context`` key to the top level,
706
+ so ``index`` needs no separate write; ``operation`` is not a context
707
+ key and is attached here so callers can see the row they sent.
708
+ """
709
+ logger.warning(f"Bulk control: {error}")
710
+ err_response = create_error_response(
711
+ code, error, suggestions=suggestions, context=context
712
+ )
713
+ err_response["operation"] = op
714
+ skipped_operations.append(err_response)
715
+
716
+ @classmethod
600
717
  def _validate_bulk_operations(
601
- operations: list[dict[str, Any]],
718
+ cls,
719
+ operations: list[Any],
602
720
  skipped_operations: list[dict[str, Any]],
603
721
  ) -> list[tuple[int, dict[str, Any], str, str]]:
604
722
  valid: list[tuple[int, dict[str, Any], str, str]] = []
605
723
  for i, op in enumerate(operations):
606
724
  if not isinstance(op, dict):
607
- error = f"Operation at index {i} is not a dict: {type(op).__name__}"
608
- logger.warning(f"Bulk control: {error}")
609
- err_response = create_error_response(
610
- ErrorCode.VALIDATION_MISSING_PARAMETER, error, context={"index": i}
725
+ cls._skip_bulk_operation(
726
+ skipped_operations,
727
+ op,
728
+ ErrorCode.VALIDATION_INVALID_PARAMETER,
729
+ f"Operation at index {i} is not a dict: {type(op).__name__}",
730
+ {"index": i},
731
+ )
732
+ continue
733
+
734
+ # str() before sorting: a direct Python caller can pass non-string
735
+ # keys, and mixed key types make sorted()/join() raise mid-batch.
736
+ obsolete_keys = sorted(str(key) for key in set(op) - _BULK_OPERATION_KEYS)
737
+ if obsolete_keys:
738
+ key_list = ", ".join(obsolete_keys)
739
+ cls._skip_bulk_operation(
740
+ skipped_operations,
741
+ op,
742
+ ErrorCode.VALIDATION_INVALID_PARAMETER,
743
+ f"Operation at index {i} contains unsupported key(s): {key_list}",
744
+ {"index": i, "unsupported_keys": obsolete_keys},
745
+ suggestions=[_BULK_ROW_SHAPE_SUGGESTION],
611
746
  )
612
- err_response["index"] = i
613
- err_response["operation"] = op
614
- skipped_operations.append(err_response)
615
747
  continue
616
748
 
617
749
  entity_id = op.get("entity_id")
@@ -619,21 +751,80 @@ class DeviceControlTools:
619
751
  missing = [f for f in ("entity_id", "action") if not op.get(f)]
620
752
 
621
753
  if missing:
622
- error = f"Operation at index {i} missing required fields: {', '.join(missing)}"
623
- logger.warning(f"Bulk control: {error}")
624
- err_response = create_error_response(
625
- ErrorCode.VALIDATION_MISSING_PARAMETER, error, context={"index": i}
754
+ cls._skip_bulk_operation(
755
+ skipped_operations,
756
+ op,
757
+ ErrorCode.VALIDATION_MISSING_PARAMETER,
758
+ f"Operation at index {i} missing required fields: "
759
+ f"{', '.join(missing)}",
760
+ {"index": i},
626
761
  )
627
- err_response["index"] = i
628
- err_response["operation"] = op
629
- skipped_operations.append(err_response)
630
- else:
631
- valid.append((i, op, str(entity_id), str(action)))
762
+ continue
763
+
764
+ if not isinstance(entity_id, str) or not isinstance(action, str):
765
+ cls._skip_bulk_operation(
766
+ skipped_operations,
767
+ op,
768
+ ErrorCode.VALIDATION_INVALID_PARAMETER,
769
+ f"Operation at index {i} requires string entity_id and "
770
+ "action fields",
771
+ {"index": i},
772
+ )
773
+ continue
774
+
775
+ parameters, parameter_error, parameter_detail = (
776
+ cls._normalize_bulk_parameters(op["parameters"])
777
+ if "parameters" in op
778
+ else (None, None, "")
779
+ )
780
+ if parameter_error is not None:
781
+ parameter_message = (
782
+ f"Operation at index {i} has invalid JSON parameters: "
783
+ f"{parameter_detail}"
784
+ if parameter_error == ErrorCode.VALIDATION_INVALID_JSON
785
+ else f"Operation at index {i} parameters must be a JSON object"
786
+ )
787
+ cls._skip_bulk_operation(
788
+ skipped_operations,
789
+ op,
790
+ parameter_error,
791
+ parameter_message,
792
+ {"index": i},
793
+ )
794
+ continue
795
+
796
+ timeout: float | None = None
797
+ if "timeout_seconds" in op:
798
+ timeout = cls._normalize_bulk_timeout(op["timeout_seconds"])
799
+ if timeout is None:
800
+ cls._skip_bulk_operation(
801
+ skipped_operations,
802
+ op,
803
+ ErrorCode.VALIDATION_INVALID_PARAMETER,
804
+ f"Operation at index {i} timeout_seconds must be a "
805
+ "non-negative number",
806
+ {"index": i},
807
+ )
808
+ continue
809
+
810
+ if "validate_first" in op and not isinstance(op["validate_first"], bool):
811
+ cls._skip_bulk_operation(
812
+ skipped_operations,
813
+ op,
814
+ ErrorCode.VALIDATION_INVALID_PARAMETER,
815
+ f"Operation at index {i} validate_first must be a boolean",
816
+ {"index": i},
817
+ )
818
+ continue
819
+
820
+ normalized_op = cls._normalized_bulk_operation(op, parameters, timeout)
821
+ valid.append((i, normalized_op, entity_id, action))
822
+
632
823
  return valid
633
824
 
634
825
  async def bulk_device_control(
635
826
  self,
636
- operations: list[dict[str, Any]],
827
+ operations: list[Any],
637
828
  parallel: bool = True,
638
829
  ctx: Context | None = None,
639
830
  ) -> dict[str, Any]:
@@ -672,6 +863,26 @@ class DeviceControlTools:
672
863
  f"{len(skipped_operations)} skipped, "
673
864
  f"mode={'parallel' if parallel else 'sequential'}",
674
865
  )
866
+ if not valid_operations:
867
+ # Nothing dispatched, so nothing to fail soft for: the batch
868
+ # carve-out from "all tool-level failures raise" exists to
869
+ # protect successful siblings, and there are none. Raising
870
+ # before the component probe keeps the no-dispatch guarantee.
871
+ raise_tool_error(
872
+ create_error_response(
873
+ ErrorCode.VALIDATION_INVALID_PARAMETER,
874
+ f"All {len(operations)} operation(s) failed validation; "
875
+ "nothing was dispatched",
876
+ suggestions=[
877
+ _BULK_ROW_SHAPE_SUGGESTION,
878
+ "Each operation requires 'entity_id' and 'action'",
879
+ "Check skipped_details for the per-operation reason",
880
+ "Example: {'entity_id': 'light.living_room', "
881
+ + "'action': 'on'}",
882
+ ],
883
+ context={"skipped_details": skipped_operations},
884
+ )
885
+ )
675
886
 
676
887
  # Route through the component's bulk_call_service capability when
677
888
  # advertised (D5a): one register-before-fire batch frame confirms every
@@ -904,7 +1115,7 @@ class DeviceControlTools:
904
1115
 
905
1116
  async def _bulk_via_component(
906
1117
  self,
907
- operations: list[dict[str, Any]],
1118
+ operations: list[Any],
908
1119
  valid_operations: list[tuple[int, dict[str, Any], str, str]],
909
1120
  skipped_operations: list[dict[str, Any]],
910
1121
  parallel: bool,
@@ -1111,7 +1322,7 @@ class DeviceControlTools:
1111
1322
 
1112
1323
  def _build_ambiguous_bulk_response(
1113
1324
  self,
1114
- operations: list[dict[str, Any]],
1325
+ operations: list[Any],
1115
1326
  valid_operations: list[tuple[int, dict[str, Any], str, str]],
1116
1327
  rows: list[dict[str, Any]],
1117
1328
  skipped_operations: list[dict[str, Any]],
@@ -1246,7 +1457,7 @@ class DeviceControlTools:
1246
1457
 
1247
1458
  def _build_bulk_response(
1248
1459
  self,
1249
- operations: list[dict[str, Any]],
1460
+ operations: list[Any],
1250
1461
  results: list[dict[str, Any]],
1251
1462
  operation_ids: list[str],
1252
1463
  skipped_operations: list[dict[str, Any]],
@@ -1284,6 +1495,7 @@ class DeviceControlTools:
1284
1495
  if skipped_operations:
1285
1496
  response["skipped_details"] = skipped_operations
1286
1497
  response["suggestions"] = [
1498
+ _BULK_ROW_SHAPE_SUGGESTION,
1287
1499
  "Some operations were skipped due to validation errors",
1288
1500
  "Each operation requires 'entity_id' and 'action' fields",
1289
1501
  "Check skipped_details for specific errors",
@@ -1293,7 +1505,7 @@ class DeviceControlTools:
1293
1505
  return response
1294
1506
 
1295
1507
  async def get_bulk_operation_status(
1296
- self, operation_ids: list[str], timeout_seconds: int = 10
1508
+ self, operation_ids: list[str], timeout_seconds: float = 10
1297
1509
  ) -> dict[str, Any]:
1298
1510
  """
1299
1511
  Check status of multiple operations.
@@ -40,7 +40,19 @@ class _SearchBase:
40
40
  logger.debug(f"Could not fetch {label}: {result}")
41
41
  cause = str(result) or type(result).__name__
42
42
  elif isinstance(result, dict) and result.get("success"):
43
- registry = result.get("result", [])
43
+ # No default for a missing key: a legitimate empty registry is
44
+ # "result": [], so an absent result is a malformed envelope and
45
+ # must degrade loudly, not read as available-and-empty.
46
+ payload = result.get("result")
47
+ if isinstance(payload, list):
48
+ registry = payload
49
+ elif payload is None and "result" not in result:
50
+ cause = "malformed result payload: result key missing"
51
+ else:
52
+ cause = (
53
+ "malformed result payload: expected list, "
54
+ f"got {type(payload).__name__}"
55
+ )
44
56
  elif isinstance(result, dict):
45
57
  cause = str(result.get("error") or "request failed")
46
58
  else: