ha-mcp-dev 7.10.0.dev775__tar.gz → 7.10.0.dev776__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 (159) hide show
  1. {ha_mcp_dev-7.10.0.dev775/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.10.0.dev776}/PKG-INFO +1 -1
  2. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/pyproject.toml +1 -1
  3. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/client/websocket_client.py +72 -13
  4. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/smart_search/_entities.py +80 -26
  5. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
  6. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/LICENSE +0 -0
  7. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/MANIFEST.in +0 -0
  8. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/README.md +0 -0
  9. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/setup.cfg +0 -0
  10. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/__init__.py +0 -0
  11. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/__main__.py +0 -0
  12. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/_pypi_marker +0 -0
  13. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/_version.py +0 -0
  14. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/auth/__init__.py +0 -0
  15. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/auth/consent_form.py +0 -0
  16. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/auth/provider.py +0 -0
  17. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/backup_manager.py +0 -0
  18. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/client/__init__.py +0 -0
  19. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/client/rest_client.py +0 -0
  20. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/client/supervisor_client.py +0 -0
  21. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/client/websocket_listener.py +0 -0
  22. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/config.py +0 -0
  23. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/dashboard_screenshot/__init__.py +0 -0
  24. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/dashboard_screenshot/capture.py +0 -0
  25. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/dashboard_screenshot/provision.py +0 -0
  26. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/errors.py +0 -0
  27. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/policy/__init__.py +0 -0
  28. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/policy/approval_queue.py +0 -0
  29. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/policy/evaluator.py +0 -0
  30. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/policy/handlers.py +0 -0
  31. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/policy/middleware.py +0 -0
  32. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/policy/model.py +0 -0
  33. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/policy/persistence.py +0 -0
  34. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/policy/value_sources.py +0 -0
  35. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/py.typed +0 -0
  36. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/read_only.py +0 -0
  37. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
  38. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
  39. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
  40. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
  41. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
  42. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
  43. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
  44. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
  45. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
  46. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
  47. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
  48. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
  49. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/appdaemon.md +0 -0
  50. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
  51. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
  52. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
  53. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
  54. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
  55. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
  56. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
  57. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
  58. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/scenes.md +0 -0
  59. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
  60. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/yaml-only-integrations.md +0 -0
  61. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/server.py +0 -0
  62. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/settings_ui/__init__.py +0 -0
  63. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/settings_ui/settings.css +0 -0
  64. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/settings_ui/settings.html +0 -0
  65. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/settings_ui/settings.js +0 -0
  66. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/smoke_test.py +0 -0
  67. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
  68. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/__init__.py +0 -0
  69. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/auto_backup.py +0 -0
  70. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/backup.py +0 -0
  71. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/best_practice_checker.py +0 -0
  72. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/config_entry_flow.py +0 -0
  73. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/device_control.py +0 -0
  74. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/enhanced.py +0 -0
  75. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/helpers.py +0 -0
  76. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/radio/__init__.py +0 -0
  77. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/radio/base.py +0 -0
  78. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/radio/matter.py +0 -0
  79. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/radio/thread.py +0 -0
  80. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/radio/zigbee.py +0 -0
  81. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/radio/zwave.py +0 -0
  82. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/reference_validator.py +0 -0
  83. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/registry.py +0 -0
  84. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/smart_search/__init__.py +0 -0
  85. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/smart_search/_base.py +0 -0
  86. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/smart_search/_config.py +0 -0
  87. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/smart_search/_deep.py +0 -0
  88. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/smart_search/_fetch.py +0 -0
  89. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/smart_search/_overview.py +0 -0
  90. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/smart_search/_scenes.py +0 -0
  91. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/smart_search/_scoring.py +0 -0
  92. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/tool_search_hint_middleware.py +0 -0
  93. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/tools_addons.py +0 -0
  94. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/tools_areas.py +0 -0
  95. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/tools_blueprints.py +0 -0
  96. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/tools_bug_report.py +0 -0
  97. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/tools_calendar.py +0 -0
  98. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/tools_camera.py +0 -0
  99. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/tools_categories.py +0 -0
  100. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/tools_code.py +0 -0
  101. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/tools_config_automations.py +0 -0
  102. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
  103. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
  104. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
  105. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
  106. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/tools_dashboard_screenshot.py +0 -0
  107. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/tools_energy.py +0 -0
  108. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/tools_entities.py +0 -0
  109. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/tools_filesystem.py +0 -0
  110. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/tools_groups.py +0 -0
  111. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/tools_hacs.py +0 -0
  112. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/tools_history.py +0 -0
  113. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/tools_integrations.py +0 -0
  114. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/tools_labels.py +0 -0
  115. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/tools_mcp_component.py +0 -0
  116. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/tools_radio.py +0 -0
  117. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/tools_registry.py +0 -0
  118. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/tools_resources.py +0 -0
  119. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/tools_search.py +0 -0
  120. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/tools_service.py +0 -0
  121. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/tools_services.py +0 -0
  122. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/tools_system.py +0 -0
  123. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/tools_themes.py +0 -0
  124. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/tools_todo.py +0 -0
  125. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/tools_traces.py +0 -0
  126. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/tools_updates.py +0 -0
  127. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/tools_utility.py +0 -0
  128. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
  129. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
  130. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/tools_zones.py +0 -0
  131. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/util_helpers.py +0 -0
  132. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/tools/validation_middleware.py +0 -0
  133. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/transforms/__init__.py +0 -0
  134. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/transforms/categorized_search.py +0 -0
  135. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
  136. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/transport_security.py +0 -0
  137. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/update_check.py +0 -0
  138. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/utils/__init__.py +0 -0
  139. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/utils/config_hash.py +0 -0
  140. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/utils/data_paths.py +0 -0
  141. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/utils/domain_handlers.py +0 -0
  142. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/utils/fuzzy_search.py +0 -0
  143. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
  144. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/utils/operation_manager.py +0 -0
  145. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/utils/python_sandbox.py +0 -0
  146. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/utils/skill_loader.py +0 -0
  147. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/utils/usage_logger.py +0 -0
  148. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/visibility/__init__.py +0 -0
  149. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/visibility/model.py +0 -0
  150. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/visibility/persistence.py +0 -0
  151. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp/visibility/resolver.py +0 -0
  152. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
  153. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
  154. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
  155. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
  156. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
  157. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/tests/__init__.py +0 -0
  158. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/tests/test_constants.py +0 -0
  159. {ha_mcp_dev-7.10.0.dev775 → ha_mcp_dev-7.10.0.dev776}/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.10.0.dev775
3
+ Version: 7.10.0.dev776
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.10.0.dev775"
7
+ version = "7.10.0.dev776"
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
@@ -30,6 +30,15 @@ from .rest_client import (
30
30
 
31
31
  logger = logging.getLogger(__name__)
32
32
 
33
+ # Matches the Supervisor's own Core-connection receive limit
34
+ # (MAX_MESSAGE_SIZE_FROM_CORE in home-assistant/supervisor, see supervisor
35
+ # issue #4392). On the add-on path frames above that limit die at the
36
+ # Supervisor proxy anyway, so a larger client-side cap adds nothing there.
37
+ # Registry list responses scale with entity count and arrive as ONE frame
38
+ # (the HA WebSocket API has no pagination); a ~6.4k-entity instance
39
+ # overflowed the previous 20MB cap (#1721).
40
+ MAX_WS_MESSAGE_BYTES = 64 * 1024 * 1024
41
+
33
42
 
34
43
  class WebSocketConnectionState:
35
44
  """Encapsulates mutable state used by the WebSocket client."""
@@ -109,20 +118,43 @@ class WebSocketConnectionState:
109
118
  """Retrieve and remove an authentication message if present."""
110
119
  return self._auth_messages.pop(message_type, None)
111
120
 
112
- def reset_connection(self) -> None:
113
- """Reset connection-specific state while preserving handlers."""
121
+ def reset_connection(self, close_reason: str | None = None) -> None:
122
+ """Reset connection-specific state while preserving handlers.
123
+
124
+ Args:
125
+ close_reason: Human-readable description of why the connection
126
+ went away (e.g. a close code/reason pair), included in the
127
+ error surfaced to any request still awaiting a response.
128
+ """
114
129
  self.connected = False
115
130
  self.authenticated = False
116
131
  self._message_id = 0
117
132
 
133
+ # ``future.cancel()`` makes awaiters see ``asyncio.CancelledError``,
134
+ # a BaseException that skips every ``except Exception`` handler in
135
+ # the tool layer and reaches the MCP SDK, which treats it as a
136
+ # client-initiated cancellation, suppresses the response entirely,
137
+ # and leaves the MCP client hanging until its own timeout (#1721).
138
+ # ``set_exception`` with a normal exception propagates through
139
+ # ``except Exception`` as expected instead.
140
+ message = (
141
+ "WebSocket connection to Home Assistant closed while waiting for a response"
142
+ )
143
+ if close_reason:
144
+ message = f"{message} ({close_reason})"
145
+
118
146
  for future in self._pending_requests.values():
119
147
  if not future.done():
120
- future.cancel()
148
+ # A fresh instance per future: sharing one exception object
149
+ # across multiple ``set_exception`` calls means the second
150
+ # raise attaches a traceback to an exception already
151
+ # associated with another future's stack.
152
+ future.set_exception(HomeAssistantConnectionError(message))
121
153
  self._pending_requests.clear()
122
154
 
123
155
  for future in self._event_responses.values():
124
156
  if not future.done():
125
- future.cancel()
157
+ future.set_exception(HomeAssistantConnectionError(message))
126
158
  self._event_responses.clear()
127
159
 
128
160
  # Drop any subscription queues — readers wake on the close signal
@@ -144,9 +176,9 @@ class WebSocketConnectionState:
144
176
  """Mark the socket as authenticated and ready for commands."""
145
177
  self.authenticated = True
146
178
 
147
- def mark_disconnected(self) -> None:
179
+ def mark_disconnected(self, close_reason: str | None = None) -> None:
148
180
  """Reset connection state when the socket is closed."""
149
- self.reset_connection()
181
+ self.reset_connection(close_reason)
150
182
 
151
183
  @property
152
184
  def is_ready(self) -> bool:
@@ -293,9 +325,7 @@ class HomeAssistantWebSocketClient:
293
325
  ping_timeout=10,
294
326
  additional_headers={"Authorization": f"Bearer {self.token}"},
295
327
  ssl=ssl_ctx,
296
- # Increase max message size to 20MB for large responses
297
- # (e.g., HACS repository list can be 2MB+)
298
- max_size=20 * 1024 * 1024,
328
+ max_size=MAX_WS_MESSAGE_BYTES,
299
329
  )
300
330
  self._state.mark_connected()
301
331
 
@@ -390,6 +420,11 @@ class HomeAssistantWebSocketClient:
390
420
  """Background task to handle incoming WebSocket messages."""
391
421
  if not self.websocket:
392
422
  raise Exception("WebSocket not connected")
423
+ # None means a clean exit (the async-for loop simply ended); the
424
+ # except blocks below fill this in so pending futures — and the
425
+ # log — carry *why* the connection went away instead of a bare
426
+ # "WebSocket connection closed" that gave no lead on #1721.
427
+ close_reason: str | None = None
393
428
  try:
394
429
  async for message in self.websocket:
395
430
  try:
@@ -400,12 +435,30 @@ class HomeAssistantWebSocketClient:
400
435
  logger.error(f"Invalid JSON received: {e}")
401
436
  except Exception as e:
402
437
  logger.error(f"Error processing message: {e}")
403
- except websockets.exceptions.ConnectionClosed:
404
- logger.info("WebSocket connection closed")
438
+ except websockets.exceptions.ConnectionClosed as e:
439
+ # Prefer the frame we received (the peer closed on us); fall
440
+ # back to the frame we sent (we failed the connection
441
+ # ourselves, e.g. an over-max_size frame produces a sent
442
+ # Close(1009, ...) with no frame received at all).
443
+ close = e.rcvd if e.rcvd is not None else e.sent
444
+ if close is not None:
445
+ verb = "received" if e.rcvd is not None else "sent"
446
+ close_reason = f"{verb} close code {close.code}"
447
+ if close.reason:
448
+ close_reason = f"{close_reason} ({close.reason})"
449
+ else:
450
+ close_reason = "connection dropped without a close frame"
451
+
452
+ log_message = f"WebSocket connection closed ({close_reason})"
453
+ if close is None or close.code not in (1000, 1001):
454
+ logger.warning(log_message)
455
+ else:
456
+ logger.info(log_message)
405
457
  except Exception as e:
458
+ close_reason = str(e)
406
459
  logger.error(f"WebSocket message handler error: {e}")
407
460
  finally:
408
- self._state.mark_disconnected()
461
+ self._state.mark_disconnected(close_reason)
409
462
 
410
463
  async def _process_message(self, data: dict[str, Any]) -> None:
411
464
  """Process incoming WebSocket message."""
@@ -649,9 +702,15 @@ class HomeAssistantWebSocketClient:
649
702
  result_response = await asyncio.wait_for(
650
703
  result_future, timeout=wait_timeout
651
704
  )
652
- except TimeoutError:
705
+ except BaseException:
653
706
  self.cancel_pending_response(message_id)
654
707
  self.cancel_event_response(message_id)
708
+ # A connection drop fails BOTH futures via reset_connection;
709
+ # only result_future gets awaited on this path, so retrieve
710
+ # event_future's exception too or asyncio logs an ERROR-level
711
+ # "Future exception was never retrieved" when it is GC'd.
712
+ if event_future.done() and not event_future.cancelled():
713
+ event_future.exception()
655
714
  raise
656
715
 
657
716
  if not result_response.get("success"):
@@ -12,6 +12,11 @@ from ._base import _SearchBase
12
12
 
13
13
  logger = logging.getLogger(__name__)
14
14
 
15
+ # Bounds the per-frame response size of config/entity_registry/get_entries
16
+ # (extended entries, ~1KB typical each) so alias enrichment can't produce an
17
+ # over-cap WebSocket frame on large instances (#1721).
18
+ _GET_ENTRIES_CHUNK_SIZE = 500
19
+
15
20
 
16
21
  class EntitySearchMixin(_SearchBase):
17
22
  """``smart_entity_search`` and ``get_entities_by_area`` plus helpers."""
@@ -144,41 +149,89 @@ class EntitySearchMixin(_SearchBase):
144
149
 
145
150
  async def _fetch_entity_aliases(
146
151
  self, survivor_ids: list[str]
147
- ) -> dict[str, list[str]]:
152
+ ) -> tuple[dict[str, list[str]], list[str]]:
148
153
  """Batch-fetch full registry entries for aliases.
149
154
 
150
155
  ``config/entity_registry/list`` deliberately omits ``aliases``;
151
- ``get_entries`` includes them. One extra round-trip enriches the
152
- survivor set without N+1 fan-out.
156
+ ``get_entries`` includes them. Survivors are split into
157
+ ``_GET_ENTRIES_CHUNK_SIZE``-sized chunks fetched concurrently: a
158
+ bounded number of round-trips (one per chunk), not N+1 fan-out, and
159
+ each response frame stays bounded regardless of instance size.
160
+ Enrichment is best-effort per chunk — one chunk failing does not
161
+ drop aliases fetched by the others; failures are reported in the
162
+ returned warnings so the caller can tell "no aliases exist" apart
163
+ from "the alias fetch failed".
164
+
165
+ Returns:
166
+ ``(aliases_map, warnings)`` — warnings has one entry when any
167
+ chunk failed, empty otherwise.
153
168
  """
154
169
  aliases_map: dict[str, list[str]] = {}
155
170
  if not survivor_ids:
156
- return aliases_map
157
- try:
158
- entries_resp = await self.client.send_websocket_message(
159
- {
160
- "type": "config/entity_registry/get_entries",
161
- "entity_ids": survivor_ids,
162
- }
163
- )
164
- if isinstance(entries_resp, dict) and entries_resp.get("success"):
165
- for eid, entry in (entries_resp.get("result", {}) or {}).items():
166
- if isinstance(entry, dict):
167
- aliases_map[eid] = entry.get("aliases", []) or []
168
- else:
171
+ return aliases_map, []
172
+
173
+ chunks: list[list[str]] = [
174
+ survivor_ids[i : i + _GET_ENTRIES_CHUNK_SIZE]
175
+ for i in range(0, len(survivor_ids), _GET_ENTRIES_CHUNK_SIZE)
176
+ ]
177
+ responses: list[Any] = await asyncio.gather(
178
+ *(
179
+ self.client.send_websocket_message(
180
+ {
181
+ "type": "config/entity_registry/get_entries",
182
+ "entity_ids": chunk,
183
+ }
184
+ )
185
+ for chunk in chunks
186
+ ),
187
+ return_exceptions=True,
188
+ )
189
+
190
+ failed_chunks = 0
191
+ for chunk, entries_resp in zip(chunks, responses, strict=True):
192
+ # Same convention as _fetch_search_entities: a captured
193
+ # CancelledError means the surrounding task is being torn
194
+ # down — propagate it instead of degrading to a warning.
195
+ if isinstance(entries_resp, asyncio.CancelledError):
196
+ raise entries_resp
197
+ if isinstance(entries_resp, BaseException):
198
+ failed_chunks += 1
169
199
  logger.warning(
170
- "alias_enrichment_failed: get_entries returned non-success "
171
- "for %d entities (resp=%r)",
172
- len(survivor_ids),
200
+ "alias_enrichment_failed: get_entries chunk of %d entities "
201
+ "raised (err=%r)",
202
+ len(chunk),
173
203
  entries_resp,
174
204
  )
175
- except (KeyError, TypeError, AttributeError) as alias_err:
176
- logger.warning(
177
- "alias_enrichment_failed: malformed payload for %d entities (err=%r)",
178
- len(survivor_ids),
179
- alias_err,
205
+ continue
206
+ try:
207
+ if isinstance(entries_resp, dict) and entries_resp.get("success"):
208
+ for eid, entry in (entries_resp.get("result", {}) or {}).items():
209
+ if isinstance(entry, dict):
210
+ aliases_map[eid] = entry.get("aliases", []) or []
211
+ else:
212
+ failed_chunks += 1
213
+ logger.warning(
214
+ "alias_enrichment_failed: get_entries returned non-success "
215
+ "for a chunk of %d entities (resp=%r)",
216
+ len(chunk),
217
+ entries_resp,
218
+ )
219
+ except (KeyError, TypeError, AttributeError) as alias_err:
220
+ failed_chunks += 1
221
+ logger.warning(
222
+ "alias_enrichment_failed: malformed payload for a chunk of "
223
+ "%d entities (err=%r)",
224
+ len(chunk),
225
+ alias_err,
226
+ )
227
+ warnings: list[str] = []
228
+ if failed_chunks:
229
+ warnings.append(
230
+ f"Alias enrichment incomplete: {failed_chunks} of {len(chunks)} "
231
+ "entity-registry lookups failed; alias-based matches may be "
232
+ "missing from these results."
180
233
  )
181
- return aliases_map
234
+ return aliases_map, warnings
182
235
 
183
236
  async def _fetch_search_entities(
184
237
  self, domain_filter: str | None, include_hidden: bool
@@ -223,7 +276,8 @@ class EntitySearchMixin(_SearchBase):
223
276
  survivor_ids, survivor_states = self._filter_hidden_entities(
224
277
  entities, registry_slim, include_hidden, visibility_hidden
225
278
  )
226
- aliases_map = await self._fetch_entity_aliases(survivor_ids)
279
+ aliases_map, alias_warnings = await self._fetch_entity_aliases(survivor_ids)
280
+ visibility_warnings = [*visibility_warnings, *alias_warnings]
227
281
 
228
282
  # Enrich with aliases + hidden_by for the fuzzy layer. Shallow copy +
229
283
  # private-prefixed keys so downstream consumers that round-trip these
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ha-mcp-dev
3
- Version: 7.10.0.dev775
3
+ Version: 7.10.0.dev776
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