ha-mcp-dev 8.3.0.dev2419__tar.gz → 8.3.0.dev2425__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 (280) hide show
  1. {ha_mcp_dev-8.3.0.dev2419/src/ha_mcp_dev.egg-info → ha_mcp_dev-8.3.0.dev2425}/PKG-INFO +1 -1
  2. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/pyproject.toml +3 -2
  3. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/__main__.py +2 -69
  4. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/client/rest_client.py +42 -40
  5. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/client/supervisor_client.py +13 -22
  6. ha_mcp_dev-8.3.0.dev2425/src/ha_mcp/log_filters.py +207 -0
  7. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/tools_addons.py +824 -452
  8. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
  9. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp_dev.egg-info/SOURCES.txt +1 -0
  10. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/LICENSE +0 -0
  11. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/MANIFEST.in +0 -0
  12. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/README.md +0 -0
  13. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/setup.cfg +0 -0
  14. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/__init__.py +0 -0
  15. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/_pypi_marker +0 -0
  16. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/_rich_compat.py +0 -0
  17. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/_vendor/__init__.py +0 -0
  18. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/_vendor/requirements.txt +0 -0
  19. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/_vendor/websockets/LICENSE +0 -0
  20. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/_vendor/websockets/MANIFEST.sha256 +0 -0
  21. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/_vendor/websockets/VENDORED +0 -0
  22. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/_vendor/websockets/__init__.py +0 -0
  23. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/_vendor/websockets/__main__.py +0 -0
  24. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/_vendor/websockets/asyncio/__init__.py +0 -0
  25. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/_vendor/websockets/asyncio/client.py +0 -0
  26. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/_vendor/websockets/asyncio/connection.py +0 -0
  27. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/_vendor/websockets/asyncio/messages.py +0 -0
  28. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/_vendor/websockets/asyncio/router.py +0 -0
  29. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/_vendor/websockets/asyncio/server.py +0 -0
  30. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/_vendor/websockets/auth.py +0 -0
  31. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/_vendor/websockets/cli.py +0 -0
  32. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/_vendor/websockets/client.py +0 -0
  33. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/_vendor/websockets/connection.py +0 -0
  34. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/_vendor/websockets/datastructures.py +0 -0
  35. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/_vendor/websockets/exceptions.py +0 -0
  36. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/_vendor/websockets/extensions/__init__.py +0 -0
  37. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/_vendor/websockets/extensions/base.py +0 -0
  38. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/_vendor/websockets/extensions/permessage_deflate.py +0 -0
  39. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/_vendor/websockets/frames.py +0 -0
  40. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/_vendor/websockets/headers.py +0 -0
  41. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/_vendor/websockets/http11.py +0 -0
  42. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/_vendor/websockets/imports.py +0 -0
  43. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/_vendor/websockets/legacy/__init__.py +0 -0
  44. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/_vendor/websockets/legacy/auth.py +0 -0
  45. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/_vendor/websockets/legacy/client.py +0 -0
  46. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/_vendor/websockets/legacy/exceptions.py +0 -0
  47. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/_vendor/websockets/legacy/framing.py +0 -0
  48. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/_vendor/websockets/legacy/handshake.py +0 -0
  49. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/_vendor/websockets/legacy/http.py +0 -0
  50. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/_vendor/websockets/legacy/protocol.py +0 -0
  51. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/_vendor/websockets/legacy/server.py +0 -0
  52. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/_vendor/websockets/protocol.py +0 -0
  53. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/_vendor/websockets/proxy.py +0 -0
  54. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/_vendor/websockets/py.typed +0 -0
  55. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/_vendor/websockets/server.py +0 -0
  56. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/_vendor/websockets/streams.py +0 -0
  57. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/_vendor/websockets/sync/__init__.py +0 -0
  58. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/_vendor/websockets/sync/client.py +0 -0
  59. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/_vendor/websockets/sync/connection.py +0 -0
  60. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/_vendor/websockets/sync/messages.py +0 -0
  61. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/_vendor/websockets/sync/router.py +0 -0
  62. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/_vendor/websockets/sync/server.py +0 -0
  63. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/_vendor/websockets/sync/utils.py +0 -0
  64. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/_vendor/websockets/trio/__init__.py +0 -0
  65. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/_vendor/websockets/trio/client.py +0 -0
  66. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/_vendor/websockets/trio/connection.py +0 -0
  67. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/_vendor/websockets/trio/messages.py +0 -0
  68. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/_vendor/websockets/trio/router.py +0 -0
  69. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/_vendor/websockets/trio/server.py +0 -0
  70. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/_vendor/websockets/trio/utils.py +0 -0
  71. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/_vendor/websockets/typing.py +0 -0
  72. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/_vendor/websockets/uri.py +0 -0
  73. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/_vendor/websockets/utils.py +0 -0
  74. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/_vendor/websockets/version.py +0 -0
  75. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/_version.py +0 -0
  76. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/auth/__init__.py +0 -0
  77. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/auth/consent_form.py +0 -0
  78. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/auth/oidc_compat.py +0 -0
  79. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/auth/provider.py +0 -0
  80. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/backup_manager.py +0 -0
  81. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/browser_landing.py +0 -0
  82. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/client/__init__.py +0 -0
  83. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/client/websocket_client.py +0 -0
  84. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/client/websocket_listener.py +0 -0
  85. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/config.py +0 -0
  86. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/dashboard_screenshot/__init__.py +0 -0
  87. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/dashboard_screenshot/capture.py +0 -0
  88. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/dashboard_screenshot/content.py +0 -0
  89. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/dashboard_screenshot/paths.py +0 -0
  90. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/dashboard_screenshot/provision.py +0 -0
  91. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/dashboard_screenshot/theme_guard.py +0 -0
  92. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/errors.py +0 -0
  93. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/hacs_auto_refresh.py +0 -0
  94. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/llm_exposure.py +0 -0
  95. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/policy/__init__.py +0 -0
  96. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/policy/approval_queue.py +0 -0
  97. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/policy/editing.py +0 -0
  98. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/policy/evaluator.py +0 -0
  99. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/policy/handlers.py +0 -0
  100. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/policy/middleware.py +0 -0
  101. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/policy/model.py +0 -0
  102. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/policy/persistence.py +0 -0
  103. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/policy/value_sources.py +0 -0
  104. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/py.typed +0 -0
  105. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/read_only.py +0 -0
  106. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/redaction.py +0 -0
  107. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/renamed_tools.py +0 -0
  108. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
  109. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
  110. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
  111. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
  112. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
  113. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
  114. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
  115. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
  116. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
  117. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
  118. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
  119. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
  120. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/appdaemon.md +0 -0
  121. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
  122. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/blueprint-guide.md +0 -0
  123. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
  124. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
  125. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
  126. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
  127. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
  128. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
  129. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
  130. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/scenes.md +0 -0
  131. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
  132. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/yaml-only-integrations.md +0 -0
  133. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/server.py +0 -0
  134. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/settings_ui/__init__.py +0 -0
  135. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/settings_ui/_handlers_advanced.py +0 -0
  136. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/settings_ui/_handlers_backups.py +0 -0
  137. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/settings_ui/_handlers_fs.py +0 -0
  138. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/settings_ui/_handlers_server.py +0 -0
  139. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/settings_ui/_handlers_theme.py +0 -0
  140. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/settings_ui/_handlers_tools.py +0 -0
  141. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/settings_ui/_i18n.py +0 -0
  142. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/settings_ui/_persistence.py +0 -0
  143. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/settings_ui/_supervisor.py +0 -0
  144. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/settings_ui/_theme.py +0 -0
  145. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/settings_ui/_tools_meta.py +0 -0
  146. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/settings_ui/locales/README.md +0 -0
  147. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/settings_ui/locales/cs.json +0 -0
  148. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/settings_ui/locales/de.json +0 -0
  149. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/settings_ui/locales/en.json +0 -0
  150. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/settings_ui/locales/eo.json +0 -0
  151. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/settings_ui/locales/es.json +0 -0
  152. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/settings_ui/locales/fr.json +0 -0
  153. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/settings_ui/locales/it.json +0 -0
  154. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/settings_ui/locales/ko.json +0 -0
  155. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/settings_ui/locales/nl.json +0 -0
  156. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/settings_ui/locales/pl.json +0 -0
  157. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/settings_ui/locales/ru.json +0 -0
  158. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/settings_ui/locales/sv.json +0 -0
  159. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/settings_ui/locales/zh-Hans.json +0 -0
  160. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/settings_ui/settings.css +0 -0
  161. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/settings_ui/settings.html +0 -0
  162. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/settings_ui/settings.js +0 -0
  163. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/smoke_test.py +0 -0
  164. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
  165. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/strict_bps.py +0 -0
  166. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/__init__.py +0 -0
  167. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/auto_backup.py +0 -0
  168. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/backup.py +0 -0
  169. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/best_practice_checker.py +0 -0
  170. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/best_practice_result.py +0 -0
  171. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/best_practice_variables.py +0 -0
  172. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/bulk_selector.py +0 -0
  173. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/component_api.py +0 -0
  174. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/component_config_entries.py +0 -0
  175. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/component_config_reads.py +0 -0
  176. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/component_devices.py +0 -0
  177. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/component_registries.py +0 -0
  178. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/component_registry_lookup.py +0 -0
  179. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/config_entry_flow.py +0 -0
  180. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/config_entry_flow_form.py +0 -0
  181. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/config_entry_flow_menu.py +0 -0
  182. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/config_entry_flow_walker.py +0 -0
  183. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/config_entry_identity.py +0 -0
  184. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/config_entry_reconfigure.py +0 -0
  185. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/config_entry_reload_watch.py +0 -0
  186. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/device_control.py +0 -0
  187. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/error_log_parsing.py +0 -0
  188. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/hacs_registration.py +0 -0
  189. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/helpers.py +0 -0
  190. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/integration_reconfigure.py +0 -0
  191. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/radio/__init__.py +0 -0
  192. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/radio/base.py +0 -0
  193. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/radio/matter.py +0 -0
  194. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/radio/thread.py +0 -0
  195. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/radio/zigbee.py +0 -0
  196. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/radio/zwave.py +0 -0
  197. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/reference_validator.py +0 -0
  198. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/registry.py +0 -0
  199. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/renamed_tool_middleware.py +0 -0
  200. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/smart_search/__init__.py +0 -0
  201. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/smart_search/_base.py +0 -0
  202. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/smart_search/_config.py +0 -0
  203. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/smart_search/_deep.py +0 -0
  204. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/smart_search/_entities.py +0 -0
  205. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/smart_search/_fetch.py +0 -0
  206. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/smart_search/_overview.py +0 -0
  207. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/smart_search/_scenes.py +0 -0
  208. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/smart_search/_scoring.py +0 -0
  209. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/tool_search_hint_middleware.py +0 -0
  210. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/tools_areas.py +0 -0
  211. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/tools_assist_pipeline.py +0 -0
  212. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/tools_blueprints.py +0 -0
  213. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/tools_bug_report.py +0 -0
  214. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/tools_calendar.py +0 -0
  215. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/tools_camera.py +0 -0
  216. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/tools_categories.py +0 -0
  217. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/tools_code.py +0 -0
  218. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/tools_config_automations.py +0 -0
  219. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
  220. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
  221. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
  222. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
  223. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/tools_dashboard_screenshot.py +0 -0
  224. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/tools_dev.py +0 -0
  225. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/tools_energy.py +0 -0
  226. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/tools_entities.py +0 -0
  227. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/tools_filesystem.py +0 -0
  228. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/tools_groups.py +0 -0
  229. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/tools_hacs.py +0 -0
  230. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/tools_history.py +0 -0
  231. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/tools_integrations.py +0 -0
  232. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/tools_labels.py +0 -0
  233. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/tools_radio.py +0 -0
  234. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/tools_registry.py +0 -0
  235. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/tools_resources.py +0 -0
  236. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/tools_search.py +0 -0
  237. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/tools_security_policy.py +0 -0
  238. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/tools_service.py +0 -0
  239. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/tools_services.py +0 -0
  240. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/tools_system.py +0 -0
  241. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/tools_themes.py +0 -0
  242. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/tools_todo.py +0 -0
  243. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/tools_traces.py +0 -0
  244. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/tools_updates.py +0 -0
  245. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/tools_utility.py +0 -0
  246. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
  247. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
  248. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/tools_yaml_read.py +0 -0
  249. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/tools_zones.py +0 -0
  250. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/util_helpers.py +0 -0
  251. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/tools/validation_middleware.py +0 -0
  252. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/transforms/__init__.py +0 -0
  253. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/transforms/categorized_search.py +0 -0
  254. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
  255. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/transport_security.py +0 -0
  256. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/update_check.py +0 -0
  257. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/utils/__init__.py +0 -0
  258. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/utils/config_hash.py +0 -0
  259. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/utils/config_write_lock.py +0 -0
  260. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/utils/data_paths.py +0 -0
  261. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/utils/domain_handlers.py +0 -0
  262. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/utils/entity_membership.py +0 -0
  263. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/utils/fuzzy_search.py +0 -0
  264. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
  265. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/utils/operation_manager.py +0 -0
  266. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/utils/python_sandbox.py +0 -0
  267. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/utils/skill_loader.py +0 -0
  268. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/utils/usage_logger.py +0 -0
  269. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/visibility/__init__.py +0 -0
  270. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/visibility/enforcement.py +0 -0
  271. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/visibility/model.py +0 -0
  272. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/visibility/persistence.py +0 -0
  273. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp/visibility/resolver.py +0 -0
  274. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
  275. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
  276. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
  277. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
  278. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/tests/__init__.py +0 -0
  279. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/tests/test_constants.py +0 -0
  280. {ha_mcp_dev-8.3.0.dev2419 → ha_mcp_dev-8.3.0.dev2425}/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.dev2419
3
+ Version: 8.3.0.dev2425
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.dev2419"
7
+ version = "8.3.0.dev2425"
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
@@ -256,8 +256,9 @@ markers = [
256
256
  "error_handling: error handling and edge case tests",
257
257
  "cleanup: tests that create entities needing cleanup",
258
258
  "performance: performance measurement and regression tests",
259
- "external_only: skips on HAOS inaddon tier (server runs in a separate addon container, so in-process env monkeypatching doesn't reach it)",
259
+ "external_only: in-process-server tiers only; skips inaddon, stdio, container-embedded, and HAOS-embedded servers",
260
260
  "inaddon_only: only runs on HAOS inaddon tier",
261
+ "beta_haos_only: beta HAOS image lanes with Supervisor/Core version expectations configured",
261
262
  ]
262
263
  asyncio_mode = "auto"
263
264
 
@@ -33,15 +33,13 @@ import threading # noqa: E402
33
33
  from collections.abc import Coroutine # noqa: E402
34
34
  from typing import TYPE_CHECKING, Any, NoReturn # noqa: E402
35
35
 
36
- from fastmcp.exceptions import ToolError # noqa: E402
37
- from pydantic import ValidationError as PydanticValidationError # noqa: E402
38
-
39
36
  from ha_mcp.browser_landing import ( # noqa: E402
40
37
  register_browser_landing as _register_landing_route,
41
38
  )
42
39
  from ha_mcp.browser_landing import ( # noqa: E402
43
40
  register_healthz as _register_healthz_route,
44
41
  )
42
+ from ha_mcp.log_filters import install_sdk_log_filters # noqa: E402
45
43
 
46
44
  if TYPE_CHECKING:
47
45
  from fastmcp import FastMCP
@@ -384,68 +382,6 @@ mcp = _DeferredMCP()
384
382
  _LOG_DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
385
383
 
386
384
 
387
- class StatelessSessionLogFilter(logging.Filter):
388
- """Suppress the routine 'Terminating session: None' log from the MCP SDK.
389
-
390
- In stateless HTTP mode every request creates and tears down a temporary
391
- session whose id is ``None``, so the SDK emits an INFO
392
- ``Terminating session: None`` (mcp/server/streamable_http.py) on *every*
393
- request. The line is routine but looks alarming and has repeatedly
394
- confused users into thinking the connection is broken.
395
-
396
- Returning ``False`` drops the record at this logger before it reaches any
397
- handler. (Merely downgrading the level to DEBUG did not work: the level
398
- gate is applied before the filter runs, so the record was already admitted
399
- and still emitted -- just relabelled.) Real session terminations carry an
400
- actual id and are not matched, so they still log.
401
-
402
- # TODO: remove when modelcontextprotocol/python-sdk#2329 is resolved
403
- """
404
-
405
- def filter(self, record: logging.LogRecord) -> bool:
406
- if record.name != "mcp.server.streamable_http":
407
- return True
408
- try:
409
- message = record.getMessage()
410
- except (ValueError, TypeError):
411
- # A malformed %-format record on this logger is not our target, and
412
- # a filter must not raise: filters run in Logger.handle() with no
413
- # exception handling, so a raise would crash the logging call.
414
- return True
415
- # Drop the stateless teardown noise; keep everything else.
416
- return "Terminating session: None" not in message
417
-
418
-
419
- class ToolValidationLogFilter(logging.Filter):
420
- """Demote fastmcp tool-failure tracebacks to single-line warnings.
421
-
422
- Pydantic ValidationError and tool-raised ToolError aren't server bugs,
423
- so the traceback through fastmcp/pydantic internals is just noise. The
424
- structured error detail is preserved in the WARNING message; stack is
425
- intentionally dropped because these are user-input errors, not bugs.
426
- """
427
-
428
- def filter(self, record: logging.LogRecord) -> bool:
429
- if record.name != "fastmcp.server.server" or not record.exc_info:
430
- return True
431
-
432
- msg = record.getMessage()
433
- err = record.exc_info[1]
434
- if "Error validating tool" in msg and isinstance(err, PydanticValidationError):
435
- record.msg = f"{msg}: {err.errors(include_url=False)}"
436
- elif "Error calling tool" in msg and isinstance(err, ToolError):
437
- record.msg = f"{msg}: {err}"
438
- else:
439
- return True
440
-
441
- record.args = ()
442
- record.levelno = logging.WARNING
443
- record.levelname = "WARNING"
444
- record.exc_info = None
445
- record.exc_text = None
446
- return True
447
-
448
-
449
385
  class ProbeAccessLogFilter(logging.Filter):
450
386
  """Drop benign, non-MCP HTTP probe noise from the uvicorn access log.
451
387
 
@@ -528,10 +464,7 @@ def _setup_logging(log_level_str: str, force: bool = True) -> None:
528
464
  # disabled. Prevent that NOTSET namespace from inheriting our root handler.
529
465
  fastmcp_logger.setLevel(logging.CRITICAL + 1)
530
466
 
531
- logging.getLogger("mcp.server.streamable_http").addFilter(
532
- StatelessSessionLogFilter()
533
- )
534
- logging.getLogger("fastmcp.server.server").addFilter(ToolValidationLogFilter())
467
+ install_sdk_log_filters()
535
468
 
536
469
 
537
470
  def _log_startup_version() -> None:
@@ -704,8 +704,9 @@ class HomeAssistantClient:
704
704
  async def get_addon_logs(self, slug: str, lines: int | None = None) -> str:
705
705
  """Fetch an add-on's container logs.
706
706
 
707
- Branch on ``is_running_in_addon()`` (which keys off ``SUPERVISOR_TOKEN``
708
- in env): inside the add-on container goes directly to the Supervisor
707
+ Branch on ``is_running_in_addon()``, which requires a truthy
708
+ ``SUPERVISOR_TOKEN`` and excludes ``HA_MCP_EMBEDDED``: inside the app
709
+ (add-on) container goes directly to the Supervisor
709
710
  REST API at ``http://supervisor/addons/{slug}/logs`` with the
710
711
  Supervisor token. The HA Core proxy at
711
712
  ``/api/hassio/addons/{slug}/logs`` rejects this token+path combination
@@ -730,8 +731,9 @@ class HomeAssistantClient:
730
731
  Raises:
731
732
  HomeAssistantAuthError: 401 response, or ``SUPERVISOR_TOKEN`` empty
732
733
  at call time on the addon branch.
733
- HomeAssistantAPIError: 403 (role too low — addon needs hassio_role
734
- ``manager``), 404 (unknown slug), or other non-2xx. The
734
+ HomeAssistantAPIError: 403 (unrecognized token, missing
735
+ ``hassio_api``, or insufficient ``hassio_role``), 404
736
+ (unknown slug), or other non-2xx. The
735
737
  ``status_code`` attribute lets callers map to specific
736
738
  suggestions.
737
739
  HomeAssistantConnectionError: Network, timeout, or transport error.
@@ -775,25 +777,28 @@ class HomeAssistantClient:
775
777
 
776
778
  ``path`` is everything between ``http://supervisor/`` and ``/logs``:
777
779
 
778
- - ``"addons/<slug>"`` for add-on container logs
780
+ - ``"addons/<slug>"`` for app (add-on) container logs
779
781
  - ``"<service>"`` (where service ∈ {supervisor, host, core, dns, audio,
780
782
  cli, multicast, observer}) for system-service logs
781
783
 
782
784
  ``lines`` maps to the endpoint's ``?lines=`` journald-window query
783
785
  param; omitted → Supervisor's 100-line default window.
784
786
 
785
- Bypasses ``HomeAssistantClient.httpx_client`` because the Supervisor
786
- endpoint uses a different base URL (``http://supervisor``) and a
787
- different token (``SUPERVISOR_TOKEN``) than HA Core REST. Both
788
- endpoints require the addon's ``hassio_role`` to be ``manager`` (not
789
- ``default``); a ``default`` role gets a 403 here — see #1116.
787
+ Bypasses ``HomeAssistantClient.httpx_client`` because that client targets
788
+ Home Assistant Core through ``http://supervisor/core/api``, while logs
789
+ belong to Supervisor at ``http://supervisor``. Both clients use the same
790
+ ``SUPERVISOR_TOKEN``; the Core proxy requires ``homeassistant_api``, while
791
+ system-service and arbitrary app-log paths require ``hassio_api`` and
792
+ ``hassio_role: manager``. The recognized-app-token exception is
793
+ ``/addons/self/logs``.
790
794
 
791
795
  Raises:
792
796
  HomeAssistantAuthError: ``SUPERVISOR_TOKEN`` absent at call time,
793
797
  or 401 from Supervisor.
794
- HomeAssistantAPIError: 403 (role too low — distinct branch with
795
- role hint), 404, other 4xx/5xx. Tries to parse Supervisor's
796
- ``{"result":"error","message":"..."}`` JSON envelope before
798
+ HomeAssistantAPIError: 403 (unrecognized token, missing
799
+ ``hassio_api``, or insufficient role), 404, other 4xx/5xx.
800
+ Parses Supervisor's ``{"result":"error","message":"..."}``
801
+ JSON envelope before
797
802
  falling back to text body / reason phrase / placeholder.
798
803
  HomeAssistantConnectionError: Timeout or transport error, with
799
804
  distinct messages so callers can tell them apart.
@@ -839,18 +844,19 @@ class HomeAssistantClient:
839
844
  if response.status_code == 401:
840
845
  raise HomeAssistantAuthError(f"Invalid Supervisor token for /{path}/logs")
841
846
  if response.status_code == 403:
842
- # Distinct from 401: token is valid but addon's hassio_role isn't
843
- # high enough. Most-likely cause for this exact endpoint at the
844
- # time #1116 surfaced (default → manager bump in addon config.yaml
845
- # is the same-PR companion fix).
847
+ # Supervisor uses 403 for an unrecognized app token, missing
848
+ # hassio_api permission, or a hassio_role that cannot access this
849
+ # endpoint. The default-to-manager role bump fixed #1116, but it is
850
+ # not the only possible cause.
846
851
  logger.warning(
847
- "Supervisor returned 403 for /%s/logs — addon hassio_role may "
848
- "be too low (need 'manager')",
852
+ "Supervisor returned 403 for /%s/logs — check token, hassio_api, "
853
+ "and hassio_role (need 'manager')",
849
854
  path,
850
855
  )
851
856
  raise HomeAssistantAPIError(
852
- f"Supervisor forbids /{path}/logs (403) — addon's hassio_role "
853
- "may be 'default'; need 'manager' or higher",
857
+ f"Supervisor forbids /{path}/logs (403) — token may be unrecognized, "
858
+ "app may lack hassio_api, or hassio_role may not allow this endpoint "
859
+ "(manager required)",
854
860
  status_code=403,
855
861
  response_data={"path": path},
856
862
  )
@@ -873,14 +879,15 @@ class HomeAssistantClient:
873
879
  async def _get_addon_logs_via_supervisor(
874
880
  self, slug: str, lines: int | None = None
875
881
  ) -> str:
876
- """Fetch add-on container logs directly from Supervisor's REST API.
882
+ """Fetch app (add-on) container logs directly from Supervisor REST.
877
883
 
878
- Distinct from ``tools_bug_report._fetch_addon_logs``: that helper is
879
- hardcoded to ``/addons/self/logs`` and silently swallows failures
880
- (it's an aux-data fetch for bug reports, fine to skip on error). This
881
- helper takes arbitrary slugs and surfaces failures as exceptions
882
- because callers (``ha_get_logs(source="supervisor", slug=...)``) need
883
- them. Both endpoints require ``hassio_role: manager``.
884
+ Distinct from ``tools_bug_report._fetch_addon_logs``: that auxiliary
885
+ helper uses the app self-service path ``/addons/self/logs`` and may skip
886
+ failures. This helper takes arbitrary slugs and surfaces failures because
887
+ callers (``ha_get_logs(source="supervisor", slug=...)``) need them.
888
+ Arbitrary app-log slugs require a recognized app token with ``hassio_api``
889
+ and ``hassio_role: manager``; ``/addons/self/logs`` bypasses the role and
890
+ API-permission checks but still requires a recognized app token.
884
891
 
885
892
  Delegates to ``_supervisor_logs_get`` so error handling stays in
886
893
  lockstep with ``_get_system_service_logs``.
@@ -895,26 +902,21 @@ class HomeAssistantClient:
895
902
  ``service`` must be one of the eight Supervisor-managed services:
896
903
  ``supervisor``, ``host``, ``core``, ``dns``, ``audio``, ``cli``,
897
904
  ``multicast``, ``observer``. Caller is responsible for validating
898
- ``service`` against the allowed set; this helper does no validation
899
- and will raise ``HomeAssistantAPIError`` on any unknown path (404).
905
+ ``service``; this helper performs no validation, and unsupported paths
906
+ are rejected by the selected direct-Supervisor or Core-proxy route.
900
907
 
901
908
  Branch on ``is_running_in_addon()`` — mirror of ``get_addon_logs``:
902
- inside the addon container goes directly to Supervisor at
909
+ inside the app (add-on) container goes directly to Supervisor at
903
910
  ``http://supervisor/{service}/logs`` with the Supervisor token
904
- (``hassio_role: manager`` required). On non-addon installs (Docker
911
+ (``hassio_api`` and ``hassio_role: manager`` required). On non-app
912
+ installs (Docker
905
913
  without Supervisor, pyinstaller, pip pointing at a normal HA URL),
906
914
  falls back to the HA Core proxy at ``/api/hassio/{service}/logs``.
907
915
 
908
- All seven slugs are whitelisted in HA Core's hassio proxy
916
+ All eight service slugs are whitelisted in HA Core's hassio proxy
909
917
  (``homeassistant/components/hassio/http.py`` — ``PATHS_ADMIN``), so
910
918
  an admin LLA is sufficient to reach any of them from outside the
911
- addon.
912
-
913
- Closes #1260: pre-fix this method had only the addon-direct branch,
914
- so non-addon installs (the Docker image, uvx ha-mcp, etc.) hit the
915
- ``SUPERVISOR_TOKEN``-absent fail-fast in ``_supervisor_logs_get`` for
916
- every service, while the sibling ``source="supervisor"`` (addon
917
- logs) call kept working through its own Core-proxy fallback.
919
+ app.
918
920
  """
919
921
  if is_running_in_addon():
920
922
  return await self._supervisor_logs_get(service, lines=lines)
@@ -1,20 +1,14 @@
1
1
  """Shared factory for direct-Supervisor httpx clients.
2
2
 
3
- Three call sites in the codebase talk directly to the Home Assistant
4
- Supervisor REST API at ``http://supervisor`` rather than through
5
- ``HomeAssistantClient.httpx_client`` (which is bound to HA Core, not the
6
- Supervisor — different base URL, different token, different role gate):
3
+ Several code paths call the Home Assistant Supervisor REST API directly with
4
+ a short-lived client configured for the Supervisor base URL and
5
+ ``SUPERVISOR_TOKEN``. Callers cover log collection, app (add-on) management
6
+ and restart, backup operations, and screenshot-engine discovery.
7
7
 
8
- - :meth:`ha_mcp.client.rest_client.HomeAssistantClient._supervisor_logs_get`
9
- — fetches addon and system-service logs
10
- - :func:`ha_mcp.tools.tools_bug_report._fetch_addon_logs` — bundles ha-mcp's
11
- own addon logs into a bug-report payload
12
- - :func:`ha_mcp.settings_ui._handlers_server._restart_addon` — POSTs
13
- ``/addons/self/restart`` from the settings UI
14
-
15
- All three share the same boilerplate (base URL, ``Authorization: Bearer
16
- ${SUPERVISOR_TOKEN}`` header), so this module supplies a single factory and
17
- keeps the three sites consistent.
8
+ These calls cannot use ``HomeAssistantClient.httpx_client``, which targets
9
+ Home Assistant Core rather than Supervisor. In app mode it carries the same
10
+ token value, but requests use a different base URL and authorization surface.
11
+ This module centralizes the direct transport setup.
18
12
  """
19
13
 
20
14
  from __future__ import annotations
@@ -56,14 +50,11 @@ def make_supervisor_httpx_client(
56
50
 
57
51
  Raises:
58
52
  RuntimeError: ``SUPERVISOR_TOKEN`` is unset or empty in the
59
- environment. Each call site has its own absent-token policy
60
- (a rich :class:`HomeAssistantAuthError`, a silent ``""``
61
- return, or a 400 ``JSONResponse``) that does not share a
62
- common shape, so the factory cannot translate. Detecting the
63
- absence at construction time prevents a malformed
64
- ``Authorization: Bearer `` header from being read as a token
65
- rejection by Supervisor, which would mask the missing-env-var
66
- root cause.
53
+ environment. Callers must either gate construction on token
54
+ presence or translate this exception for their response surface.
55
+ Detecting it here prevents a malformed
56
+ ``Authorization: Bearer `` header from masking the missing
57
+ environment variable as a token rejection from Supervisor.
67
58
 
68
59
  Note:
69
60
  ``SUPERVISOR_TOKEN`` is read from env at construction time and
@@ -0,0 +1,207 @@
1
+ """MCP SDK / fastmcp log-noise filters shared across every HTTP launcher.
2
+
3
+ Extracted from :mod:`ha_mcp.__main__` for the same reason as
4
+ :mod:`ha_mcp.browser_landing`: the in-process server (the ``ha_mcp_tools``
5
+ custom-component worker thread) must never import ``ha_mcp.__main__``, since
6
+ that module runs process-global side effects at import time
7
+ (``truststore.inject_into_ssl()``, signal handlers, ``asyncio.run``). These
8
+ filters only call ``logging.Logger.addFilter`` on specific named loggers --
9
+ no ``basicConfig``, no handler or root-logger changes -- so
10
+ ``install_sdk_log_filters()`` is safe to call from any launcher, including
11
+ one that must leave Home Assistant's own logging configuration untouched
12
+ (see the ``log_config=None`` comment in ``embedded_server.py``).
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import logging
18
+
19
+ import anyio
20
+ from fastmcp.exceptions import ToolError
21
+ from pydantic import ValidationError as PydanticValidationError
22
+
23
+
24
+ class StatelessSessionLogFilter(logging.Filter):
25
+ """Suppress the routine 'Terminating session: None' log from the MCP SDK.
26
+
27
+ In stateless HTTP mode every request creates and tears down a temporary
28
+ session whose id is ``None``, so the SDK emits an INFO
29
+ ``Terminating session: None`` (mcp/server/streamable_http.py) on *every*
30
+ request. The line is routine but looks alarming and has repeatedly
31
+ confused users into thinking the connection is broken.
32
+
33
+ Returning ``False`` drops the record at this logger before it reaches any
34
+ handler. (Merely downgrading the level to DEBUG did not work: the level
35
+ gate is applied before the filter runs, so the record was already admitted
36
+ and still emitted -- just relabelled.) Real session terminations carry an
37
+ actual id and are not matched, so they still log.
38
+
39
+ # TODO: remove when modelcontextprotocol/python-sdk#2329 is resolved
40
+ """
41
+
42
+ def filter(self, record: logging.LogRecord) -> bool:
43
+ """Drop the routine stateless-teardown record; pass everything else."""
44
+ if record.name != "mcp.server.streamable_http":
45
+ return True
46
+ try:
47
+ message = record.getMessage()
48
+ except (ValueError, TypeError):
49
+ # A malformed %-format record on this logger is not our target, and
50
+ # a filter must not raise: filters run in Logger.handle() with no
51
+ # exception handling, so a raise would crash the logging call.
52
+ return True
53
+ # Drop the stateless teardown noise; keep everything else.
54
+ return "Terminating session: None" not in message
55
+
56
+
57
+ class ToolValidationLogFilter(logging.Filter):
58
+ """Demote fastmcp tool-failure tracebacks to single-line warnings.
59
+
60
+ Pydantic ValidationError and tool-raised ToolError aren't server bugs,
61
+ so the traceback through fastmcp/pydantic internals is just noise. The
62
+ structured error detail is preserved in the WARNING message; stack is
63
+ intentionally dropped because these are user-input errors, not bugs.
64
+ """
65
+
66
+ def filter(self, record: logging.LogRecord) -> bool:
67
+ """Demote a known-benign validation/tool-error record to WARNING."""
68
+ if record.name != "fastmcp.server.server" or not record.exc_info:
69
+ return True
70
+
71
+ msg = record.getMessage()
72
+ err = record.exc_info[1]
73
+ if "Error validating tool" in msg and isinstance(err, PydanticValidationError):
74
+ record.msg = f"{msg}: {err.errors(include_url=False)}"
75
+ elif "Error calling tool" in msg and isinstance(err, ToolError):
76
+ record.msg = f"{msg}: {err}"
77
+ else:
78
+ return True
79
+
80
+ record.args = ()
81
+ record.levelno = logging.WARNING
82
+ record.levelname = "WARNING"
83
+ record.exc_info = None
84
+ record.exc_text = None
85
+ return True
86
+
87
+
88
+ _DISCONNECT_TEARDOWN_ERRORS = (anyio.ClosedResourceError, anyio.BrokenResourceError)
89
+
90
+
91
+ def _is_only_disconnect_teardown_errors(err: BaseException) -> bool:
92
+ """True if ``err`` is (or an ExceptionGroup wrapping only) a benign
93
+ streamable-HTTP memory-stream teardown error.
94
+
95
+ ``mcp.server.lowlevel.server.Server.run()`` dispatches each incoming
96
+ message via ``anyio.create_task_group().start_soon(...)``, so an error
97
+ raised while delivering one message's response is raised from a
98
+ task-group child -- anyio always wraps that in an ``ExceptionGroup``,
99
+ even for a single failure. The exception a "session crashed" log record
100
+ carries is therefore ``ExceptionGroup(...[<teardown error>])``, not a
101
+ bare one.
102
+
103
+ Both ``ClosedResourceError`` and ``BrokenResourceError`` -- sibling
104
+ ``Exception`` subclasses, neither a subclass of the other -- can surface
105
+ here for the same disconnect race: which one depends on which end
106
+ closed first (our end closing raises ``Closed``; the peer's
107
+ ``streamable_http`` ``terminate()`` closing first can wake a parked
108
+ sender into raising ``Broken`` instead, even though the situation is
109
+ identical from the caller's perspective). This repo already treats the
110
+ two as an equivalent pair for streamable-HTTP memory-stream teardown
111
+ elsewhere -- see ``tests/src/haos_runtime.py``'s transient-error tuple.
112
+
113
+ Recurse through (possibly nested) groups; any other leaf type means
114
+ this is not the known-benign disconnect race, so the caller should
115
+ leave the record alone.
116
+ """
117
+ if isinstance(err, _DISCONNECT_TEARDOWN_ERRORS):
118
+ return True
119
+ if isinstance(err, BaseExceptionGroup):
120
+ return bool(err.exceptions) and all(
121
+ _is_only_disconnect_teardown_errors(sub) for sub in err.exceptions
122
+ )
123
+ return False
124
+
125
+
126
+ class SessionDisconnectLogFilter(logging.Filter):
127
+ """Demote 'session crashed' tracebacks caused by an already-gone client.
128
+
129
+ Every HTTP entry point runs Streamable HTTP in stateless mode (see
130
+ ``ha_mcp.__main__._http_run_kwargs``). A tool call slow enough to outlast
131
+ the client's patience -- a busy Home Assistant instance, a
132
+ resource-contended local LLM host on the client side, or an ordinary HTTP
133
+ timeout -- lets the SDK finish serving the response and tear the
134
+ transport down while ``app.run()`` is still working; the eventual attempt
135
+ to deliver the response then writes into an already-closed memory stream
136
+ and raises ``anyio.ClosedResourceError`` or ``anyio.BrokenResourceError``
137
+ (wrapped in an ``ExceptionGroup`` -- see
138
+ ``_is_only_disconnect_teardown_errors``). The SDK already catches this
139
+ (``except Exception: logger.exception(...)`` in both the stateless and
140
+ stateful session runners of mcp/server/streamable_http_manager.py) -- it
141
+ just logs it as an alarming ERROR-level traceback. That's an expected
142
+ race in a stateless HTTP protocol (the client already gave up), not a
143
+ server bug, so demote it the same way ToolValidationLogFilter demotes
144
+ other known-benign failures. Any other exception on this logger -- an
145
+ actual crash -- is left untouched.
146
+ """
147
+
148
+ def filter(self, record: logging.LogRecord) -> bool:
149
+ """Demote a disconnect-caused 'session crashed' record to WARNING."""
150
+ if record.name != "mcp.server.streamable_http_manager" or not record.exc_info:
151
+ return True
152
+
153
+ err = record.exc_info[1]
154
+ if err is None or not _is_only_disconnect_teardown_errors(err):
155
+ return True
156
+
157
+ record.msg = (
158
+ f"{record.getMessage()}: client disconnected before response delivery"
159
+ )
160
+ record.args = ()
161
+ record.levelno = logging.WARNING
162
+ record.levelname = "WARNING"
163
+ record.exc_info = None
164
+ record.exc_text = None
165
+ return True
166
+
167
+
168
+ def _add_filter_once(logger_name: str, filter_cls: type[logging.Filter]) -> None:
169
+ """Attach one ``filter_cls`` instance to ``logger_name``, replacing any stale one.
170
+
171
+ ``install_sdk_log_filters()`` can run more than once per process: the
172
+ in-process embedded server calls it on every ``_serve()`` reload without
173
+ a process restart, and process-wide ``logging`` state (including each
174
+ named logger's filter list) persists across those reloads. Each reload
175
+ also re-imports this module from scratch (the embedded server purges
176
+ ``ha_mcp.*`` from ``sys.modules`` before reinstalling -- see
177
+ ``_purge_ha_mcp_modules``), so ``filter_cls`` is a BRAND NEW class object
178
+ each time, even though its name is unchanged. A same-``isinstance``-check
179
+ against a filter instance from a PREVIOUS generation would therefore
180
+ never match, letting filters accumulate one more stale instance per
181
+ reload forever. Match by ``(module, qualname)`` instead -- stable across
182
+ reloads of the same module path -- and drop every stale-generation match
183
+ before attaching the current one, so a logger never carries more than
184
+ one filter of a given conceptual type, and it's always this generation's.
185
+ """
186
+ logger = logging.getLogger(logger_name)
187
+ identity = (filter_cls.__module__, filter_cls.__qualname__)
188
+ logger.filters[:] = [
189
+ f
190
+ for f in logger.filters
191
+ if (type(f).__module__, type(f).__qualname__) != identity
192
+ ]
193
+ logger.addFilter(filter_cls())
194
+
195
+
196
+ def install_sdk_log_filters() -> None:
197
+ """Attach the demotion filters above to their target SDK/fastmcp loggers.
198
+
199
+ Every HTTP launcher must call this: the CLI (``ha_mcp.__main__``), the
200
+ Home Assistant app's ``start.py``, and the in-process embedded server
201
+ (``ha_mcp_tools/embedded_server.py``) each build and run their own
202
+ Streamable HTTP app, so none of them share another launcher's logging
203
+ setup. Safe to call repeatedly -- see ``_add_filter_once``.
204
+ """
205
+ _add_filter_once("mcp.server.streamable_http", StatelessSessionLogFilter)
206
+ _add_filter_once("mcp.server.streamable_http_manager", SessionDisconnectLogFilter)
207
+ _add_filter_once("fastmcp.server.server", ToolValidationLogFilter)