ha-mcp-dev 7.12.3.dev1891__tar.gz → 7.12.3.dev1895__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 (165) hide show
  1. {ha_mcp_dev-7.12.3.dev1891/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.12.3.dev1895}/PKG-INFO +1 -1
  2. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/pyproject.toml +1 -1
  3. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/client/websocket_client.py +27 -3
  4. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/tools_calendar.py +61 -31
  5. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/tools_dev.py +54 -5
  6. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/tools_history.py +2 -12
  7. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/tools_radio.py +15 -1
  8. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/util_helpers.py +32 -0
  9. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
  10. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/LICENSE +0 -0
  11. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/MANIFEST.in +0 -0
  12. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/README.md +0 -0
  13. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/setup.cfg +0 -0
  14. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/__init__.py +0 -0
  15. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/__main__.py +0 -0
  16. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/_pypi_marker +0 -0
  17. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/_version.py +0 -0
  18. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/auth/__init__.py +0 -0
  19. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/auth/consent_form.py +0 -0
  20. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/auth/provider.py +0 -0
  21. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/backup_manager.py +0 -0
  22. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/browser_landing.py +0 -0
  23. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/client/__init__.py +0 -0
  24. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/client/rest_client.py +0 -0
  25. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/client/supervisor_client.py +0 -0
  26. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/client/websocket_listener.py +0 -0
  27. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/config.py +0 -0
  28. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/dashboard_screenshot/__init__.py +0 -0
  29. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/dashboard_screenshot/capture.py +0 -0
  30. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/dashboard_screenshot/provision.py +0 -0
  31. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/errors.py +0 -0
  32. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/llm_exposure.py +0 -0
  33. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/policy/__init__.py +0 -0
  34. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/policy/approval_queue.py +0 -0
  35. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/policy/evaluator.py +0 -0
  36. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/policy/handlers.py +0 -0
  37. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/policy/middleware.py +0 -0
  38. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/policy/model.py +0 -0
  39. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/policy/persistence.py +0 -0
  40. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/policy/value_sources.py +0 -0
  41. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/py.typed +0 -0
  42. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/read_only.py +0 -0
  43. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
  44. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
  45. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
  46. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
  47. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
  48. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
  49. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
  50. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
  51. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
  52. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
  53. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
  54. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
  55. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/appdaemon.md +0 -0
  56. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
  57. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/blueprint-guide.md +0 -0
  58. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
  59. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
  60. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
  61. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
  62. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
  63. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
  64. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
  65. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/scenes.md +0 -0
  66. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
  67. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/yaml-only-integrations.md +0 -0
  68. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/server.py +0 -0
  69. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/settings_ui/__init__.py +0 -0
  70. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/settings_ui/settings.css +0 -0
  71. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/settings_ui/settings.html +0 -0
  72. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/settings_ui/settings.js +0 -0
  73. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/smoke_test.py +0 -0
  74. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
  75. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/strict_bps.py +0 -0
  76. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/__init__.py +0 -0
  77. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/auto_backup.py +0 -0
  78. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/backup.py +0 -0
  79. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/best_practice_checker.py +0 -0
  80. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/component_api.py +0 -0
  81. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/config_entry_flow.py +0 -0
  82. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/device_control.py +0 -0
  83. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/enhanced.py +0 -0
  84. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/helpers.py +0 -0
  85. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/radio/__init__.py +0 -0
  86. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/radio/base.py +0 -0
  87. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/radio/matter.py +0 -0
  88. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/radio/thread.py +0 -0
  89. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/radio/zigbee.py +0 -0
  90. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/radio/zwave.py +0 -0
  91. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/reference_validator.py +0 -0
  92. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/registry.py +0 -0
  93. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/smart_search/__init__.py +0 -0
  94. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/smart_search/_base.py +0 -0
  95. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/smart_search/_config.py +0 -0
  96. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/smart_search/_deep.py +0 -0
  97. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/smart_search/_entities.py +0 -0
  98. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/smart_search/_fetch.py +0 -0
  99. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/smart_search/_overview.py +0 -0
  100. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/smart_search/_scenes.py +0 -0
  101. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/smart_search/_scoring.py +0 -0
  102. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/tool_search_hint_middleware.py +0 -0
  103. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/tools_addons.py +0 -0
  104. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/tools_areas.py +0 -0
  105. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/tools_blueprints.py +0 -0
  106. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/tools_bug_report.py +0 -0
  107. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/tools_camera.py +0 -0
  108. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/tools_categories.py +0 -0
  109. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/tools_code.py +0 -0
  110. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/tools_config_automations.py +0 -0
  111. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
  112. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
  113. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
  114. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
  115. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/tools_dashboard_screenshot.py +0 -0
  116. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/tools_energy.py +0 -0
  117. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/tools_entities.py +0 -0
  118. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/tools_filesystem.py +0 -0
  119. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/tools_groups.py +0 -0
  120. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/tools_hacs.py +0 -0
  121. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/tools_integrations.py +0 -0
  122. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/tools_labels.py +0 -0
  123. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/tools_mcp_component.py +0 -0
  124. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/tools_registry.py +0 -0
  125. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/tools_resources.py +0 -0
  126. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/tools_search.py +0 -0
  127. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/tools_service.py +0 -0
  128. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/tools_services.py +0 -0
  129. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/tools_system.py +0 -0
  130. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/tools_themes.py +0 -0
  131. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/tools_todo.py +0 -0
  132. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/tools_traces.py +0 -0
  133. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/tools_updates.py +0 -0
  134. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/tools_utility.py +0 -0
  135. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
  136. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
  137. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/tools_zones.py +0 -0
  138. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/tools/validation_middleware.py +0 -0
  139. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/transforms/__init__.py +0 -0
  140. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/transforms/categorized_search.py +0 -0
  141. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
  142. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/transport_security.py +0 -0
  143. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/update_check.py +0 -0
  144. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/utils/__init__.py +0 -0
  145. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/utils/config_hash.py +0 -0
  146. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/utils/data_paths.py +0 -0
  147. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/utils/domain_handlers.py +0 -0
  148. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/utils/fuzzy_search.py +0 -0
  149. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
  150. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/utils/operation_manager.py +0 -0
  151. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/utils/python_sandbox.py +0 -0
  152. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/utils/skill_loader.py +0 -0
  153. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/utils/usage_logger.py +0 -0
  154. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/visibility/__init__.py +0 -0
  155. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/visibility/model.py +0 -0
  156. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/visibility/persistence.py +0 -0
  157. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp/visibility/resolver.py +0 -0
  158. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
  159. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
  160. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
  161. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
  162. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
  163. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/tests/__init__.py +0 -0
  164. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/tests/test_constants.py +0 -0
  165. {ha_mcp_dev-7.12.3.dev1891 → ha_mcp_dev-7.12.3.dev1895}/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.12.3.dev1891
3
+ Version: 7.12.3.dev1895
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.12.3.dev1891"
7
+ version = "7.12.3.dev1895"
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
@@ -1083,6 +1083,23 @@ class WebSocketManager:
1083
1083
  """Create a cache key from credentials."""
1084
1084
  return hashlib.sha256(f"{url.rstrip('/')}:{token}".encode()).hexdigest()
1085
1085
 
1086
+ @staticmethod
1087
+ def _effective_verify_ssl(verify_ssl: bool | None) -> bool:
1088
+ """Resolve the effective TLS-verification mode for the pool key."""
1089
+ if verify_ssl is not None:
1090
+ return verify_ssl
1091
+ try:
1092
+ return bool(get_global_settings().verify_ssl)
1093
+ except Exception as e:
1094
+ # Mirror HomeAssistantWebSocketClient.__init__: a bad env var
1095
+ # elsewhere should not crash pooling or silently flip TLS off.
1096
+ logger.warning(
1097
+ "Could not load settings while resolving the pool verify_ssl "
1098
+ "key (%s); falling back to verify_ssl=True.",
1099
+ e,
1100
+ )
1101
+ return True
1102
+
1086
1103
  async def get_client(
1087
1104
  self,
1088
1105
  url: str | None = None,
@@ -1138,9 +1155,16 @@ class WebSocketManager:
1138
1155
  ws_url = settings.homeassistant_url
1139
1156
  ws_token = settings.homeassistant_token
1140
1157
 
1141
- key = self._client_key(ws_url, ws_token)
1142
- if verify_ssl is not None:
1143
- key = f"{key}|verify_ssl={verify_ssl}"
1158
+ # Key on the EFFECTIVE verification mode: a caller passing the
1159
+ # resolved settings default (send_websocket_message) must share
1160
+ # the pooled connection with callers that omit the argument
1161
+ # (listener, HACS, installer) — only a genuine override such as
1162
+ # verify_ssl=False gets its own isolated connection.
1163
+ effective_verify_ssl = self._effective_verify_ssl(verify_ssl)
1164
+ key = (
1165
+ f"{self._client_key(ws_url, ws_token)}"
1166
+ f"|verify_ssl={effective_verify_ssl}"
1167
+ )
1144
1168
 
1145
1169
  # Return existing connected client for these credentials
1146
1170
  existing = self._clients.get(key)
@@ -15,7 +15,10 @@ from fastmcp.exceptions import ToolError
15
15
  from fastmcp.tools import tool
16
16
  from pydantic import Field
17
17
 
18
- from ..client.rest_client import HomeAssistantCommandError
18
+ from ..client.rest_client import (
19
+ HomeAssistantCommandError,
20
+ HomeAssistantConnectionError,
21
+ )
19
22
  from ..errors import ErrorCode, create_error_response
20
23
  from .auto_backup import with_auto_backup
21
24
  from .helpers import (
@@ -25,6 +28,7 @@ from .helpers import (
25
28
  register_tool_methods,
26
29
  validate_identifier_not_empty,
27
30
  )
31
+ from .util_helpers import is_connection_error_message
28
32
 
29
33
  logger = logging.getLogger(__name__)
30
34
 
@@ -201,17 +205,20 @@ class CalendarTools:
201
205
 
202
206
  # Route through the shared pooled WebSocket (issue #1813) instead of a
203
207
  # dedicated connect/auth handshake per call. The pooled path collapses a
204
- # failed WS command into ``{"success": False, "error": ...}``; re-raise
205
- # it as the same ``HomeAssistantCommandError`` the dedicated send_command
206
- # used to raise so the caller's error handler still attaches the
207
- # rrule-specific suggestions.
208
+ # failed WS command into ``{"success": False, "error": ...}``; a
209
+ # transport-shaped failure re-raises as ``HomeAssistantConnectionError``
210
+ # (the classifier type-matches it to connectivity guidance), anything
211
+ # else as the same ``HomeAssistantCommandError`` the dedicated
212
+ # send_command used to raise so the caller's error handler still
213
+ # attaches the rrule-specific suggestions.
208
214
  result = await self._client.send_websocket_message(
209
215
  {"type": "calendar/event/create", "entity_id": entity_id, "event": event}
210
216
  )
211
217
  if not result.get("success"):
212
- raise HomeAssistantCommandError(
213
- str(result.get("error", "calendar/event/create failed"))
214
- )
218
+ error = str(result.get("error", "calendar/event/create failed"))
219
+ if is_connection_error_message(error):
220
+ raise HomeAssistantConnectionError(error)
221
+ raise HomeAssistantCommandError(error)
215
222
  return result
216
223
 
217
224
  async def _create_simple_calendar_event(
@@ -242,6 +249,13 @@ class CalendarTools:
242
249
  self, entity_id: str, rrule: str | None, error: Exception
243
250
  ) -> list[str]:
244
251
  """Build suggestions for a failed ha_config_set_calendar_event call."""
252
+ if isinstance(error, HomeAssistantConnectionError):
253
+ # A transport drop is not a calendar problem — domain hints would
254
+ # send the agent chasing a non-issue during an HA restart.
255
+ return [
256
+ "Home Assistant may be restarting or unreachable — retry shortly",
257
+ "Check the connection to Home Assistant",
258
+ ]
245
259
  suggestions = [
246
260
  f"Verify calendar entity '{entity_id}' exists and supports event creation",
247
261
  "Check datetime format (ISO 8601)",
@@ -527,17 +541,20 @@ class CalendarTools:
527
541
  ws_kwargs["recurrence_range"] = recurrence_range
528
542
 
529
543
  # Route through the shared pooled WebSocket (issue #1813) instead of a
530
- # dedicated connect/auth handshake per call. Re-raise a failed WS
531
- # command as the same ``HomeAssistantCommandError`` the dedicated
532
- # send_command used to raise so the outer handler builds the
533
- # delete-specific suggestions (404 / not-supported).
544
+ # dedicated connect/auth handshake per call. Transport-shaped
545
+ # failures raise ``HomeAssistantConnectionError`` (connectivity
546
+ # guidance); anything else re-raises as the same
547
+ # ``HomeAssistantCommandError`` the dedicated send_command used to
548
+ # raise so the outer handler builds the delete-specific
549
+ # suggestions (404 / not-supported).
534
550
  result = await self._client.send_websocket_message(
535
551
  {"type": "calendar/event/delete", **ws_kwargs}
536
552
  )
537
553
  if not result.get("success"):
538
- raise HomeAssistantCommandError(
539
- str(result.get("error", "calendar/event/delete failed"))
540
- )
554
+ error = str(result.get("error", "calendar/event/delete failed"))
555
+ if is_connection_error_message(error):
556
+ raise HomeAssistantConnectionError(error)
557
+ raise HomeAssistantCommandError(error)
541
558
 
542
559
  return {
543
560
  "success": True,
@@ -554,28 +571,41 @@ class CalendarTools:
554
571
  except Exception as error:
555
572
  logger.error(f"Failed to delete calendar event from {entity_id}: {error}")
556
573
 
557
- suggestions = [
558
- f"Verify calendar entity '{entity_id}' exists",
559
- f"Verify event with UID '{uid}' exists in the calendar",
560
- "Use ha_config_get_calendar_events() to find the correct event UID",
561
- "Some calendar integrations may not support event deletion",
562
- ]
563
-
564
- error_str = str(error)
565
- if "404" in error_str or "not found" in error_str.lower():
566
- suggestions.insert(
567
- 0, f"Calendar entity '{entity_id}' or event '{uid}' not found"
568
- )
569
- if "not supported" in error_str.lower():
570
- suggestions.insert(0, "This calendar does not support event deletion")
571
-
572
574
  exception_to_structured_error(
573
575
  error,
574
576
  context={"entity_id": entity_id, "uid": uid},
575
- suggestions=suggestions,
577
+ suggestions=self._build_remove_calendar_event_error_suggestions(
578
+ entity_id, uid, error
579
+ ),
576
580
  )
577
581
  return None # unreachable: exception_to_structured_error always raises
578
582
 
583
+ def _build_remove_calendar_event_error_suggestions(
584
+ self, entity_id: str, uid: str, error: Exception
585
+ ) -> list[str]:
586
+ """Build suggestions for a failed ha_config_remove_calendar_event call."""
587
+ if isinstance(error, HomeAssistantConnectionError):
588
+ # A transport drop is not a calendar problem — domain hints would
589
+ # send the agent chasing a non-issue during an HA restart.
590
+ return [
591
+ "Home Assistant may be restarting or unreachable — retry shortly",
592
+ "Check the connection to Home Assistant",
593
+ ]
594
+ suggestions = [
595
+ f"Verify calendar entity '{entity_id}' exists",
596
+ f"Verify event with UID '{uid}' exists in the calendar",
597
+ "Use ha_config_get_calendar_events() to find the correct event UID",
598
+ "Some calendar integrations may not support event deletion",
599
+ ]
600
+ error_str = str(error)
601
+ if "404" in error_str or "not found" in error_str.lower():
602
+ suggestions.insert(
603
+ 0, f"Calendar entity '{entity_id}' or event '{uid}' not found"
604
+ )
605
+ if "not supported" in error_str.lower():
606
+ suggestions.insert(0, "This calendar does not support event deletion")
607
+ return suggestions
608
+
579
609
 
580
610
  def register_calendar_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
581
611
  """Register calendar management tools with the MCP server."""
@@ -44,8 +44,25 @@ COMPONENT_DOMAIN = "ha_mcp_tools"
44
44
  # (custom_components/ha_mcp_tools/const.py OPT_CHANNEL / OPT_PIP_SPEC).
45
45
  _OPT_CHANNEL = "channel"
46
46
  _OPT_PIP_SPEC = "pip_spec"
47
+ _OPT_SERVER_URL = "server_url"
48
+ _OPT_EXTERNAL_URL = "external_url"
49
+ _OPT_WEBHOOK_ID_OVERRIDE = "webhook_id_override"
50
+ _OPT_SECRET_PATH_OVERRIDE = "secret_path_override"
47
51
  _VALID_CHANNELS = ("stable", "dev")
48
52
 
53
+ # Optional text fields the component's options flow pre-fills via
54
+ # suggested_value (so the UI can clear them). Because an OMITTED optional field
55
+ # reads as "cleared" rather than "unchanged", a partial update_source submit
56
+ # must resend these at their current values or it would blank the user's
57
+ # server-URL / connect-secret overrides.
58
+ _PRESERVED_OPTION_KEYS = (
59
+ _OPT_PIP_SPEC,
60
+ _OPT_SERVER_URL,
61
+ _OPT_EXTERNAL_URL,
62
+ _OPT_WEBHOOK_ID_OVERRIDE,
63
+ _OPT_SECRET_PATH_OVERRIDE,
64
+ )
65
+
49
66
  # Delay before a self-affecting action (embedded entry reload / options
50
67
  # submit) fires, so this tool's JSON response flushes to the MCP client
51
68
  # before the serving thread is torn down. Mirrors
@@ -85,6 +102,23 @@ def _spawn_background(coro: Any) -> None:
85
102
  task.add_done_callback(_BACKGROUND_TASKS.discard)
86
103
 
87
104
 
105
+ def _field_prefill(item: dict[str, Any]) -> Any:
106
+ """Return a serialized options-flow field's current value.
107
+
108
+ Reads ``description.suggested_value`` first: a persisted option is
109
+ serialized there (as ``add_suggested_values_to_schema`` does; this component
110
+ sets it directly on the ``vol.Optional`` marker), and the clearable text
111
+ fields carry their value there rather than as a schema ``default`` (a
112
+ ``default`` equal to the value would make the field impossible to clear).
113
+ Falls back to ``default`` then ``value`` for the dropdown/toggle fields.
114
+ Mirrors ``tools_integrations.options_from_form_flow``.
115
+ """
116
+ description = item.get("description")
117
+ if isinstance(description, dict) and description.get("suggested_value") is not None:
118
+ return description["suggested_value"]
119
+ return item.get("default", item.get("value"))
120
+
121
+
88
122
  async def find_server_config_entry(
89
123
  client: Any,
90
124
  ) -> tuple[str, dict[str, Any], dict[str, Any]] | None:
@@ -95,8 +129,9 @@ async def find_server_config_entry(
95
129
  (services) entry's flow aborts immediately. Returns
96
130
  ``(entry_id, open_flow, current_options)`` with the options flow left
97
131
  OPEN (callers must submit or abort it), or ``None`` when no server
98
- entry exists. ``current_options`` maps schema field names to their
99
- defaults — i.e. the entry's current option values.
132
+ entry exists. ``current_options`` maps schema field names to their current
133
+ values (persisted ``suggested_value`` first, else the schema ``default`` or
134
+ ``value``, via ``_field_prefill``).
100
135
 
101
136
  Module-level (not a DevTools method) so the settings UI's embedded
102
137
  restart handler can share it.
@@ -131,7 +166,7 @@ async def find_server_config_entry(
131
166
  continue
132
167
  schema = flow.get("data_schema") or []
133
168
  fields: dict[str, Any] = {
134
- str(item["name"]): item.get("default")
169
+ str(item["name"]): _field_prefill(item)
135
170
  for item in schema
136
171
  if isinstance(item, dict) and item.get("name")
137
172
  }
@@ -400,7 +435,16 @@ class DevTools:
400
435
  await asyncio.sleep(_SELF_ACTION_FLUSH_DELAY_S)
401
436
  try:
402
437
  result = await self._client.submit_options_flow_step(flow_id, user_input)
403
- logger.info("Deferred options submit result: %s", result.get("type"))
438
+ if result.get("type") == "create_entry":
439
+ logger.info("Deferred options submit applied")
440
+ else:
441
+ # Fire-and-forget: no caller is left to raise to, so a rejected
442
+ # self-restart must at least be discoverable in the log.
443
+ logger.warning(
444
+ "Deferred options submit was not applied (type=%s, errors=%s)",
445
+ result.get("type"),
446
+ result.get("errors") or result.get("reason"),
447
+ )
404
448
  except Exception:
405
449
  logger.exception("Deferred options-flow submit failed")
406
450
 
@@ -819,7 +863,12 @@ class DevTools:
819
863
  )
820
864
  )
821
865
  entry_id, flow, current = found
822
- user_input: dict[str, Any] = {}
866
+ # Resend the user's current overrides (see _PRESERVED_OPTION_KEYS) so a
867
+ # channel/pip-spec change here does not blank them — an omitted optional
868
+ # field reads as "cleared", not "unchanged".
869
+ user_input: dict[str, Any] = {
870
+ key: current[key] for key in _PRESERVED_OPTION_KEYS if current.get(key)
871
+ }
823
872
  if channel is not None:
824
873
  user_input[_OPT_CHANNEL] = channel
825
874
  if pip_spec is not None:
@@ -32,6 +32,7 @@ from .util_helpers import (
32
32
  JSON_STRING_COERCION,
33
33
  add_timezone_metadata,
34
34
  build_pagination_metadata,
35
+ is_connection_error_message,
35
36
  parse_string_list_param,
36
37
  project_fields,
37
38
  )
@@ -490,16 +491,6 @@ def _parse_time_range(
490
491
  return start_dt, end_dt
491
492
 
492
493
 
493
- _WS_CONNECTION_SIGNATURES = (
494
- "failed to connect",
495
- "timed out",
496
- "timeout",
497
- "connection closed",
498
- "disconnected",
499
- "not connected",
500
- )
501
-
502
-
503
494
  def _raise_recorder_ws_failure(
504
495
  kind: str,
505
496
  error_msg: str,
@@ -513,8 +504,7 @@ def _raise_recorder_ws_failure(
513
504
  as CONNECTION_FAILED (retry/connectivity guidance) instead of presenting
514
505
  recorder-retention suggestions during an HA restart or WS outage.
515
506
  """
516
- lowered = error_msg.lower()
517
- if any(sig in lowered for sig in _WS_CONNECTION_SIGNATURES):
507
+ if is_connection_error_message(error_msg):
518
508
  raise_tool_error(
519
509
  create_error_response(
520
510
  ErrorCode.CONNECTION_FAILED,
@@ -31,7 +31,7 @@ from .radio import thread as thread_handler
31
31
  from .radio import zigbee as zigbee_handler
32
32
  from .radio import zwave as zwave_handler
33
33
  from .radio.base import confirm_required, require
34
- from .util_helpers import JSON_STRING_COERCION
34
+ from .util_helpers import JSON_STRING_COERCION, is_connection_error_message
35
35
 
36
36
  logger = logging.getLogger(__name__)
37
37
 
@@ -62,6 +62,20 @@ class RadioTools:
62
62
  device_id = (result.get("result") or {}).get("device_id")
63
63
  if device_id:
64
64
  return str(device_id)
65
+ else:
66
+ error = str(result.get("error", ""))
67
+ if is_connection_error_message(error):
68
+ # A transport drop is not evidence the entity is missing.
69
+ raise_tool_error(
70
+ create_error_response(
71
+ ErrorCode.CONNECTION_FAILED,
72
+ f"Could not resolve entity '{entity_id}': {error}",
73
+ context={"entity_id": entity_id},
74
+ suggestions=[
75
+ "Home Assistant may be restarting or unreachable — retry shortly",
76
+ ],
77
+ )
78
+ )
65
79
  raise_tool_error(
66
80
  create_error_response(
67
81
  ErrorCode.ENTITY_NOT_FOUND,
@@ -2236,3 +2236,35 @@ def merge_visibility_warnings(
2236
2236
  if warnings:
2237
2237
  response.setdefault("warnings", []).extend(warnings)
2238
2238
  return response
2239
+
2240
+
2241
+ # Error strings produced by the pooled WebSocket path when the transport (not
2242
+ # the command) fails: the manager's connect raise, send timeouts, and socket
2243
+ # drops. ``send_websocket_message`` collapses these into
2244
+ # ``{"success": False, "error": ...}``, so callers that attach domain-specific
2245
+ # suggestions must first check the shape or an HA restart gets presented as a
2246
+ # domain problem (issue #1832 review).
2247
+ WS_CONNECTION_SIGNATURES = (
2248
+ "failed to connect",
2249
+ "timed out",
2250
+ "timeout",
2251
+ "connection closed",
2252
+ # reset_connection: "WebSocket connection to Home Assistant closed while
2253
+ # waiting for a response" — "connection closed" is not adjacent there.
2254
+ "closed while waiting",
2255
+ # listener close reason: "connection dropped without a close frame".
2256
+ "connection dropped",
2257
+ "disconnected",
2258
+ "not connected",
2259
+ "not authenticated",
2260
+ )
2261
+
2262
+
2263
+ def is_connection_error_message(error_msg: Any) -> bool:
2264
+ """True when a pooled-WS failure payload is connection/transport-shaped.
2265
+
2266
+ Accepts any payload shape: HA error frames can carry a dict (or None)
2267
+ in the error slot, so the value is stringified before matching.
2268
+ """
2269
+ lowered = str(error_msg).lower()
2270
+ return any(sig in lowered for sig in WS_CONNECTION_SIGNATURES)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ha-mcp-dev
3
- Version: 7.12.3.dev1891
3
+ Version: 7.12.3.dev1895
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