ha-mcp-dev 7.14.1.dev2073__tar.gz → 7.14.2.dev2083__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 (187) hide show
  1. {ha_mcp_dev-7.14.1.dev2073/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.14.2.dev2083}/PKG-INFO +1 -1
  2. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/pyproject.toml +1 -1
  3. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/client/websocket_client.py +94 -18
  4. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/smart_search/_overview.py +32 -60
  5. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
  6. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/LICENSE +0 -0
  7. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/MANIFEST.in +0 -0
  8. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/README.md +0 -0
  9. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/setup.cfg +0 -0
  10. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/__init__.py +0 -0
  11. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/__main__.py +0 -0
  12. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/_pypi_marker +0 -0
  13. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/_version.py +0 -0
  14. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/auth/__init__.py +0 -0
  15. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/auth/consent_form.py +0 -0
  16. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/auth/provider.py +0 -0
  17. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/backup_manager.py +0 -0
  18. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/browser_landing.py +0 -0
  19. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/client/__init__.py +0 -0
  20. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/client/rest_client.py +0 -0
  21. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/client/supervisor_client.py +0 -0
  22. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/client/websocket_listener.py +0 -0
  23. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/config.py +0 -0
  24. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/dashboard_screenshot/__init__.py +0 -0
  25. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/dashboard_screenshot/capture.py +0 -0
  26. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/dashboard_screenshot/content.py +0 -0
  27. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/dashboard_screenshot/paths.py +0 -0
  28. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/dashboard_screenshot/provision.py +0 -0
  29. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/dashboard_screenshot/theme_guard.py +0 -0
  30. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/errors.py +0 -0
  31. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/llm_exposure.py +0 -0
  32. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/policy/__init__.py +0 -0
  33. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/policy/approval_queue.py +0 -0
  34. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/policy/evaluator.py +0 -0
  35. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/policy/handlers.py +0 -0
  36. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/policy/middleware.py +0 -0
  37. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/policy/model.py +0 -0
  38. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/policy/persistence.py +0 -0
  39. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/policy/value_sources.py +0 -0
  40. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/py.typed +0 -0
  41. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/read_only.py +0 -0
  42. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
  43. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
  44. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
  45. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
  46. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
  47. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
  48. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
  49. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
  50. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
  51. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
  52. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
  53. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
  54. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/appdaemon.md +0 -0
  55. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
  56. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/blueprint-guide.md +0 -0
  57. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
  58. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
  59. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
  60. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
  61. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
  62. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
  63. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
  64. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/scenes.md +0 -0
  65. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
  66. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/yaml-only-integrations.md +0 -0
  67. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/server.py +0 -0
  68. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/settings_ui/__init__.py +0 -0
  69. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/settings_ui/_handlers_advanced.py +0 -0
  70. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/settings_ui/_handlers_backups.py +0 -0
  71. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/settings_ui/_handlers_fs.py +0 -0
  72. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/settings_ui/_handlers_server.py +0 -0
  73. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/settings_ui/_handlers_theme.py +0 -0
  74. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/settings_ui/_handlers_tools.py +0 -0
  75. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/settings_ui/_i18n.py +0 -0
  76. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/settings_ui/_persistence.py +0 -0
  77. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/settings_ui/_supervisor.py +0 -0
  78. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/settings_ui/_theme.py +0 -0
  79. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/settings_ui/_tools_meta.py +0 -0
  80. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/settings_ui/locales/README.md +0 -0
  81. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/settings_ui/locales/de.json +0 -0
  82. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/settings_ui/locales/en.json +0 -0
  83. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/settings_ui/locales/ru.json +0 -0
  84. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/settings_ui/settings.css +0 -0
  85. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/settings_ui/settings.html +0 -0
  86. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/settings_ui/settings.js +0 -0
  87. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/smoke_test.py +0 -0
  88. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
  89. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/strict_bps.py +0 -0
  90. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/__init__.py +0 -0
  91. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/auto_backup.py +0 -0
  92. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/backup.py +0 -0
  93. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/best_practice_checker.py +0 -0
  94. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/component_api.py +0 -0
  95. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/component_config_reads.py +0 -0
  96. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/component_devices.py +0 -0
  97. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/component_registries.py +0 -0
  98. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/component_registry_lookup.py +0 -0
  99. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/config_entry_flow.py +0 -0
  100. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/device_control.py +0 -0
  101. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/enhanced.py +0 -0
  102. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/helpers.py +0 -0
  103. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/radio/__init__.py +0 -0
  104. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/radio/base.py +0 -0
  105. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/radio/matter.py +0 -0
  106. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/radio/thread.py +0 -0
  107. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/radio/zigbee.py +0 -0
  108. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/radio/zwave.py +0 -0
  109. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/reference_validator.py +0 -0
  110. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/registry.py +0 -0
  111. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/smart_search/__init__.py +0 -0
  112. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/smart_search/_base.py +0 -0
  113. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/smart_search/_config.py +0 -0
  114. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/smart_search/_deep.py +0 -0
  115. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/smart_search/_entities.py +0 -0
  116. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/smart_search/_fetch.py +0 -0
  117. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/smart_search/_scenes.py +0 -0
  118. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/smart_search/_scoring.py +0 -0
  119. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/tool_search_hint_middleware.py +0 -0
  120. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/tools_addons.py +0 -0
  121. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/tools_areas.py +0 -0
  122. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/tools_blueprints.py +0 -0
  123. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/tools_bug_report.py +0 -0
  124. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/tools_calendar.py +0 -0
  125. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/tools_camera.py +0 -0
  126. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/tools_categories.py +0 -0
  127. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/tools_code.py +0 -0
  128. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/tools_config_automations.py +0 -0
  129. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
  130. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
  131. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
  132. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
  133. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/tools_dashboard_screenshot.py +0 -0
  134. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/tools_dev.py +0 -0
  135. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/tools_energy.py +0 -0
  136. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/tools_entities.py +0 -0
  137. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/tools_filesystem.py +0 -0
  138. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/tools_groups.py +0 -0
  139. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/tools_hacs.py +0 -0
  140. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/tools_history.py +0 -0
  141. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/tools_integrations.py +0 -0
  142. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/tools_labels.py +0 -0
  143. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/tools_radio.py +0 -0
  144. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/tools_registry.py +0 -0
  145. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/tools_resources.py +0 -0
  146. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/tools_search.py +0 -0
  147. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/tools_service.py +0 -0
  148. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/tools_services.py +0 -0
  149. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/tools_system.py +0 -0
  150. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/tools_themes.py +0 -0
  151. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/tools_todo.py +0 -0
  152. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/tools_traces.py +0 -0
  153. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/tools_updates.py +0 -0
  154. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/tools_utility.py +0 -0
  155. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
  156. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
  157. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/tools_yaml_read.py +0 -0
  158. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/tools_zones.py +0 -0
  159. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/util_helpers.py +0 -0
  160. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/tools/validation_middleware.py +0 -0
  161. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/transforms/__init__.py +0 -0
  162. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/transforms/categorized_search.py +0 -0
  163. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
  164. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/transport_security.py +0 -0
  165. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/update_check.py +0 -0
  166. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/utils/__init__.py +0 -0
  167. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/utils/config_hash.py +0 -0
  168. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/utils/data_paths.py +0 -0
  169. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/utils/domain_handlers.py +0 -0
  170. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/utils/fuzzy_search.py +0 -0
  171. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
  172. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/utils/operation_manager.py +0 -0
  173. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/utils/python_sandbox.py +0 -0
  174. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/utils/skill_loader.py +0 -0
  175. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/utils/usage_logger.py +0 -0
  176. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/visibility/__init__.py +0 -0
  177. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/visibility/model.py +0 -0
  178. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/visibility/persistence.py +0 -0
  179. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp/visibility/resolver.py +0 -0
  180. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
  181. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
  182. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
  183. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
  184. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
  185. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/tests/__init__.py +0 -0
  186. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/tests/test_constants.py +0 -0
  187. {ha_mcp_dev-7.14.1.dev2073 → ha_mcp_dev-7.14.2.dev2083}/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: 7.14.1.dev2073
3
+ Version: 7.14.2.dev2083
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 = "7.14.1.dev2073"
7
+ version = "7.14.2.dev2083"
8
8
  description = "Home Assistant MCP Server - Complete control of Home Assistant through MCP"
9
9
  readme = "README.md"
10
10
  # Upper bound widened to <3.15 so the package installs on Home Assistant's
@@ -8,6 +8,7 @@ This module handles WebSocket connections to Home Assistant for:
8
8
  """
9
9
 
10
10
  import asyncio
11
+ import concurrent.futures
11
12
  import hashlib
12
13
  import json
13
14
  import logging
@@ -1036,6 +1037,21 @@ class HomeAssistantWebSocketClient:
1036
1037
  MAX_POOL_SIZE = 50
1037
1038
 
1038
1039
 
1040
+ def _log_stale_disconnect(future: "concurrent.futures.Future[None]") -> None:
1041
+ """Report how a disconnect scheduled on a stale event loop ended.
1042
+
1043
+ Nothing awaits that future, so without this the outcome would be invisible
1044
+ at every log level: a ``concurrent.futures.Future`` never warns about an
1045
+ exception no one retrieved.
1046
+ """
1047
+ if future.cancelled():
1048
+ logger.debug("Stale WebSocket disconnect was cancelled with its loop")
1049
+ return
1050
+ error = future.exception()
1051
+ if error is not None:
1052
+ logger.debug("Stale WebSocket disconnect failed: %s", error)
1053
+
1054
+
1039
1055
  class WebSocketManager:
1040
1056
  """Singleton manager for Home Assistant WebSocket connections.
1041
1057
 
@@ -1114,6 +1130,56 @@ class WebSocketManager:
1114
1130
  )
1115
1131
  return True
1116
1132
 
1133
+ @staticmethod
1134
+ def _release_stale_clients(
1135
+ clients: list[HomeAssistantWebSocketClient],
1136
+ loop: asyncio.AbstractEventLoop | None,
1137
+ ) -> None:
1138
+ """Best-effort cleanup of clients orphaned by an event-loop change.
1139
+
1140
+ Never awaits: the connections belong to ``loop``, and awaiting them
1141
+ from the loop that replaced it is exactly the cross-loop failure this
1142
+ avoids. Each disconnect is scheduled on the owning loop and
1143
+ deliberately not waited for. A merely stopped loop is scheduled too:
1144
+ it still owns live transports and still accepts callbacks, so the
1145
+ disconnect runs once that loop is resumed, and otherwise stays an
1146
+ unrun callback like any other the abandoned loop still holds. Only a
1147
+ closed or unknown loop has nothing left to schedule on; its
1148
+ connection is abandoned and the socket closes when the orphaned
1149
+ transport is garbage-collected (with a ``ResourceWarning``), because
1150
+ closing a loop does not close the transports it carried. Either way
1151
+ the caller has already detached the pool, so a failure here cannot
1152
+ make it stale again.
1153
+ """
1154
+ if not clients:
1155
+ return
1156
+ if loop is None or loop.is_closed():
1157
+ logger.debug(
1158
+ "Abandoning %d stale WebSocket client(s): the owning event "
1159
+ "loop is gone",
1160
+ len(clients),
1161
+ )
1162
+ return
1163
+ for client in clients:
1164
+ coro = client.disconnect()
1165
+ try:
1166
+ future = asyncio.run_coroutine_threadsafe(coro, loop)
1167
+ except RuntimeError:
1168
+ # The loop closed between the check above and now. Closing the
1169
+ # coroutine keeps it from surfacing as "never awaited". The
1170
+ # narrower window stays open by construction: a loop that
1171
+ # accepts the callback and then stops before draining it never
1172
+ # runs the coroutine at all, and ``run_coroutine_threadsafe``
1173
+ # needs the coroutine object up front, so there is nothing left
1174
+ # to reclaim from this side.
1175
+ coro.close()
1176
+ logger.debug(
1177
+ "Could not schedule disconnect of a stale WebSocket client",
1178
+ exc_info=True,
1179
+ )
1180
+ continue
1181
+ future.add_done_callback(_log_stale_disconnect)
1182
+
1117
1183
  async def get_client(
1118
1184
  self,
1119
1185
  url: str | None = None,
@@ -1143,22 +1209,21 @@ class WebSocketManager:
1143
1209
  if not self._lock:
1144
1210
  raise Exception("Lock not initialized")
1145
1211
  async with self._lock:
1146
- if self._current_loop is not None and self._current_loop != current_loop:
1147
- # Event loop changed — disconnect all clients
1148
- for client in self._clients.values():
1149
- try:
1150
- await client.disconnect()
1151
- except (OSError, asyncio.CancelledError):
1152
- # Best-effort cleanup — failure is expected when the
1153
- # event loop changed and connections are stale.
1154
- logger.debug(
1155
- "Ignoring error disconnecting stale WebSocket client",
1156
- exc_info=True,
1157
- )
1212
+ previous_loop = self._current_loop
1213
+ stale_clients: list[HomeAssistantWebSocketClient] = []
1214
+ if previous_loop is not None and previous_loop is not current_loop:
1215
+ # Event loop changed: detach the pool BEFORE cleaning it up.
1216
+ # The pooled clients' futures belong to ``previous_loop``, so
1217
+ # awaiting their ``disconnect()`` here raises ``RuntimeError:
1218
+ # ... attached to a different loop``, which used to escape the
1219
+ # best-effort catch and leave both the pool and the loop
1220
+ # reference stale for every later call (issue #1994).
1221
+ stale_clients = list(self._clients.values())
1158
1222
  self._clients.clear()
1159
1223
  self._last_used.clear()
1160
1224
 
1161
1225
  self._current_loop = current_loop
1226
+ self._release_stale_clients(stale_clients, previous_loop)
1162
1227
 
1163
1228
  # Determine credentials to use
1164
1229
  if url and token:
@@ -1226,7 +1291,7 @@ class WebSocketManager:
1226
1291
  if stale:
1227
1292
  try:
1228
1293
  await stale.disconnect()
1229
- except (OSError, asyncio.CancelledError):
1294
+ except (OSError, RuntimeError, asyncio.CancelledError):
1230
1295
  logger.warning(
1231
1296
  "Error disconnecting evicted WebSocket client",
1232
1297
  exc_info=True,
@@ -1239,16 +1304,27 @@ class WebSocketManager:
1239
1304
  if not self._lock:
1240
1305
  raise Exception("Lock not initialized")
1241
1306
  async with self._lock:
1242
- for client in self._clients.values():
1307
+ # Detach the pool before disconnecting so a raising disconnect
1308
+ # cannot leave it populated (the bookkeeping half of the
1309
+ # loop-change branch in ``get_client``). The only caller,
1310
+ # ``__main__``'s shutdown, runs on the pool's own loop, so each
1311
+ # ``disconnect()`` is awaited to completion here. ``RuntimeError``
1312
+ # is caught purely defensively: were this ever driven from a
1313
+ # different loop, the cross-loop future would raise instead of
1314
+ # hanging the shutdown. Unlike ``get_client`` it does not
1315
+ # reschedule onto the owning loop, because the process is going
1316
+ # away and there is nothing left to resume it.
1317
+ clients = list(self._clients.values())
1318
+ self._clients.clear()
1319
+ self._last_used.clear()
1320
+ self._current_loop = None
1321
+ for client in clients:
1243
1322
  try:
1244
1323
  await client.disconnect()
1245
- except (OSError, asyncio.CancelledError):
1324
+ except (OSError, RuntimeError, asyncio.CancelledError):
1246
1325
  logger.warning(
1247
1326
  "Error disconnecting WebSocket client", exc_info=True
1248
1327
  )
1249
- self._clients.clear()
1250
- self._last_used.clear()
1251
- self._current_loop = None
1252
1328
 
1253
1329
 
1254
1330
  # Global WebSocket manager instance
@@ -3,6 +3,7 @@
3
3
  import asyncio
4
4
  import logging
5
5
  import random
6
+ from itertools import islice
6
7
  from typing import Any
7
8
 
8
9
  from ...visibility.resolver import load_hidden_set
@@ -433,65 +434,41 @@ class SystemOverviewMixin(_SearchBase):
433
434
  return formatted_domain_stats
434
435
 
435
436
  @staticmethod
436
- def _allocate_page_one(
437
+ def _allocate_page(
437
438
  formatted_domain_stats: dict[str, dict[str, Any]],
438
439
  effective_limit: int,
439
- total_entity_count: int,
440
+ offset: int,
440
441
  ) -> int:
441
- """Distribute the page-1 budget: a min allocation per domain, rest proportional.
442
+ """Take one stable round-robin page across domains.
443
+
444
+ A single ordering must drive every page. Otherwise a specially balanced
445
+ first page followed by sequential offset pages can repeat entities.
442
446
 
443
- Gives each domain a minimum slice so the LLM sees entities from every
444
- domain, then distributes the remaining budget proportionally. Mutates
445
- ``formatted_domain_stats`` in place; returns the count included.
447
+ Mutates ``formatted_domain_stats`` in place; returns the count included.
446
448
  """
447
- min_per_domain = 3
448
- num_domains = len(formatted_domain_stats)
449
- reserved = min(min_per_domain * num_domains, effective_limit)
450
- remaining_budget = effective_limit - reserved
451
-
452
- entities_included = 0
453
- for domain_data in formatted_domain_stats.values():
454
- domain_entities = domain_data["entities"]
455
- domain_len = len(domain_entities)
456
- base = min(min_per_domain, domain_len)
457
- if total_entity_count > 0 and remaining_budget > 0:
458
- extra = int(remaining_budget * domain_len / total_entity_count)
459
- else:
460
- extra = 0
461
- take = min(base + extra, domain_len)
462
- if take < domain_len:
463
- domain_data["entities"] = domain_entities[:take]
464
- domain_data["truncated"] = True
465
- entities_included += len(domain_data["entities"])
466
- return entities_included
449
+ max_domain_size = max(
450
+ (len(data["entities"]) for data in formatted_domain_stats.values()),
451
+ default=0,
452
+ )
453
+ ordered = (
454
+ (domain, data["entities"][index])
455
+ for index in range(max_domain_size)
456
+ for domain, data in formatted_domain_stats.items()
457
+ if index < len(data["entities"])
458
+ )
467
459
 
468
- @staticmethod
469
- def _allocate_subsequent_pages(
470
- formatted_domain_stats: dict[str, dict[str, Any]],
471
- effective_limit: int,
472
- offset: int,
473
- ) -> int:
474
- """Apply pages-2+ sequential skip/take across domains. Mutates in place."""
475
- entities_skipped = 0
476
- entities_included = 0
477
- for domain_data in formatted_domain_stats.values():
478
- domain_entities = domain_data["entities"]
479
- domain_len = len(domain_entities)
480
-
481
- skip_from_domain = max(0, min(domain_len, offset - entities_skipped))
482
- budget_left = effective_limit - entities_included
483
- take_from_domain = max(0, min(domain_len - skip_from_domain, budget_left))
484
-
485
- if skip_from_domain > 0 or take_from_domain < domain_len:
486
- domain_data["entities"] = domain_entities[
487
- skip_from_domain : skip_from_domain + take_from_domain
488
- ]
489
- if take_from_domain < domain_len:
490
- domain_data["truncated"] = True
460
+ selected: dict[str, list[dict[str, Any]]] = {
461
+ domain: [] for domain in formatted_domain_stats
462
+ }
463
+ for domain, entity in islice(ordered, offset, offset + effective_limit):
464
+ selected[domain].append(entity)
491
465
 
492
- entities_skipped += skip_from_domain
493
- entities_included += take_from_domain
494
- return entities_included
466
+ for domain, data in formatted_domain_stats.items():
467
+ original_count = len(data["entities"])
468
+ data["entities"] = selected[domain]
469
+ if len(data["entities"]) < original_count:
470
+ data["truncated"] = True
471
+ return sum(len(entities) for entities in selected.values())
495
472
 
496
473
  def _paginate_overview_entities(
497
474
  self,
@@ -514,14 +491,9 @@ class SystemOverviewMixin(_SearchBase):
514
491
  total_entity_count = sum(
515
492
  len(ds["entities"]) for ds in formatted_domain_stats.values()
516
493
  )
517
- if offset == 0:
518
- entities_included = self._allocate_page_one(
519
- formatted_domain_stats, effective_limit, total_entity_count
520
- )
521
- else:
522
- entities_included = self._allocate_subsequent_pages(
523
- formatted_domain_stats, effective_limit, offset
524
- )
494
+ entities_included = self._allocate_page(
495
+ formatted_domain_stats, effective_limit, offset
496
+ )
525
497
 
526
498
  has_more = (offset + entities_included) < total_entity_count
527
499
  return {
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ha-mcp-dev
3
- Version: 7.14.1.dev2073
3
+ Version: 7.14.2.dev2083
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