ha-mcp-dev 8.2.0.dev2364__tar.gz → 8.2.0.dev2367__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.2.0.dev2364/src/ha_mcp_dev.egg-info → ha_mcp_dev-8.2.0.dev2367}/PKG-INFO +1 -1
  2. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/pyproject.toml +1 -1
  3. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/policy/middleware.py +11 -9
  4. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/read_only.py +10 -8
  5. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/settings_ui/__init__.py +19 -15
  6. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/settings_ui/locales/en.json +4 -2
  7. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/settings_ui/settings.html +4 -2
  8. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/settings_ui/settings.js +2 -0
  9. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/stdio_settings_sidecar.py +274 -1
  10. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/visibility/enforcement.py +113 -34
  11. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/visibility/model.py +14 -6
  12. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
  13. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/LICENSE +0 -0
  14. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/MANIFEST.in +0 -0
  15. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/README.md +0 -0
  16. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/setup.cfg +0 -0
  17. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/__init__.py +0 -0
  18. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/__main__.py +0 -0
  19. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_pypi_marker +0 -0
  20. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_rich_compat.py +0 -0
  21. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/__init__.py +0 -0
  22. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/requirements.txt +0 -0
  23. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/LICENSE +0 -0
  24. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/MANIFEST.sha256 +0 -0
  25. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/VENDORED +0 -0
  26. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/__init__.py +0 -0
  27. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/__main__.py +0 -0
  28. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/asyncio/__init__.py +0 -0
  29. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/asyncio/client.py +0 -0
  30. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/asyncio/connection.py +0 -0
  31. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/asyncio/messages.py +0 -0
  32. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/asyncio/router.py +0 -0
  33. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/asyncio/server.py +0 -0
  34. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/auth.py +0 -0
  35. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/cli.py +0 -0
  36. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/client.py +0 -0
  37. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/connection.py +0 -0
  38. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/datastructures.py +0 -0
  39. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/exceptions.py +0 -0
  40. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/extensions/__init__.py +0 -0
  41. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/extensions/base.py +0 -0
  42. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/extensions/permessage_deflate.py +0 -0
  43. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/frames.py +0 -0
  44. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/headers.py +0 -0
  45. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/http11.py +0 -0
  46. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/imports.py +0 -0
  47. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/legacy/__init__.py +0 -0
  48. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/legacy/auth.py +0 -0
  49. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/legacy/client.py +0 -0
  50. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/legacy/exceptions.py +0 -0
  51. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/legacy/framing.py +0 -0
  52. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/legacy/handshake.py +0 -0
  53. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/legacy/http.py +0 -0
  54. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/legacy/protocol.py +0 -0
  55. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/legacy/server.py +0 -0
  56. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/protocol.py +0 -0
  57. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/proxy.py +0 -0
  58. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/py.typed +0 -0
  59. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/server.py +0 -0
  60. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/streams.py +0 -0
  61. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/sync/__init__.py +0 -0
  62. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/sync/client.py +0 -0
  63. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/sync/connection.py +0 -0
  64. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/sync/messages.py +0 -0
  65. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/sync/router.py +0 -0
  66. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/sync/server.py +0 -0
  67. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/sync/utils.py +0 -0
  68. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/trio/__init__.py +0 -0
  69. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/trio/client.py +0 -0
  70. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/trio/connection.py +0 -0
  71. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/trio/messages.py +0 -0
  72. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/trio/router.py +0 -0
  73. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/trio/server.py +0 -0
  74. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/trio/utils.py +0 -0
  75. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/typing.py +0 -0
  76. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/uri.py +0 -0
  77. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/utils.py +0 -0
  78. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_vendor/websockets/version.py +0 -0
  79. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/_version.py +0 -0
  80. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/auth/__init__.py +0 -0
  81. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/auth/consent_form.py +0 -0
  82. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/auth/oidc_compat.py +0 -0
  83. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/auth/provider.py +0 -0
  84. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/backup_manager.py +0 -0
  85. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/browser_landing.py +0 -0
  86. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/client/__init__.py +0 -0
  87. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/client/rest_client.py +0 -0
  88. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/client/supervisor_client.py +0 -0
  89. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/client/websocket_client.py +0 -0
  90. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/client/websocket_listener.py +0 -0
  91. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/config.py +0 -0
  92. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/dashboard_screenshot/__init__.py +0 -0
  93. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/dashboard_screenshot/capture.py +0 -0
  94. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/dashboard_screenshot/content.py +0 -0
  95. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/dashboard_screenshot/paths.py +0 -0
  96. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/dashboard_screenshot/provision.py +0 -0
  97. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/dashboard_screenshot/theme_guard.py +0 -0
  98. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/errors.py +0 -0
  99. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/hacs_auto_refresh.py +0 -0
  100. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/llm_exposure.py +0 -0
  101. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/policy/__init__.py +0 -0
  102. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/policy/approval_queue.py +0 -0
  103. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/policy/editing.py +0 -0
  104. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/policy/evaluator.py +0 -0
  105. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/policy/handlers.py +0 -0
  106. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/policy/model.py +0 -0
  107. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/policy/persistence.py +0 -0
  108. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/policy/value_sources.py +0 -0
  109. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/py.typed +0 -0
  110. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/redaction.py +0 -0
  111. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/renamed_tools.py +0 -0
  112. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
  113. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
  114. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
  115. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
  116. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
  117. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
  118. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
  119. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
  120. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
  121. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
  122. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
  123. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
  124. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/appdaemon.md +0 -0
  125. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
  126. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/blueprint-guide.md +0 -0
  127. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
  128. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
  129. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
  130. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
  131. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
  132. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
  133. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
  134. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/scenes.md +0 -0
  135. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
  136. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/yaml-only-integrations.md +0 -0
  137. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/server.py +0 -0
  138. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/settings_ui/_handlers_advanced.py +0 -0
  139. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/settings_ui/_handlers_backups.py +0 -0
  140. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/settings_ui/_handlers_fs.py +0 -0
  141. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/settings_ui/_handlers_server.py +0 -0
  142. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/settings_ui/_handlers_theme.py +0 -0
  143. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/settings_ui/_handlers_tools.py +0 -0
  144. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/settings_ui/_i18n.py +0 -0
  145. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/settings_ui/_persistence.py +0 -0
  146. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/settings_ui/_supervisor.py +0 -0
  147. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/settings_ui/_theme.py +0 -0
  148. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/settings_ui/_tools_meta.py +0 -0
  149. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/settings_ui/locales/README.md +0 -0
  150. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/settings_ui/locales/cs.json +0 -0
  151. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/settings_ui/locales/de.json +0 -0
  152. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/settings_ui/locales/eo.json +0 -0
  153. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/settings_ui/locales/es.json +0 -0
  154. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/settings_ui/locales/fr.json +0 -0
  155. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/settings_ui/locales/it.json +0 -0
  156. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/settings_ui/locales/nl.json +0 -0
  157. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/settings_ui/locales/pl.json +0 -0
  158. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/settings_ui/locales/ru.json +0 -0
  159. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/settings_ui/locales/sv.json +0 -0
  160. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/settings_ui/locales/zh-Hans.json +0 -0
  161. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/settings_ui/settings.css +0 -0
  162. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/smoke_test.py +0 -0
  163. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/strict_bps.py +0 -0
  164. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/__init__.py +0 -0
  165. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/auto_backup.py +0 -0
  166. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/backup.py +0 -0
  167. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/best_practice_checker.py +0 -0
  168. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/best_practice_result.py +0 -0
  169. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/best_practice_variables.py +0 -0
  170. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/component_api.py +0 -0
  171. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/component_config_entries.py +0 -0
  172. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/component_config_reads.py +0 -0
  173. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/component_devices.py +0 -0
  174. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/component_registries.py +0 -0
  175. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/component_registry_lookup.py +0 -0
  176. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/config_entry_flow.py +0 -0
  177. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/config_entry_flow_form.py +0 -0
  178. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/config_entry_flow_menu.py +0 -0
  179. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/config_entry_flow_walker.py +0 -0
  180. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/config_entry_identity.py +0 -0
  181. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/config_entry_reconfigure.py +0 -0
  182. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/config_entry_reload_watch.py +0 -0
  183. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/device_control.py +0 -0
  184. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/error_log_parsing.py +0 -0
  185. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/hacs_registration.py +0 -0
  186. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/helpers.py +0 -0
  187. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/integration_reconfigure.py +0 -0
  188. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/radio/__init__.py +0 -0
  189. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/radio/base.py +0 -0
  190. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/radio/matter.py +0 -0
  191. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/radio/thread.py +0 -0
  192. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/radio/zigbee.py +0 -0
  193. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/radio/zwave.py +0 -0
  194. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/reference_validator.py +0 -0
  195. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/registry.py +0 -0
  196. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/renamed_tool_middleware.py +0 -0
  197. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/smart_search/__init__.py +0 -0
  198. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/smart_search/_base.py +0 -0
  199. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/smart_search/_config.py +0 -0
  200. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/smart_search/_deep.py +0 -0
  201. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/smart_search/_entities.py +0 -0
  202. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/smart_search/_fetch.py +0 -0
  203. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/smart_search/_overview.py +0 -0
  204. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/smart_search/_scenes.py +0 -0
  205. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/smart_search/_scoring.py +0 -0
  206. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tool_search_hint_middleware.py +0 -0
  207. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_addons.py +0 -0
  208. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_areas.py +0 -0
  209. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_assist_pipeline.py +0 -0
  210. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_blueprints.py +0 -0
  211. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_bug_report.py +0 -0
  212. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_calendar.py +0 -0
  213. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_camera.py +0 -0
  214. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_categories.py +0 -0
  215. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_code.py +0 -0
  216. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_config_automations.py +0 -0
  217. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
  218. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
  219. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
  220. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
  221. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_dashboard_screenshot.py +0 -0
  222. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_dev.py +0 -0
  223. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_energy.py +0 -0
  224. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_entities.py +0 -0
  225. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_filesystem.py +0 -0
  226. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_groups.py +0 -0
  227. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_hacs.py +0 -0
  228. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_history.py +0 -0
  229. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_integrations.py +0 -0
  230. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_labels.py +0 -0
  231. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_radio.py +0 -0
  232. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_registry.py +0 -0
  233. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_resources.py +0 -0
  234. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_search.py +0 -0
  235. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_security_policy.py +0 -0
  236. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_service.py +0 -0
  237. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_services.py +0 -0
  238. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_system.py +0 -0
  239. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_themes.py +0 -0
  240. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_todo.py +0 -0
  241. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_traces.py +0 -0
  242. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_updates.py +0 -0
  243. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_utility.py +0 -0
  244. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
  245. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
  246. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_yaml_read.py +0 -0
  247. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/tools_zones.py +0 -0
  248. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/util_helpers.py +0 -0
  249. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/tools/validation_middleware.py +0 -0
  250. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/transforms/__init__.py +0 -0
  251. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/transforms/categorized_search.py +0 -0
  252. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
  253. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/transport_security.py +0 -0
  254. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/update_check.py +0 -0
  255. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/utils/__init__.py +0 -0
  256. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/utils/config_hash.py +0 -0
  257. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/utils/config_write_lock.py +0 -0
  258. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/utils/data_paths.py +0 -0
  259. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/utils/domain_handlers.py +0 -0
  260. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/utils/fuzzy_search.py +0 -0
  261. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
  262. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/utils/operation_manager.py +0 -0
  263. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/utils/python_sandbox.py +0 -0
  264. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/utils/skill_loader.py +0 -0
  265. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/utils/usage_logger.py +0 -0
  266. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/visibility/__init__.py +0 -0
  267. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/visibility/persistence.py +0 -0
  268. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp/visibility/resolver.py +0 -0
  269. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
  270. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
  271. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
  272. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
  273. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
  274. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/tests/__init__.py +0 -0
  275. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/tests/test_constants.py +0 -0
  276. {ha_mcp_dev-8.2.0.dev2364 → ha_mcp_dev-8.2.0.dev2367}/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.2.0.dev2364
3
+ Version: 8.2.0.dev2367
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.2.0.dev2364"
7
+ version = "8.2.0.dev2367"
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
@@ -20,21 +20,23 @@ from .model import Policy, Rule
20
20
 
21
21
  logger = logging.getLogger(__name__)
22
22
 
23
- # Toolsearch proxy meta-tools — always pass through. Gating the proxy
24
- # directly would be wrong: rule predicates target the REAL tool's args
25
- # (e.g. args.domain), but the proxy receives wrapped {"name": "...",
26
- # "arguments": {...}} envelopes. The proxy re-dispatches via
27
- # ctx.fastmcp.call_tool(name, arguments), which re-enters the middleware
28
- # chain with the real tool name and args, so the inner call gets gated
29
- # correctly there.
30
- PROXY_META_TOOLS = frozenset(
23
+ # Dispatching call proxies are a strict subset of the ungated tool-search
24
+ # meta-tools. Only these three execute their envelope's ``name``; search merely
25
+ # returns catalog metadata and must not be unwrapped by other middleware.
26
+ # Gating a call proxy directly would be wrong: rule predicates target the real
27
+ # tool's args (for example args.domain), while the proxy receives a wrapped
28
+ # {"name": "...", "arguments": {...}} envelope. Its dispatch re-enters the
29
+ # middleware chain with the real name and args, so the inner call is gated there.
30
+ CALL_PROXY_META_TOOLS = frozenset(
31
31
  {
32
32
  "ha_call_read_tool",
33
33
  "ha_call_write_tool",
34
34
  "ha_call_delete_tool",
35
- "ha_search_tools",
36
35
  }
37
36
  )
37
+ # Policy itself also leaves catalog search ungated; unlike the call proxies it
38
+ # never dispatches a tool named in client-supplied arguments.
39
+ PROXY_META_TOOLS = CALL_PROXY_META_TOOLS | {"ha_search_tools"}
38
40
 
39
41
  # ha_dev_manage_server actions that MANAGE the approval queue itself.
40
42
  # Gating these deadlocks by construction: with a wildcard (or
@@ -39,7 +39,7 @@ from fastmcp.tools import Tool
39
39
 
40
40
  from .config import get_global_settings
41
41
  from .errors import ErrorCode, create_error_response
42
- from .policy.middleware import PROXY_META_TOOLS
42
+ from .policy.middleware import CALL_PROXY_META_TOOLS
43
43
  from .renamed_tools import current_tool_name
44
44
  from .tools.helpers import raise_tool_error
45
45
 
@@ -428,7 +428,7 @@ class ReadOnlyMiddleware(Middleware):
428
428
  arguments = cls._coerce_arguments(args.get("arguments"))
429
429
  while (
430
430
  isinstance(name, str)
431
- and name in PROXY_META_TOOLS
431
+ and name in CALL_PROXY_META_TOOLS
432
432
  and isinstance(arguments, dict)
433
433
  and isinstance(arguments.get("name"), str)
434
434
  ):
@@ -460,12 +460,14 @@ class ReadOnlyMiddleware(Middleware):
460
460
  args = context.message.arguments or {}
461
461
 
462
462
  # Call proxies: decide on the INNER call (see _unwrap_proxy_call).
463
- # ha_search_tools and envelope-less proxy calls pass through —
464
- # searching is a read, and the proxy raises its own validation
465
- # error for a missing inner name. When the inner call is allowed,
466
- # the proxy dispatch re-enters this middleware with the real tool
467
- # name anyway (harmless re-check, same verdict).
468
- if name in PROXY_META_TOOLS:
463
+ # Envelope-less proxy calls pass through because the proxy raises its own
464
+ # validation error for a missing inner name. When the inner call is
465
+ # allowed, proxy dispatch re-enters this middleware with the real tool name
466
+ # anyway (harmless re-check, same verdict). ``ha_search_tools`` is not a
467
+ # dispatch proxy; it reaches generic classification below as an unknown
468
+ # synthetic tool, which passes through to the search transform (or the
469
+ # stale-tool hint path when search is disabled).
470
+ if name in CALL_PROXY_META_TOOLS:
469
471
  unwrapped = self._unwrap_proxy_call(args)
470
472
  if unwrapped is None:
471
473
  return await call_next(context)
@@ -312,21 +312,25 @@ def _build_visibility_handlers(*, data_dir: Path) -> dict[str, Any]:
312
312
  new_config = VisibilityConfig.model_validate(await request.json())
313
313
  except (ValidationError, ValueError) as e:
314
314
  return JSONResponse({"error": str(e)}, status_code=400)
315
- # Optimistic concurrency: reject if the on-disk version moved between
316
- # this caller's GET and PUT.
317
- current = load_visibility_config(data_dir)
318
- if new_config.version != current.version:
319
- return JSONResponse(
320
- {
321
- "error": (
322
- "Visibility config version mismatch. Reload before saving."
323
- ),
324
- "current_version": current.version,
325
- "current_config": current.model_dump(mode="json"),
326
- },
327
- status_code=409,
328
- )
329
- save_visibility_config(data_dir, new_config)
315
+ # The guard makes the version check a real cross-process
316
+ # compare-and-swap: the stdio settings sidecar and main MCP process can
317
+ # both write this file.
318
+ from ..utils.config_write_lock import config_write_guard
319
+
320
+ async with config_write_guard():
321
+ current = load_visibility_config(data_dir)
322
+ if new_config.version != current.version:
323
+ return JSONResponse(
324
+ {
325
+ "error": (
326
+ "Visibility config version mismatch. Reload before saving."
327
+ ),
328
+ "current_version": current.version,
329
+ "current_config": current.model_dump(mode="json"),
330
+ },
331
+ status_code=409,
332
+ )
333
+ save_visibility_config(data_dir, new_config)
330
334
  return JSONResponse({"saved": True, "version": new_config.version + 1})
331
335
 
332
336
  return {
@@ -96,8 +96,10 @@
96
96
  "visibility.boundary": "Without enforce mode (below), this is noise reduction, not access control: a hidden entity is still returned by a direct read of its entity_id and still appears in automation, dashboard, and template content. Do not rely on the filter alone as a security boundary.",
97
97
  "visibility.enabled": "Enable entity visibility filter",
98
98
  "visibility.enforce.title": "Enforce mode",
99
- "visibility.enforce.help": "Extends the filter from decluttering search and overview to every tool read. A direct read of a hidden entity returns “not found”, and a content read (dashboard, template, automation, trace, log, or file) that would surface a hidden entity is refused. Best-effort concealment against incidental exposure, not a hardened security boundary.",
100
- "visibility.enforce.only_enforced": "Make hidden entities unreadable across all tools (not just search)",
99
+ "visibility.enforce.help": "Extends the filter from decluttering search and overview to every tool read except ha_report_issue by default. A direct read of a hidden entity returns “not found”, and a content read that would surface one is refused. The report-tool exception is controlled below. Best-effort concealment against incidental exposure, not a hardened security boundary.",
100
+ "visibility.enforce.only_enforced": "Make hidden entities unreadable across tool reads (except issue reports by default)",
101
+ "visibility.enforce.report_issue_help": "While this is off, ha_report_issue and all of its diagnostic output—including logs—stay outside enforce mode on every call, even when visibility data is healthy or unavailable. Turn it on to scan and refuse that tool like other reads.",
102
+ "visibility.enforce.restrict_report_issue": "Apply enforce mode to ha_report_issue",
101
103
  "visibility.categories.title": "Hide by category",
102
104
  "visibility.categories.help": "Home Assistant tags auto-generated entities as diagnostic or config.",
103
105
  "visibility.categories.legend": "Entity categories to hide",
@@ -485,8 +485,10 @@
485
485
 
486
486
  <section class="a11y-section">
487
487
  <h2 class="a11y-section-title" data-i18n="visibility.enforce.title">Enforce mode</h2>
488
- <p class="a11y-section-help" data-i18n="visibility.enforce.help">Extends the filter from decluttering search and overview to every tool read. A direct read of a hidden entity returns &ldquo;not found&rdquo;, and a content read (dashboard, template, automation, trace, log, or file) that would surface a hidden entity is refused. Best-effort concealment against incidental exposure, not a hardened security boundary.</p>
489
- <label class="a11y-option"><input type="checkbox" id="visibility-enforce"> <span data-i18n="visibility.enforce.only_enforced">Make hidden entities unreadable across all tools (not just search)</span></label>
488
+ <p class="a11y-section-help" data-i18n="visibility.enforce.help">Extends the filter from decluttering search and overview to every tool read except ha_report_issue by default. A direct read of a hidden entity returns &ldquo;not found&rdquo;, and a content read that would surface one is refused. The report-tool exception is controlled below. Best-effort concealment against incidental exposure, not a hardened security boundary.</p>
489
+ <label class="a11y-option"><input type="checkbox" id="visibility-enforce"> <span data-i18n="visibility.enforce.only_enforced">Make hidden entities unreadable across tool reads (except issue reports by default)</span></label>
490
+ <p class="a11y-section-help" data-i18n="visibility.enforce.report_issue_help">While this is off, ha_report_issue and all of its diagnostic output&mdash;including logs&mdash;stay outside enforce mode on every call, even when visibility data is healthy or unavailable. Turn it on to scan and refuse that tool like other reads.</p>
491
+ <label class="a11y-option"><input type="checkbox" id="visibility-restrict-report-issue"> <span data-i18n="visibility.enforce.restrict_report_issue">Apply enforce mode to ha_report_issue</span></label>
490
492
  </section>
491
493
 
492
494
  <section class="a11y-section">
@@ -4488,6 +4488,7 @@ async function visibilityLoadConfig() {
4488
4488
  const cats = c.exclude_categories || [];
4489
4489
  document.getElementById('visibility-enabled').checked = !!c.enabled;
4490
4490
  document.getElementById('visibility-enforce').checked = !!c.enforce;
4491
+ document.getElementById('visibility-restrict-report-issue').checked = !!c.restrict_report_issue;
4491
4492
  document.getElementById('visibility-cat-diagnostic').checked = cats.includes('diagnostic');
4492
4493
  document.getElementById('visibility-cat-config').checked = cats.includes('config');
4493
4494
  document.getElementById('visibility-exclude-hidden').checked = !!c.exclude_hidden;
@@ -4509,6 +4510,7 @@ async function visibilitySaveConfig() {
4509
4510
  version: visibilityVersion,
4510
4511
  enabled: document.getElementById('visibility-enabled').checked,
4511
4512
  enforce: document.getElementById('visibility-enforce').checked,
4513
+ restrict_report_issue: document.getElementById('visibility-restrict-report-issue').checked,
4512
4514
  exclude_categories: cats,
4513
4515
  exclude_hidden: document.getElementById('visibility-exclude-hidden').checked,
4514
4516
  deny_entity_ids: _visibilityParseList(document.getElementById('visibility-deny').value, '\n'),
@@ -277,7 +277,15 @@ def _no_proxy_opener() -> Any:
277
277
  """
278
278
  import urllib.request
279
279
 
280
- return urllib.request.build_opener(urllib.request.ProxyHandler({}))
280
+ class _NoRedirectHandler(urllib.request.HTTPRedirectHandler):
281
+ """Keep the loopback-only shutdown request on its original target."""
282
+
283
+ def redirect_request(self, *_args: Any, **_kwargs: Any) -> None:
284
+ return None
285
+
286
+ return urllib.request.build_opener(
287
+ urllib.request.ProxyHandler({}), _NoRedirectHandler()
288
+ )
281
289
 
282
290
 
283
291
  # Shape run_main() writes into ui.url. Doubles as the legacy-migration
@@ -287,6 +295,15 @@ _SIDECAR_URL_RE = re.compile(
287
295
  )
288
296
 
289
297
 
298
+ def _is_trusted_sidecar_url(url: str) -> bool:
299
+ """Return whether a URL matches the loopback sidecar producer contract."""
300
+ match = _SIDECAR_URL_RE.fullmatch(url.strip())
301
+ if match is None:
302
+ return False
303
+ port = int(match.group(1))
304
+ return 1024 <= port <= 65535
305
+
306
+
290
307
  def _seed_state_from_url(url: str) -> None:
291
308
  """Migrate a pre-``ui.state`` install's stable URL into the state file.
292
309
 
@@ -335,6 +352,13 @@ def _post_shutdown(url: str) -> tuple[bool, bool | None]:
335
352
  """
336
353
  import urllib.error
337
354
 
355
+ if not _is_trusted_sidecar_url(url):
356
+ logger.warning(
357
+ "Recorded sidecar URL is invalid; refusing the shutdown request and "
358
+ "replacing state files only."
359
+ )
360
+ return False, None
361
+
338
362
  try:
339
363
  import urllib.request
340
364
 
@@ -384,6 +408,185 @@ def _post_shutdown(url: str) -> tuple[bool, bool | None]:
384
408
  return False, None
385
409
 
386
410
 
411
+ def _read_external_retirement_url(config_dir: Path) -> str | None:
412
+ """Read the sidecar URL used by an external-process cleanup."""
413
+ url_path = config_dir / "ui.url"
414
+ try:
415
+ url = url_path.read_text(encoding="utf-8").strip()
416
+ except FileNotFoundError:
417
+ logger.debug("Sidecar URL file %s not found; nothing to retire.", url_path)
418
+ return None
419
+ except PermissionError:
420
+ logger.warning(
421
+ "Cannot read sidecar URL file %s; a detached listener may still be "
422
+ "running for config dir %s.",
423
+ url_path,
424
+ config_dir,
425
+ exc_info=True,
426
+ )
427
+ return None
428
+ except OSError:
429
+ logger.warning(
430
+ "Failed to read sidecar URL file %s; a detached listener may still be "
431
+ "running for config dir %s.",
432
+ url_path,
433
+ config_dir,
434
+ exc_info=True,
435
+ )
436
+ return None
437
+ if not url:
438
+ logger.warning(
439
+ "Sidecar URL file %s is empty; a detached listener may still be running "
440
+ "for config dir %s.",
441
+ url_path,
442
+ config_dir,
443
+ )
444
+ return None
445
+ return url
446
+
447
+
448
+ def _read_external_retirement_pid(config_dir: Path) -> int | None:
449
+ """Read the recorded PID so an acknowledged retirement can await exit."""
450
+ pid_path = config_dir / "ui.pid"
451
+ try:
452
+ return int(pid_path.read_text(encoding="utf-8").strip())
453
+ except FileNotFoundError:
454
+ logger.debug("Sidecar PID file %s not found; exit cannot be polled.", pid_path)
455
+ except PermissionError:
456
+ logger.warning(
457
+ "Cannot read sidecar PID file %s; exit cannot be polled for config dir %s.",
458
+ pid_path,
459
+ config_dir,
460
+ exc_info=True,
461
+ )
462
+ except OSError:
463
+ logger.warning(
464
+ "Failed to read sidecar PID file %s; exit cannot be polled for config "
465
+ "dir %s.",
466
+ pid_path,
467
+ config_dir,
468
+ exc_info=True,
469
+ )
470
+ except ValueError:
471
+ logger.warning(
472
+ "Sidecar PID file %s is invalid; exit cannot be polled for config dir %s.",
473
+ pid_path,
474
+ config_dir,
475
+ )
476
+ return None
477
+
478
+
479
+ def _external_retirement_sentinel_preexisting(sentinel_path: Path) -> bool:
480
+ """Snapshot whether the user already disabled the settings sidecar."""
481
+ try:
482
+ return sentinel_path.exists()
483
+ except OSError:
484
+ logger.warning(
485
+ "Cannot inspect sidecar disable sentinel %s; preserving it.",
486
+ sentinel_path,
487
+ exc_info=True,
488
+ )
489
+ return True
490
+
491
+
492
+ def _clear_external_retirement_sentinel(
493
+ sentinel_path: Path,
494
+ *,
495
+ url: str,
496
+ sentinel_preexisting: bool,
497
+ sentinel_created: bool | None,
498
+ ) -> None:
499
+ """Clear only a legacy disable sentinel attributable to this retirement."""
500
+ should_clear = (
501
+ sentinel_created if sentinel_created is not None else not sentinel_preexisting
502
+ )
503
+ if not should_clear:
504
+ return
505
+ try:
506
+ sentinel_path.unlink()
507
+ except FileNotFoundError:
508
+ logger.debug(
509
+ "Sidecar disable sentinel %s was already absent after retiring %s.",
510
+ sentinel_path,
511
+ url,
512
+ )
513
+ except OSError:
514
+ logger.warning(
515
+ "Retired sidecar at %s but could not remove its disable sentinel %s.",
516
+ url,
517
+ sentinel_path,
518
+ exc_info=True,
519
+ )
520
+
521
+
522
+ def _wait_for_external_retired_pid(
523
+ pid: int | None,
524
+ *,
525
+ url: str,
526
+ config_dir: Path,
527
+ ) -> None:
528
+ """Wait a bounded interval for an acknowledged detached sidecar to exit."""
529
+ if pid is None:
530
+ return
531
+ import time
532
+
533
+ deadline = time.monotonic() + _OLD_SIDECAR_EXIT_WAIT
534
+ while _pid_alive(pid):
535
+ if time.monotonic() >= deadline:
536
+ logger.warning(
537
+ "Retired sidecar pid %s at %s is still alive after %.0fs for "
538
+ "config dir %s.",
539
+ pid,
540
+ url,
541
+ _OLD_SIDECAR_EXIT_WAIT,
542
+ config_dir,
543
+ )
544
+ return
545
+ time.sleep(_OLD_SIDECAR_EXIT_POLL)
546
+
547
+
548
+ def retire_sidecar(config_dir: Path) -> bool:
549
+ """Best-effort retirement for an externally managed stdio test process.
550
+
551
+ This public helper intentionally crosses the process boundary: callers that
552
+ spawn a real ``ha-mcp`` stdio server otherwise have no handle to its detached
553
+ settings listener. It uses discovery files in ``config_dir``, reports each
554
+ failure mode, and returns whether the sidecar acknowledged shutdown.
555
+
556
+ The replacement path in ``_shutdown_existing_sidecar`` deliberately remains
557
+ separate because it seeds global sidecar state and immediately spawns a successor.
558
+ """
559
+ url = _read_external_retirement_url(config_dir)
560
+ if url is None:
561
+ return False
562
+ # Capture both before the POST: an acknowledged sidecar may remove its PID,
563
+ # and the sentinel snapshot distinguishes a legacy endpoint's write from a
564
+ # user's preexisting disable choice.
565
+ pid = _read_external_retirement_pid(config_dir)
566
+ sentinel_path = config_dir / "settings_ui_disabled"
567
+ sentinel_preexisting = _external_retirement_sentinel_preexisting(sentinel_path)
568
+
569
+ acked, sentinel_created = _post_shutdown(url)
570
+ if not acked:
571
+ logger.warning(
572
+ "Sidecar at %s did not acknowledge retirement for config dir %s; "
573
+ "the detached listener may still be running.",
574
+ url,
575
+ config_dir,
576
+ )
577
+ return False
578
+
579
+ _clear_external_retirement_sentinel(
580
+ sentinel_path,
581
+ url=url,
582
+ sentinel_preexisting=sentinel_preexisting,
583
+ sentinel_created=sentinel_created,
584
+ )
585
+ logger.info("Retired settings UI sidecar at %s for config dir %s", url, config_dir)
586
+ _wait_for_external_retired_pid(pid, url=url, config_dir=config_dir)
587
+ return True
588
+
589
+
387
590
  def _log_shutdown_timeout(exc: BaseException) -> None:
388
591
  logger.warning(
389
592
  "Old sidecar did not answer /shutdown within %.1fs (%s); "
@@ -420,6 +623,9 @@ def _shutdown_existing_sidecar() -> None:
420
623
  replacement, so nothing hands out the old URL anymore (if the
421
624
  replacement loses the port to that lingering process, it serves on
422
625
  an ephemeral fallback instead).
626
+
627
+ External test-process cleanup uses ``retire_sidecar``; the sequences stay
628
+ separate because only this path seeds state and immediately replaces the listener.
423
629
  """
424
630
  url = read_sidecar_url()
425
631
  if url is None:
@@ -1163,6 +1369,60 @@ def _build_app(
1163
1369
  handlers["save_theme_prefs"],
1164
1370
  methods=["POST"],
1165
1371
  ),
1372
+ # Advanced settings. The stdio-only sidecar-port control is rendered
1373
+ # from this handler, so these routes are required in sidecar mode.
1374
+ Route(
1375
+ f"{secret_prefix}/api/settings/advanced",
1376
+ handlers["get_advanced_settings"],
1377
+ methods=["GET"],
1378
+ ),
1379
+ Route(
1380
+ f"{secret_prefix}/api/settings/advanced",
1381
+ handlers["save_advanced_settings"],
1382
+ methods=["POST"],
1383
+ ),
1384
+ # Auto-backup endpoints (#1288). The Backups tab is available in the
1385
+ # shared settings page and must use the same handlers in stdio mode.
1386
+ Route(
1387
+ f"{secret_prefix}/api/settings/backups",
1388
+ handlers["list_backups"],
1389
+ methods=["GET"],
1390
+ ),
1391
+ Route(
1392
+ f"{secret_prefix}/api/settings/backups",
1393
+ handlers["delete_backups_bulk"],
1394
+ methods=["DELETE"],
1395
+ ),
1396
+ Route(
1397
+ f"{secret_prefix}/api/settings/backups/{{name}}",
1398
+ handlers["view_backup"],
1399
+ methods=["GET"],
1400
+ ),
1401
+ Route(
1402
+ f"{secret_prefix}/api/settings/backups/{{name}}/diff",
1403
+ handlers["diff_backup"],
1404
+ methods=["GET"],
1405
+ ),
1406
+ Route(
1407
+ f"{secret_prefix}/api/settings/backups/{{name}}/restore",
1408
+ handlers["restore_backup"],
1409
+ methods=["POST"],
1410
+ ),
1411
+ Route(
1412
+ f"{secret_prefix}/api/settings/backups/{{name}}",
1413
+ handlers["delete_backup"],
1414
+ methods=["DELETE"],
1415
+ ),
1416
+ Route(
1417
+ f"{secret_prefix}/api/settings/backup-config",
1418
+ handlers["get_backup_config"],
1419
+ methods=["GET"],
1420
+ ),
1421
+ Route(
1422
+ f"{secret_prefix}/api/settings/backup-config",
1423
+ handlers["save_backup_config"],
1424
+ methods=["POST"],
1425
+ ),
1166
1426
  # Custom filesystem directories (issue #1567). The sub-form is rendered
1167
1427
  # in the features panel, so the stdio sidecar must serve these too — its
1168
1428
  # route list is hand-maintained and does NOT derive from
@@ -1217,6 +1477,19 @@ def _build_app(
1217
1477
  handlers["policy_get_value_source"],
1218
1478
  methods=["GET"],
1219
1479
  ),
1480
+ # Entity visibility filter endpoints. The sidecar owns a hand-maintained
1481
+ # route table (it cannot reuse FastMCP custom-route registration), so
1482
+ # keep this pair in lockstep with settings_ui.register_settings_routes.
1483
+ Route(
1484
+ f"{secret_prefix}/api/visibility/config",
1485
+ handlers["visibility_get_config"],
1486
+ methods=["GET"],
1487
+ ),
1488
+ Route(
1489
+ f"{secret_prefix}/api/visibility/config",
1490
+ handlers["visibility_put_config"],
1491
+ methods=["PUT"],
1492
+ ),
1220
1493
  ]
1221
1494
 
1222
1495
  # /shutdown — POST endpoint that drops the disable sentinel and