ha-mcp-dev 8.3.0.dev2395__tar.gz → 8.3.0.dev2397__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 (281) hide show
  1. {ha_mcp_dev-8.3.0.dev2395/src/ha_mcp_dev.egg-info → ha_mcp_dev-8.3.0.dev2397}/PKG-INFO +1 -1
  2. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/pyproject.toml +1 -1
  3. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/client/rest_client.py +16 -3
  4. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/errors.py +27 -2
  5. ha_mcp_dev-8.3.0.dev2397/src/ha_mcp/policy/evaluator.py +314 -0
  6. ha_mcp_dev-8.3.0.dev2397/src/ha_mcp/policy/middleware.py +442 -0
  7. ha_mcp_dev-8.3.0.dev2397/src/ha_mcp/tools/bulk_selector.py +858 -0
  8. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/registry.py +49 -3
  9. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/tools_service.py +676 -58
  10. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/util_helpers.py +6 -2
  11. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
  12. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp_dev.egg-info/SOURCES.txt +1 -0
  13. ha_mcp_dev-8.3.0.dev2395/src/ha_mcp/policy/evaluator.py +0 -166
  14. ha_mcp_dev-8.3.0.dev2395/src/ha_mcp/policy/middleware.py +0 -260
  15. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/LICENSE +0 -0
  16. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/MANIFEST.in +0 -0
  17. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/README.md +0 -0
  18. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/setup.cfg +0 -0
  19. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/__init__.py +0 -0
  20. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/__main__.py +0 -0
  21. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/_pypi_marker +0 -0
  22. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/_rich_compat.py +0 -0
  23. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/_vendor/__init__.py +0 -0
  24. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/_vendor/requirements.txt +0 -0
  25. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/_vendor/websockets/LICENSE +0 -0
  26. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/_vendor/websockets/MANIFEST.sha256 +0 -0
  27. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/_vendor/websockets/VENDORED +0 -0
  28. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/_vendor/websockets/__init__.py +0 -0
  29. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/_vendor/websockets/__main__.py +0 -0
  30. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/_vendor/websockets/asyncio/__init__.py +0 -0
  31. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/_vendor/websockets/asyncio/client.py +0 -0
  32. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/_vendor/websockets/asyncio/connection.py +0 -0
  33. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/_vendor/websockets/asyncio/messages.py +0 -0
  34. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/_vendor/websockets/asyncio/router.py +0 -0
  35. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/_vendor/websockets/asyncio/server.py +0 -0
  36. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/_vendor/websockets/auth.py +0 -0
  37. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/_vendor/websockets/cli.py +0 -0
  38. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/_vendor/websockets/client.py +0 -0
  39. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/_vendor/websockets/connection.py +0 -0
  40. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/_vendor/websockets/datastructures.py +0 -0
  41. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/_vendor/websockets/exceptions.py +0 -0
  42. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/_vendor/websockets/extensions/__init__.py +0 -0
  43. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/_vendor/websockets/extensions/base.py +0 -0
  44. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/_vendor/websockets/extensions/permessage_deflate.py +0 -0
  45. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/_vendor/websockets/frames.py +0 -0
  46. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/_vendor/websockets/headers.py +0 -0
  47. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/_vendor/websockets/http11.py +0 -0
  48. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/_vendor/websockets/imports.py +0 -0
  49. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/_vendor/websockets/legacy/__init__.py +0 -0
  50. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/_vendor/websockets/legacy/auth.py +0 -0
  51. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/_vendor/websockets/legacy/client.py +0 -0
  52. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/_vendor/websockets/legacy/exceptions.py +0 -0
  53. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/_vendor/websockets/legacy/framing.py +0 -0
  54. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/_vendor/websockets/legacy/handshake.py +0 -0
  55. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/_vendor/websockets/legacy/http.py +0 -0
  56. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/_vendor/websockets/legacy/protocol.py +0 -0
  57. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/_vendor/websockets/legacy/server.py +0 -0
  58. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/_vendor/websockets/protocol.py +0 -0
  59. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/_vendor/websockets/proxy.py +0 -0
  60. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/_vendor/websockets/py.typed +0 -0
  61. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/_vendor/websockets/server.py +0 -0
  62. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/_vendor/websockets/streams.py +0 -0
  63. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/_vendor/websockets/sync/__init__.py +0 -0
  64. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/_vendor/websockets/sync/client.py +0 -0
  65. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/_vendor/websockets/sync/connection.py +0 -0
  66. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/_vendor/websockets/sync/messages.py +0 -0
  67. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/_vendor/websockets/sync/router.py +0 -0
  68. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/_vendor/websockets/sync/server.py +0 -0
  69. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/_vendor/websockets/sync/utils.py +0 -0
  70. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/_vendor/websockets/trio/__init__.py +0 -0
  71. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/_vendor/websockets/trio/client.py +0 -0
  72. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/_vendor/websockets/trio/connection.py +0 -0
  73. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/_vendor/websockets/trio/messages.py +0 -0
  74. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/_vendor/websockets/trio/router.py +0 -0
  75. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/_vendor/websockets/trio/server.py +0 -0
  76. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/_vendor/websockets/trio/utils.py +0 -0
  77. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/_vendor/websockets/typing.py +0 -0
  78. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/_vendor/websockets/uri.py +0 -0
  79. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/_vendor/websockets/utils.py +0 -0
  80. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/_vendor/websockets/version.py +0 -0
  81. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/_version.py +0 -0
  82. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/auth/__init__.py +0 -0
  83. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/auth/consent_form.py +0 -0
  84. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/auth/oidc_compat.py +0 -0
  85. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/auth/provider.py +0 -0
  86. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/backup_manager.py +0 -0
  87. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/browser_landing.py +0 -0
  88. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/client/__init__.py +0 -0
  89. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/client/supervisor_client.py +0 -0
  90. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/client/websocket_client.py +0 -0
  91. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/client/websocket_listener.py +0 -0
  92. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/config.py +0 -0
  93. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/dashboard_screenshot/__init__.py +0 -0
  94. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/dashboard_screenshot/capture.py +0 -0
  95. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/dashboard_screenshot/content.py +0 -0
  96. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/dashboard_screenshot/paths.py +0 -0
  97. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/dashboard_screenshot/provision.py +0 -0
  98. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/dashboard_screenshot/theme_guard.py +0 -0
  99. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/hacs_auto_refresh.py +0 -0
  100. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/llm_exposure.py +0 -0
  101. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/policy/__init__.py +0 -0
  102. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/policy/approval_queue.py +0 -0
  103. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/policy/editing.py +0 -0
  104. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/policy/handlers.py +0 -0
  105. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/policy/model.py +0 -0
  106. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/policy/persistence.py +0 -0
  107. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/policy/value_sources.py +0 -0
  108. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/py.typed +0 -0
  109. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/read_only.py +0 -0
  110. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/redaction.py +0 -0
  111. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/renamed_tools.py +0 -0
  112. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
  113. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
  114. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
  115. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
  116. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
  117. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
  118. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
  119. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
  120. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
  121. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
  122. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
  123. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
  124. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/appdaemon.md +0 -0
  125. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
  126. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/blueprint-guide.md +0 -0
  127. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
  128. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
  129. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
  130. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
  131. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
  132. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
  133. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
  134. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/scenes.md +0 -0
  135. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
  136. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/yaml-only-integrations.md +0 -0
  137. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/server.py +0 -0
  138. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/settings_ui/__init__.py +0 -0
  139. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/settings_ui/_handlers_advanced.py +0 -0
  140. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/settings_ui/_handlers_backups.py +0 -0
  141. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/settings_ui/_handlers_fs.py +0 -0
  142. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/settings_ui/_handlers_server.py +0 -0
  143. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/settings_ui/_handlers_theme.py +0 -0
  144. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/settings_ui/_handlers_tools.py +0 -0
  145. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/settings_ui/_i18n.py +0 -0
  146. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/settings_ui/_persistence.py +0 -0
  147. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/settings_ui/_supervisor.py +0 -0
  148. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/settings_ui/_theme.py +0 -0
  149. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/settings_ui/_tools_meta.py +0 -0
  150. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/settings_ui/locales/README.md +0 -0
  151. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/settings_ui/locales/cs.json +0 -0
  152. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/settings_ui/locales/de.json +0 -0
  153. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/settings_ui/locales/en.json +0 -0
  154. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/settings_ui/locales/eo.json +0 -0
  155. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/settings_ui/locales/es.json +0 -0
  156. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/settings_ui/locales/fr.json +0 -0
  157. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/settings_ui/locales/it.json +0 -0
  158. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/settings_ui/locales/ko.json +0 -0
  159. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/settings_ui/locales/nl.json +0 -0
  160. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/settings_ui/locales/pl.json +0 -0
  161. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/settings_ui/locales/ru.json +0 -0
  162. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/settings_ui/locales/sv.json +0 -0
  163. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/settings_ui/locales/zh-Hans.json +0 -0
  164. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/settings_ui/settings.css +0 -0
  165. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/settings_ui/settings.html +0 -0
  166. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/settings_ui/settings.js +0 -0
  167. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/smoke_test.py +0 -0
  168. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
  169. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/strict_bps.py +0 -0
  170. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/__init__.py +0 -0
  171. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/auto_backup.py +0 -0
  172. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/backup.py +0 -0
  173. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/best_practice_checker.py +0 -0
  174. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/best_practice_result.py +0 -0
  175. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/best_practice_variables.py +0 -0
  176. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/component_api.py +0 -0
  177. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/component_config_entries.py +0 -0
  178. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/component_config_reads.py +0 -0
  179. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/component_devices.py +0 -0
  180. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/component_registries.py +0 -0
  181. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/component_registry_lookup.py +0 -0
  182. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/config_entry_flow.py +0 -0
  183. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/config_entry_flow_form.py +0 -0
  184. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/config_entry_flow_menu.py +0 -0
  185. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/config_entry_flow_walker.py +0 -0
  186. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/config_entry_identity.py +0 -0
  187. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/config_entry_reconfigure.py +0 -0
  188. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/config_entry_reload_watch.py +0 -0
  189. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/device_control.py +0 -0
  190. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/error_log_parsing.py +0 -0
  191. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/hacs_registration.py +0 -0
  192. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/helpers.py +0 -0
  193. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/integration_reconfigure.py +0 -0
  194. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/radio/__init__.py +0 -0
  195. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/radio/base.py +0 -0
  196. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/radio/matter.py +0 -0
  197. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/radio/thread.py +0 -0
  198. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/radio/zigbee.py +0 -0
  199. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/radio/zwave.py +0 -0
  200. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/reference_validator.py +0 -0
  201. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/renamed_tool_middleware.py +0 -0
  202. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/smart_search/__init__.py +0 -0
  203. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/smart_search/_base.py +0 -0
  204. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/smart_search/_config.py +0 -0
  205. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/smart_search/_deep.py +0 -0
  206. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/smart_search/_entities.py +0 -0
  207. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/smart_search/_fetch.py +0 -0
  208. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/smart_search/_overview.py +0 -0
  209. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/smart_search/_scenes.py +0 -0
  210. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/smart_search/_scoring.py +0 -0
  211. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/tool_search_hint_middleware.py +0 -0
  212. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/tools_addons.py +0 -0
  213. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/tools_areas.py +0 -0
  214. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/tools_assist_pipeline.py +0 -0
  215. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/tools_blueprints.py +0 -0
  216. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/tools_bug_report.py +0 -0
  217. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/tools_calendar.py +0 -0
  218. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/tools_camera.py +0 -0
  219. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/tools_categories.py +0 -0
  220. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/tools_code.py +0 -0
  221. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/tools_config_automations.py +0 -0
  222. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
  223. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
  224. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
  225. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
  226. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/tools_dashboard_screenshot.py +0 -0
  227. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/tools_dev.py +0 -0
  228. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/tools_energy.py +0 -0
  229. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/tools_entities.py +0 -0
  230. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/tools_filesystem.py +0 -0
  231. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/tools_groups.py +0 -0
  232. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/tools_hacs.py +0 -0
  233. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/tools_history.py +0 -0
  234. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/tools_integrations.py +0 -0
  235. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/tools_labels.py +0 -0
  236. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/tools_radio.py +0 -0
  237. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/tools_registry.py +0 -0
  238. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/tools_resources.py +0 -0
  239. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/tools_search.py +0 -0
  240. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/tools_security_policy.py +0 -0
  241. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/tools_services.py +0 -0
  242. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/tools_system.py +0 -0
  243. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/tools_themes.py +0 -0
  244. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/tools_todo.py +0 -0
  245. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/tools_traces.py +0 -0
  246. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/tools_updates.py +0 -0
  247. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/tools_utility.py +0 -0
  248. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
  249. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
  250. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/tools_yaml_read.py +0 -0
  251. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/tools_zones.py +0 -0
  252. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/tools/validation_middleware.py +0 -0
  253. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/transforms/__init__.py +0 -0
  254. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/transforms/categorized_search.py +0 -0
  255. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
  256. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/transport_security.py +0 -0
  257. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/update_check.py +0 -0
  258. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/utils/__init__.py +0 -0
  259. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/utils/config_hash.py +0 -0
  260. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/utils/config_write_lock.py +0 -0
  261. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/utils/data_paths.py +0 -0
  262. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/utils/domain_handlers.py +0 -0
  263. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/utils/entity_membership.py +0 -0
  264. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/utils/fuzzy_search.py +0 -0
  265. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
  266. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/utils/operation_manager.py +0 -0
  267. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/utils/python_sandbox.py +0 -0
  268. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/utils/skill_loader.py +0 -0
  269. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/utils/usage_logger.py +0 -0
  270. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/visibility/__init__.py +0 -0
  271. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/visibility/enforcement.py +0 -0
  272. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/visibility/model.py +0 -0
  273. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/visibility/persistence.py +0 -0
  274. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp/visibility/resolver.py +0 -0
  275. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
  276. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
  277. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
  278. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
  279. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/tests/__init__.py +0 -0
  280. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/tests/test_constants.py +0 -0
  281. {ha_mcp_dev-8.3.0.dev2395 → ha_mcp_dev-8.3.0.dev2397}/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.dev2395
3
+ Version: 8.3.0.dev2397
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.dev2395"
7
+ version = "8.3.0.dev2397"
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
@@ -409,13 +409,26 @@ class HomeAssistantClient:
409
409
  return await self._request("GET", "/config")
410
410
 
411
411
  async def get_states(self) -> list[dict[str, Any]]:
412
- """Get all entity states."""
412
+ """Get all entity states.
413
+
414
+ Raises ``HomeAssistantConnectionError`` when the response isn't the
415
+ JSON array ``/states`` always returns on success -- including
416
+ ``_request``'s own empty-dict fallback for an unparseable body.
417
+ Silently returning ``[]`` here would let a genuine fetch failure
418
+ masquerade as "this instance has zero entities", which no real,
419
+ running Home Assistant instance produces; callers that need to
420
+ verify something against the live entity set (e.g. a bulk-control
421
+ safety check) would otherwise treat that failure as "verified,
422
+ nothing to worry about" instead of "could not verify".
423
+ """
413
424
  logger.debug("Fetching all entity states")
414
425
  result = await self._request("GET", "/states")
415
426
  if isinstance(result, list):
416
427
  return result
417
- else:
418
- return []
428
+ raise HomeAssistantConnectionError(
429
+ f"Home Assistant /states returned an unexpected response shape: "
430
+ f"{type(result).__name__}"
431
+ )
419
432
 
420
433
  async def get_entity_state(self, entity_id: str) -> dict[str, Any]:
421
434
  """
@@ -100,6 +100,16 @@ class ErrorCode(StrEnum):
100
100
  USER_DENIED = "USER_DENIED"
101
101
  POLICY_LOAD_FAILED = "POLICY_LOAD_FAILED"
102
102
 
103
+ # Distinct from POLICY_LOAD_FAILED above: the policy file itself loaded
104
+ # fine, but this call's own arguments were too deeply nested to
105
+ # evaluate against it safely (see normalize_stringified_containers'
106
+ # RecursionError handling in middleware.py). A different failure with a
107
+ # different, caller-shaped remedy ("reduce the nesting depth and
108
+ # retry") -- sharing POLICY_LOAD_FAILED would make the two
109
+ # indistinguishable to anything grouping on the code (a dashboard, an
110
+ # operator grepping logs after "my policy broke").
111
+ POLICY_ARGS_TOO_DEEPLY_NESTED = "POLICY_ARGS_TOO_DEEPLY_NESTED"
112
+
103
113
  # Read Only Mode (discussion #1569). A write operation was blocked
104
114
  # because the server-wide Read Only Mode toggle is on.
105
115
  READ_ONLY_MODE = "READ_ONLY_MODE"
@@ -210,6 +220,10 @@ DEFAULT_SUGGESTIONS: dict[ErrorCode, list[str]] = {
210
220
  "Check documentation for required fields",
211
221
  "Ensure all required parameters are provided",
212
222
  ],
223
+ ErrorCode.VALIDATION_FAILED: [
224
+ "Check the parameter values and format against the tool documentation",
225
+ "Review the message and details fields for the specific constraint that failed",
226
+ ],
213
227
  ErrorCode.VALIDATION_INVALID_JSON: [
214
228
  "Ensure the parameter is valid JSON",
215
229
  "Check for syntax errors in JSON",
@@ -310,10 +324,21 @@ def create_connection_error(
310
324
  details: str | None = None,
311
325
  timeout: bool = False,
312
326
  context: dict[str, Any] | None = None,
327
+ suggestions: list[str] | None = None,
313
328
  ) -> dict[str, Any]:
314
- """Create a connection error response."""
329
+ """Create a connection error response.
330
+
331
+ ``suggestions`` overrides ``DEFAULT_SUGGESTIONS[CONNECTION_FAILED/TIMEOUT]``
332
+ (network/URL/connectivity checks) for a caller whose failure is
333
+ CONNECTION_FAILED-shaped (unavailable, not the caller's fault to fix by
334
+ editing input) but not actually a network problem — e.g. malformed local
335
+ registry data or an unloadable local config file, where "check your
336
+ HOMEASSISTANT_URL" is not an actionable next step.
337
+ """
315
338
  code = ErrorCode.CONNECTION_TIMEOUT if timeout else ErrorCode.CONNECTION_FAILED
316
- return create_error_response(code, message, details, context=context)
339
+ return create_error_response(
340
+ code, message, details, suggestions=suggestions, context=context
341
+ )
317
342
 
318
343
 
319
344
  # Authentication-error suggestions for Home Assistant add-on installs. On the
@@ -0,0 +1,314 @@
1
+ """Evaluate a tool call against a Policy. Pure functions — no I/O, no state."""
2
+
3
+ import logging
4
+ import re
5
+ from collections.abc import Iterator
6
+ from enum import StrEnum
7
+ from typing import Any
8
+
9
+ from ..tools.util_helpers import loads_if_json_container_str
10
+ from .model import Policy, Predicate, Rule
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ class Verdict(StrEnum):
16
+ ALLOW = "allow"
17
+ REQUIRE_APPROVAL = "require_approval"
18
+
19
+
20
+ def normalize_stringified_containers(value: Any) -> Any:
21
+ """Recursively parse JSON-encoded object/array strings into real containers.
22
+
23
+ Some MCP client stacks (Claude Desktop stdio among them — see
24
+ ``tools/util_helpers.py``'s ``JSON_STRING_COERCION``) pass model-emitted
25
+ stringified objects through unrepaired, e.g. sending
26
+ ``{"selector": "{\\"domain\\": \\"light\\"}"}`` instead of a nested
27
+ object. Pydantic's ``JSON_STRING_COERCION`` ``BeforeValidator`` repairs
28
+ this, but only when the tool's own parameter validation runs — INSIDE
29
+ ``call_next``, after policy evaluation. ``iter_path_values`` only
30
+ descends into ``dict``/``list``, so a still-stringified value makes a
31
+ predicate targeting a nested field (``args.selector.domain``,
32
+ ``args.operations.*.entity_id``) silently yield nothing: no rule
33
+ matches, and — for ``ha_bulk_control`` — ``args.get("selector")`` still
34
+ sees a truthy string, so the selector fail-safe's own dynamic-call
35
+ detection still fires, but nothing inside it can be inspected either.
36
+ Applying the same repair here, once, before evaluation and hashing,
37
+ closes that gap for both plain predicate rules and the fail-safe, and
38
+ makes ``compute_args_hash`` key on the same logical value regardless of
39
+ which wire shape the client sent.
40
+
41
+ Malformed JSON that merely looks like a container is left as the raw
42
+ string (not raised): policy evaluation is not the place to surface a
43
+ JSON syntax error — the tool's own validation does that, with a
44
+ properly attributed parameter name.
45
+
46
+ Deeply-nested INPUT (real nested dicts/lists in the caller's own
47
+ ``args`` -- NOT a stringified container decoding into deep nesting;
48
+ ``loads_if_json_container_str`` already absorbs ``json.loads``'s own
49
+ ``RecursionError``, so a still-stringified value can never reach this
50
+ function's own recursion at all) can exceed the interpreter's
51
+ recursion limit and raise ``RecursionError`` here. Unlike the
52
+ malformed-JSON case, this is deliberately NOT swallowed: silently
53
+ returning the unrepaired value would let this security gate evaluate
54
+ (and hash) unnormalized args and, for a rule scoped to a nested
55
+ selector-inspectable field, silently ALLOW a call that should have
56
+ required approval -- the identical "fail open on a degraded gate"
57
+ mistake ``PolicyMiddleware.on_call_tool`` already refuses to make for
58
+ a corrupt policy file. The caller (``on_call_tool``) is responsible
59
+ for catching this and failing the call closed with a structured,
60
+ logged error, matching that same precedent.
61
+ """
62
+ return _normalize_stringified_containers(value)
63
+
64
+
65
+ def _normalize_stringified_containers(value: Any) -> Any:
66
+ """Unbounded recursive worker for ``normalize_stringified_containers``.
67
+
68
+ Split out so the public function's ``RecursionError`` guard wraps a
69
+ single top-level call instead of needing a try/except at every
70
+ recursive frame.
71
+ """
72
+ if isinstance(value, str):
73
+ try:
74
+ return loads_if_json_container_str(value)
75
+ except ValueError:
76
+ return value
77
+ if isinstance(value, dict):
78
+ return {key: _normalize_stringified_containers(v) for key, v in value.items()}
79
+ if isinstance(value, list):
80
+ return [_normalize_stringified_containers(v) for v in value]
81
+ return value
82
+
83
+
84
+ def has_dynamic_selector_targets(name: str, args: dict[str, Any]) -> bool:
85
+ """Return whether identical arguments can resolve to different targets later.
86
+
87
+ Single source of truth for the ``ha_bulk_control`` selector-mode predicate:
88
+ both ``PolicyMiddleware`` (approval-sharing/remembering gates) and
89
+ ``evaluate()`` below (the operations fail-safe) must agree on exactly
90
+ which calls are "dynamic", or a future rename/extension of this check in
91
+ one place silently stops applying to the other.
92
+ """
93
+ return name == "ha_bulk_control" and args.get("selector") is not None
94
+
95
+
96
+ def iter_path_values(args: dict[str, Any], path: str) -> Iterator[Any]:
97
+ """Yield every value the dotted path resolves to.
98
+
99
+ The leading ``args`` segment is implicit and stripped. A ``*`` segment
100
+ fans out across the current node — across dict values for dicts,
101
+ across items for lists — so ``args.*`` yields every top-level
102
+ argument, ``args.config.*`` yields every leaf of the ``config``
103
+ sub-dict, and so on. Empty iterator = no match.
104
+ """
105
+ parts = path.split(".")
106
+ if parts[0] == "args":
107
+ parts = parts[1:]
108
+
109
+ def walk(cur: Any, rest: list[str]) -> Iterator[Any]:
110
+ if not rest:
111
+ yield cur
112
+ return
113
+ head, tail = rest[0], rest[1:]
114
+ if head == "*":
115
+ if isinstance(cur, dict):
116
+ for v in cur.values():
117
+ yield from walk(v, tail)
118
+ elif isinstance(cur, (list, tuple)):
119
+ for v in cur:
120
+ yield from walk(v, tail)
121
+ return
122
+ if isinstance(cur, dict) and head in cur:
123
+ yield from walk(cur[head], tail)
124
+
125
+ yield from walk(args, parts)
126
+
127
+
128
+ def _ci(x: Any) -> Any:
129
+ """Lower-case strings for case-insensitive comparison; pass other
130
+ types through unchanged so type semantics (int != "1") survive.
131
+ Used on both sides of every string op — security gates should fire
132
+ whether the caller wrote 'Lock' or 'LOCK' or 'lock'."""
133
+ return x.lower() if isinstance(x, str) else x
134
+
135
+
136
+ def _contains_matches(val: Any, pv: Any) -> bool:
137
+ if isinstance(val, str) and isinstance(pv, str):
138
+ return pv.lower() in val.lower()
139
+ # Mirror the case-insensitive treatment that ``eq`` / ``in`` /
140
+ # ``not_in`` already apply: a rule listing ``["light.kitchen"]``
141
+ # must match an LLM passing ``"Light.Kitchen"``. Per-element
142
+ # ``_ci`` guards non-string entries so mixed-type collections
143
+ # (e.g. ``[1, "two"]``) keep their natural equality semantics.
144
+ return isinstance(val, (list, tuple, set)) and any(_ci(pv) == _ci(x) for x in val)
145
+
146
+
147
+ def _numeric_matches(val: Any, op: str, pv: Any) -> bool:
148
+ try:
149
+ return bool(val > pv) if op == "gt" else bool(val < pv)
150
+ except TypeError:
151
+ # Numeric rule against a non-numeric arg value — log so
152
+ # users can tell their "temperature > 30" rule isn't
153
+ # silently never firing because the arg is a string.
154
+ logger.debug(
155
+ "policy: %s type-mismatch (val=%r pv=%r) — predicate skipped",
156
+ op,
157
+ val,
158
+ pv,
159
+ )
160
+ return False
161
+
162
+
163
+ def _op_matches(val: Any, op: str, pv: Any) -> bool:
164
+ """Apply one op to one concrete value. Predicate dispatches over
165
+ the candidate values (which may be many for wildcard paths).
166
+
167
+ String comparisons are case-insensitive (security gates shouldn't
168
+ care whether the LLM lowercased its args). Non-string types
169
+ preserve their natural comparison semantics.
170
+ """
171
+ match op:
172
+ case "eq":
173
+ return bool(_ci(val) == _ci(pv))
174
+ case "neq":
175
+ return bool(_ci(val) != _ci(pv))
176
+ case "in":
177
+ return _ci(val) in [_ci(x) for x in (pv or [])]
178
+ case "not_in":
179
+ return _ci(val) not in [_ci(x) for x in (pv or [])]
180
+ case "regex":
181
+ # `regex` is re.search (substring match). Anchor with ^...$
182
+ # for full-match. re.IGNORECASE so '^light\.' matches 'Light.x'.
183
+ return (
184
+ isinstance(val, str)
185
+ and isinstance(pv, str)
186
+ and re.search(pv, val, re.IGNORECASE) is not None
187
+ )
188
+ case "contains":
189
+ return _contains_matches(val, pv)
190
+ case "gt" | "lt":
191
+ return _numeric_matches(val, op, pv)
192
+ return False
193
+
194
+
195
+ def match_predicate(predicate: Predicate, args: dict[str, Any]) -> bool:
196
+ values = list(iter_path_values(args, predicate.path))
197
+ if predicate.op == "exists":
198
+ return bool(values)
199
+ if not values:
200
+ return False
201
+ # Existential semantics: a wildcard path matches if ANY value at the
202
+ # wildcard satisfies the op. For non-wildcard paths there's at most
203
+ # one value so the any() collapses to a single check.
204
+ return any(_op_matches(v, predicate.op, predicate.value) for v in values)
205
+
206
+
207
+ def match_rule(rule: Rule, tool_name: str, args: dict[str, Any]) -> bool:
208
+ if rule.tool_name not in ("*", tool_name):
209
+ return False
210
+ return all(match_predicate(p, args) for p in rule.when)
211
+
212
+
213
+ def find_matching_rule(
214
+ tool_name: str, args: dict[str, Any], policy: Policy
215
+ ) -> Rule | None:
216
+ for rule in policy.rules:
217
+ if match_rule(rule, tool_name, args):
218
+ return rule
219
+ return None
220
+
221
+
222
+ def _predicate_reaches_operations(path: str) -> bool:
223
+ """Whether ``path`` can walk into ``args.operations`` under ``iter_path_values``.
224
+
225
+ ``operations`` sits directly under ``args``, so only the first two
226
+ segments after stripping the implicit ``args`` prefix matter. A literal
227
+ ``operations`` first segment obviously reaches it. A leading ``*`` reaches
228
+ it too — a wildcard segment fans out over EVERY value at that level (see
229
+ ``iter_path_values``), landing on the `operations` list value exactly as
230
+ readily as any other top-level key — but ``operations`` is a *list*, so a
231
+ literal segment right after that wildcard (e.g. ``domain`` in
232
+ ``args.*.domain``) can only ever match a *dict* value at that level (like
233
+ ``selector``) — ``walk()`` requires ``isinstance(cur, dict)`` for a
234
+ literal head, so it silently yields nothing against a list and can never
235
+ reach an operation row. Only a SECOND wildcard (``args.*.*...``, as in
236
+ ``args.*.*.entity_id``) or no further segment at all (bare ``args.*``,
237
+ which yields the raw ``operations`` list value itself) can actually reach
238
+ into the list. Any other concrete first segment (e.g. ``selector``) can
239
+ only ever address selector-inspectable fields and is precisely excluded.
240
+ """
241
+ parts = path.split(".")
242
+ if parts and parts[0] == "args":
243
+ parts = parts[1:]
244
+ if not parts:
245
+ return False
246
+ if parts[0] == "operations":
247
+ return True
248
+ return parts[0] == "*" and (len(parts) == 1 or parts[1] == "*")
249
+
250
+
251
+ def _rule_needs_resolved_operations(rule: Rule) -> bool:
252
+ """Whether ``rule`` inspects fields only known after selector resolution.
253
+
254
+ A selector-mode ``ha_bulk_control`` call carries ``args.selector``, not
255
+ ``args.operations`` — the leaf targets don't exist yet, they're resolved
256
+ inside the tool after this middleware runs. A rule predicate that can
257
+ reach ``args.operations`` (exact, prefixed, or via a leading wildcard —
258
+ see ``_predicate_reaches_operations``) can therefore never get a fair
259
+ match attempt against a selector call and needs the fail-safe below. A
260
+ rule whose predicates only ever address selector-inspectable fields
261
+ (e.g. ``args.selector.domain``) already got a fair, precise match
262
+ attempt in ``find_matching_rule`` and must not be broadened into an
263
+ unconditional gate.
264
+ """
265
+ return any(_predicate_reaches_operations(p.path) for p in rule.when)
266
+
267
+
268
+ def evaluate(tool_name: str, args: dict[str, Any], policy: Policy) -> Verdict:
269
+ """Decide whether one tool call requires approval under ``policy``.
270
+
271
+ A normal rule match (``find_matching_rule``) decides most calls. Two
272
+ fail-safes broaden approval beyond an exact predicate match, each only
273
+ when the operator has SOME rule that could plausibly apply: an
274
+ unmatched ``ha_call_service`` ``ws_command`` call (no ``domain``/
275
+ ``service`` args for a domain/service-keyed rule to match), and an
276
+ ``ha_bulk_control`` selector call whose matching rule needs fields
277
+ (``args.operations.*``) that don't exist yet at gate time, resolved
278
+ only later inside the tool. See the fail-safe blocks below for the
279
+ full reasoning behind each.
280
+ """
281
+ if find_matching_rule(tool_name, args, policy) is not None:
282
+ return Verdict.REQUIRE_APPROVAL
283
+ # ha_call_service exposes a raw WebSocket escape hatch (``ws_command``) that
284
+ # carries no ``domain``/``service`` argument, so a rule keyed on
285
+ # ``args.domain``/``args.service`` cannot match it and it would otherwise slip
286
+ # through the fail-open default. If the operator has ANY rule that applies to
287
+ # ha_call_service -- one scoped to it by name, or a wildcard ``*`` rule, which
288
+ # ``match_rule`` treats as applying to every tool -- treat an unmatched
289
+ # ws_command call as require-approval (fail safe) so the escape hatch cannot
290
+ # sneak past that oversight. Blocking the call (require-approval) rather than
291
+ # silently allowing it is the safe error direction for a raw WS escape hatch,
292
+ # especially since the write-command blocklist is a deliberately
293
+ # non-exhaustive wrapper-bypass list that leans on this gate.
294
+ # Operators who want finer control can add a rule keyed on ``args.ws_command``.
295
+ if (
296
+ tool_name == "ha_call_service"
297
+ and args.get("ws_command")
298
+ and any(rule.tool_name in ("ha_call_service", "*") for rule in policy.rules)
299
+ ):
300
+ return Verdict.REQUIRE_APPROVAL
301
+ # Structural selectors are resolved inside the tool, after this middleware.
302
+ # A pre-existing rule that inspects args.operations.* therefore cannot inspect
303
+ # the eventual leaf targets. Fail safe only for rules that actually depend on
304
+ # that unresolved data — a rule fully expressed over selector-inspectable
305
+ # fields (e.g. args.selector.domain) already had its precise shot at matching
306
+ # above, and broadening it here would defeat a deliberately conditional rule
307
+ # (a rule scoped to selector.domain == "lock" must not gate a "light" call).
308
+ if has_dynamic_selector_targets(tool_name, args) and any(
309
+ rule.tool_name in ("ha_bulk_control", "*")
310
+ and _rule_needs_resolved_operations(rule)
311
+ for rule in policy.rules
312
+ ):
313
+ return Verdict.REQUIRE_APPROVAL
314
+ return Verdict.ALLOW