ha-mcp-dev 7.12.3.dev1949__tar.gz → 7.13.0.dev1955__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 (177) hide show
  1. {ha_mcp_dev-7.12.3.dev1949/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.13.0.dev1955}/PKG-INFO +1 -1
  2. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/pyproject.toml +1 -1
  3. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/client/rest_client.py +4 -1
  4. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/config.py +21 -5
  5. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/smart_search/_config.py +2 -1
  6. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/smart_search/_deep.py +3 -5
  7. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/smart_search/_entities.py +10 -1
  8. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/smart_search/_fetch.py +18 -21
  9. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/smart_search/_scenes.py +1 -2
  10. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/tools_blueprints.py +119 -33
  11. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
  12. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/LICENSE +0 -0
  13. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/MANIFEST.in +0 -0
  14. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/README.md +0 -0
  15. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/setup.cfg +0 -0
  16. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/__init__.py +0 -0
  17. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/__main__.py +0 -0
  18. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/_pypi_marker +0 -0
  19. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/_version.py +0 -0
  20. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/auth/__init__.py +0 -0
  21. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/auth/consent_form.py +0 -0
  22. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/auth/provider.py +0 -0
  23. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/backup_manager.py +0 -0
  24. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/browser_landing.py +0 -0
  25. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/client/__init__.py +0 -0
  26. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/client/supervisor_client.py +0 -0
  27. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/client/websocket_client.py +0 -0
  28. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/client/websocket_listener.py +0 -0
  29. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/dashboard_screenshot/__init__.py +0 -0
  30. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/dashboard_screenshot/capture.py +0 -0
  31. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/dashboard_screenshot/content.py +0 -0
  32. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/dashboard_screenshot/paths.py +0 -0
  33. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/dashboard_screenshot/provision.py +0 -0
  34. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/errors.py +0 -0
  35. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/llm_exposure.py +0 -0
  36. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/policy/__init__.py +0 -0
  37. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/policy/approval_queue.py +0 -0
  38. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/policy/evaluator.py +0 -0
  39. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/policy/handlers.py +0 -0
  40. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/policy/middleware.py +0 -0
  41. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/policy/model.py +0 -0
  42. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/policy/persistence.py +0 -0
  43. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/policy/value_sources.py +0 -0
  44. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/py.typed +0 -0
  45. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/read_only.py +0 -0
  46. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
  47. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
  48. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
  49. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
  50. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
  51. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
  52. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
  53. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
  54. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
  55. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
  56. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
  57. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
  58. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/appdaemon.md +0 -0
  59. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
  60. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/blueprint-guide.md +0 -0
  61. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
  62. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
  63. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
  64. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
  65. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
  66. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
  67. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
  68. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/scenes.md +0 -0
  69. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
  70. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/yaml-only-integrations.md +0 -0
  71. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/server.py +0 -0
  72. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/settings_ui/__init__.py +0 -0
  73. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/settings_ui/_handlers_advanced.py +0 -0
  74. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/settings_ui/_handlers_backups.py +0 -0
  75. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/settings_ui/_handlers_fs.py +0 -0
  76. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/settings_ui/_handlers_server.py +0 -0
  77. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/settings_ui/_handlers_theme.py +0 -0
  78. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/settings_ui/_handlers_tools.py +0 -0
  79. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/settings_ui/_persistence.py +0 -0
  80. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/settings_ui/_supervisor.py +0 -0
  81. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/settings_ui/_theme.py +0 -0
  82. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/settings_ui/_tools_meta.py +0 -0
  83. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/settings_ui/settings.css +0 -0
  84. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/settings_ui/settings.html +0 -0
  85. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/settings_ui/settings.js +0 -0
  86. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/smoke_test.py +0 -0
  87. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
  88. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/strict_bps.py +0 -0
  89. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/__init__.py +0 -0
  90. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/auto_backup.py +0 -0
  91. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/backup.py +0 -0
  92. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/best_practice_checker.py +0 -0
  93. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/component_api.py +0 -0
  94. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/config_entry_flow.py +0 -0
  95. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/device_control.py +0 -0
  96. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/enhanced.py +0 -0
  97. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/helpers.py +0 -0
  98. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/radio/__init__.py +0 -0
  99. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/radio/base.py +0 -0
  100. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/radio/matter.py +0 -0
  101. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/radio/thread.py +0 -0
  102. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/radio/zigbee.py +0 -0
  103. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/radio/zwave.py +0 -0
  104. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/reference_validator.py +0 -0
  105. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/registry.py +0 -0
  106. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/smart_search/__init__.py +0 -0
  107. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/smart_search/_base.py +0 -0
  108. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/smart_search/_overview.py +0 -0
  109. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/smart_search/_scoring.py +0 -0
  110. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/tool_search_hint_middleware.py +0 -0
  111. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/tools_addons.py +0 -0
  112. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/tools_areas.py +0 -0
  113. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/tools_bug_report.py +0 -0
  114. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/tools_calendar.py +0 -0
  115. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/tools_camera.py +0 -0
  116. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/tools_categories.py +0 -0
  117. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/tools_code.py +0 -0
  118. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/tools_config_automations.py +0 -0
  119. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
  120. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
  121. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
  122. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
  123. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/tools_dashboard_screenshot.py +0 -0
  124. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/tools_dev.py +0 -0
  125. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/tools_energy.py +0 -0
  126. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/tools_entities.py +0 -0
  127. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/tools_filesystem.py +0 -0
  128. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/tools_groups.py +0 -0
  129. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/tools_hacs.py +0 -0
  130. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/tools_history.py +0 -0
  131. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/tools_integrations.py +0 -0
  132. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/tools_labels.py +0 -0
  133. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/tools_radio.py +0 -0
  134. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/tools_registry.py +0 -0
  135. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/tools_resources.py +0 -0
  136. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/tools_search.py +0 -0
  137. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/tools_service.py +0 -0
  138. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/tools_services.py +0 -0
  139. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/tools_system.py +0 -0
  140. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/tools_themes.py +0 -0
  141. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/tools_todo.py +0 -0
  142. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/tools_traces.py +0 -0
  143. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/tools_updates.py +0 -0
  144. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/tools_utility.py +0 -0
  145. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
  146. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
  147. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/tools_yaml_read.py +0 -0
  148. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/tools_zones.py +0 -0
  149. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/util_helpers.py +0 -0
  150. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/tools/validation_middleware.py +0 -0
  151. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/transforms/__init__.py +0 -0
  152. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/transforms/categorized_search.py +0 -0
  153. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
  154. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/transport_security.py +0 -0
  155. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/update_check.py +0 -0
  156. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/utils/__init__.py +0 -0
  157. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/utils/config_hash.py +0 -0
  158. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/utils/data_paths.py +0 -0
  159. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/utils/domain_handlers.py +0 -0
  160. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/utils/fuzzy_search.py +0 -0
  161. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
  162. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/utils/operation_manager.py +0 -0
  163. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/utils/python_sandbox.py +0 -0
  164. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/utils/skill_loader.py +0 -0
  165. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/utils/usage_logger.py +0 -0
  166. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/visibility/__init__.py +0 -0
  167. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/visibility/model.py +0 -0
  168. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/visibility/persistence.py +0 -0
  169. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp/visibility/resolver.py +0 -0
  170. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
  171. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
  172. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
  173. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
  174. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
  175. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/tests/__init__.py +0 -0
  176. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/tests/test_constants.py +0 -0
  177. {ha_mcp_dev-7.12.3.dev1949 → ha_mcp_dev-7.13.0.dev1955}/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.dev1949
3
+ Version: 7.13.0.dev1955
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.dev1949"
7
+ version = "7.13.0.dev1955"
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
@@ -1453,7 +1453,10 @@ class HomeAssistantClient:
1453
1453
  ],
1454
1454
  }
1455
1455
 
1456
- logger.error(f"WebSocket message failed: {e}")
1456
+ # Name the command in the log line: a bare "Command failed:
1457
+ # Unknown command." is undiagnosable from a user's log
1458
+ # (issue #1889 took a live reproduction to attribute).
1459
+ logger.error(f"WebSocket message failed ({message.get('type')}): {e}")
1457
1460
  return {"success": False, "error": str(e)}
1458
1461
 
1459
1462
  return {"success": False, "error": "WebSocket request failed"}
@@ -1440,10 +1440,12 @@ _settings: Settings | None = None
1440
1440
  # THROUGH THIS DICT — never
1441
1441
  # via os.environ — so the admin token can't be read from the shared HA process
1442
1442
  # environment. Applied onto the Settings singleton in ``get_global_settings``.
1443
- _EMBEDDED_CONNECTION: dict[str, str] = {}
1443
+ _EMBEDDED_CONNECTION: dict[str, str | bool] = {}
1444
1444
 
1445
1445
 
1446
- def set_embedded_connection(url: str, token: str) -> None:
1446
+ def set_embedded_connection(
1447
+ url: str, token: str, verify_ssl: bool | None = None
1448
+ ) -> None:
1447
1449
  """Register the in-process HA connection for embedded mode.
1448
1450
 
1449
1451
  Embedded-mode only: called by the ha_mcp_tools custom component's in-process
@@ -1459,9 +1461,20 @@ def set_embedded_connection(url: str, token: str) -> None:
1459
1461
  Registration therefore cannot assume it runs before the first build — the
1460
1462
  integration imports this function from the very package whose import
1461
1463
  creates the singleton.
1464
+
1465
+ ``verify_ssl`` lets the component disable certificate verification when it
1466
+ derives an ``https://127.0.0.1`` loopback URL from Home Assistant's SSL
1467
+ config (issue #1890): HA's certificate is issued for its hostname, never
1468
+ for 127.0.0.1, so verification on the loopback connection can only fail.
1469
+ ``None`` (the default, and what pre-#1890 components pass implicitly)
1470
+ leaves ``Settings.verify_ssl`` alone.
1462
1471
  """
1463
1472
  _EMBEDDED_CONNECTION["url"] = url
1464
1473
  _EMBEDDED_CONNECTION["token"] = token
1474
+ if verify_ssl is None:
1475
+ _EMBEDDED_CONNECTION.pop("verify_ssl", None)
1476
+ else:
1477
+ _EMBEDDED_CONNECTION["verify_ssl"] = verify_ssl
1465
1478
  if _settings is not None:
1466
1479
  _apply_embedded_connection(_settings)
1467
1480
 
@@ -1764,7 +1777,7 @@ def _apply_backup_overrides(settings: "Settings") -> None:
1764
1777
 
1765
1778
 
1766
1779
  def _apply_embedded_connection(settings: "Settings") -> None:
1767
- """Apply the in-process embedded HA connection (url/token) if registered.
1780
+ """Apply the in-process embedded HA connection (url/token/verify_ssl) if registered.
1768
1781
 
1769
1782
  No-op outside embedded mode. Plain ``setattr`` (``validate_assignment`` is off
1770
1783
  on ``Settings``, mirroring ``_apply_backup_overrides``), so the loopback URL
@@ -1773,10 +1786,13 @@ def _apply_embedded_connection(settings: "Settings") -> None:
1773
1786
  """
1774
1787
  url = _EMBEDDED_CONNECTION.get("url")
1775
1788
  token = _EMBEDDED_CONNECTION.get("token")
1776
- if url:
1789
+ if isinstance(url, str) and url:
1777
1790
  settings.homeassistant_url = url.rstrip("/")
1778
- if token:
1791
+ if isinstance(token, str) and token:
1779
1792
  settings.homeassistant_token = token
1793
+ verify_ssl = _EMBEDDED_CONNECTION.get("verify_ssl")
1794
+ if isinstance(verify_ssl, bool):
1795
+ settings.verify_ssl = verify_ssl
1780
1796
 
1781
1797
 
1782
1798
  def get_global_settings() -> Settings:
@@ -15,7 +15,8 @@ BULK_REST_TIMEOUT = 5.0 # Timeout for bulk REST endpoint calls
15
15
  BULK_WEBSOCKET_TIMEOUT = 3.0 # Timeout for bulk WebSocket calls
16
16
 
17
17
 
18
- # Attempt-C (per-id fallback) tuning knobs. Sourced from the resolved
18
+ # Attempt-C (per-id fallback; the letter predates the removal of the phantom
19
+ # WS bulk tier in #1889 and is kept as a proper name) tuning knobs. Sourced from the resolved
19
20
  # Settings (issues #1538 / #1784) so the env var, the web Settings UI
20
21
  # override file, and the field defaults all flow through one precedence path
21
22
  # — and so add-on users (who cannot set raw env vars) can tune them from the
@@ -319,7 +319,7 @@ class DeepSearchMixin(SceneSearchMixin):
319
319
  *,
320
320
  config_time_budget: float | None = None,
321
321
  ) -> tuple[list[dict[str, Any]], int, int, int, int]:
322
- """Deep-search automations: 3-tier config fetch (REST bulk -> WS bulk -> individual).
322
+ """Deep-search automations: two-tier config fetch (REST bulk -> budgeted individual).
323
323
 
324
324
  Returns ``(matches, skipped_count, failed_count, yaml_skipped_count,
325
325
  timeout_count)``. ``skipped_count`` is non-zero only when bulk fetch
@@ -360,10 +360,9 @@ class DeepSearchMixin(SceneSearchMixin):
360
360
  )
361
361
  )
362
362
 
363
- # Phase 2: bulk fetch (Attempt A REST, Attempt B WebSocket)
363
+ # Phase 2: bulk fetch (REST)
364
364
  configs = await self._bulk_fetch_configs(
365
365
  "/config/automation/config",
366
- ["config/automation/config/list", "automation/config/list"],
367
366
  lambda item: item.get("id"),
368
367
  BULK_REST_TIMEOUT,
369
368
  "Automation",
@@ -474,7 +473,7 @@ class DeepSearchMixin(SceneSearchMixin):
474
473
  *,
475
474
  config_time_budget: float | None = None,
476
475
  ) -> tuple[list[dict[str, Any]], int, int, int, int]:
477
- """Deep-search scripts: same 3-tier strategy as automations.
476
+ """Deep-search scripts: same two-tier strategy as automations.
478
477
 
479
478
  Returns ``(matches, skipped_count, failed_count, yaml_skipped_count,
480
479
  timeout_count)``; semantics identical to ``_deep_search_automations``
@@ -500,7 +499,6 @@ class DeepSearchMixin(SceneSearchMixin):
500
499
  # Phase 2: bulk fetch
501
500
  configs = await self._bulk_fetch_configs(
502
501
  "/config/script/config",
503
- ["config/script/config/list", "script/config/list"],
504
502
  lambda item: (
505
503
  item.get("id") or item.get("alias", "").lower().replace(" ", "_")
506
504
  ),
@@ -221,7 +221,16 @@ class EntitySearchMixin(_SearchBase):
221
221
  if isinstance(entries_resp, dict) and entries_resp.get("success"):
222
222
  for eid, entry in (entries_resp.get("result", {}) or {}).items():
223
223
  if isinstance(entry, dict):
224
- aliases_map[eid] = entry.get("aliases", []) or []
224
+ # String entries only: HA serializes the
225
+ # COMPUTED_NAME alias sentinel ("the computed
226
+ # entity name is an alias") as null over the
227
+ # websocket API; ingesting it would put None in
228
+ # every downstream alias string op.
229
+ aliases_map[eid] = [
230
+ a
231
+ for a in (entry.get("aliases") or [])
232
+ if isinstance(a, str)
233
+ ]
225
234
  else:
226
235
  failed_chunks += 1
227
236
  logger.warning(
@@ -1,4 +1,4 @@
1
- """Shared 3-tier config fetching and entry scoring for deep search."""
1
+ """Shared two-tier config fetching (bulk REST, then budgeted per-id) for deep search."""
2
2
 
3
3
  import asyncio
4
4
  import logging
@@ -8,7 +8,7 @@ from typing import Any
8
8
 
9
9
  import httpx
10
10
 
11
- from ._config import BULK_WEBSOCKET_TIMEOUT, INDIVIDUAL_FETCH_BATCH_SIZE
11
+ from ._config import INDIVIDUAL_FETCH_BATCH_SIZE
12
12
  from ._scoring import ScoringMixin
13
13
 
14
14
  logger = logging.getLogger(__name__)
@@ -35,7 +35,7 @@ def is_timeout_error(exc: BaseException) -> bool:
35
35
 
36
36
 
37
37
  class ConfigFetchMixin(ScoringMixin):
38
- """REST/WebSocket bulk + budgeted individual config fetch, and scoring of fetched entries."""
38
+ """REST bulk + budgeted individual config fetch, and scoring of fetched entries."""
39
39
 
40
40
  @staticmethod
41
41
  def _index_configs(
@@ -53,17 +53,25 @@ class ConfigFetchMixin(ScoringMixin):
53
53
  async def _bulk_fetch_configs(
54
54
  self,
55
55
  rest_endpoint: str,
56
- ws_types: list[str],
57
56
  id_of: Callable[[dict[str, Any]], str | None],
58
57
  rest_timeout: float,
59
58
  label: str,
60
59
  ) -> dict[str, dict[str, Any]] | None:
61
- """Bulk-fetch all configs of one domain: REST endpoint, then WS list endpoints.
62
-
63
- Returns ``{id: config}`` (possibly empty) on the first successful
64
- attempt, or ``None`` when every attempt failed. An empty-but-successful
65
- REST list returns ``{}`` (not ``None``) so the caller skips the
66
- individual-fetch fallback exactly as it would for a populated response.
60
+ """Bulk-fetch all configs of one domain from its REST endpoint.
61
+
62
+ Returns ``{id: config}`` (possibly empty) on success, or ``None`` when
63
+ the fetch failed (the caller then falls back to budgeted individual
64
+ fetches). An empty-but-successful REST list returns ``{}`` (not
65
+ ``None``) so the caller skips the individual-fetch fallback exactly as
66
+ it would for a populated response.
67
+
68
+ There is deliberately NO WebSocket fallback here: the
69
+ ``config/<domain>/config/list`` and ``<domain>/config/list`` command
70
+ types this used to try have never existed in HA core, so every legacy
71
+ config search fired paired ``unknown_command`` rejections that
72
+ ``send_websocket_message`` logged as ERRORs — the recurring
73
+ "WebSocket message failed: Command failed: Unknown command." log spam
74
+ of issue #1889.
67
75
  """
68
76
  try:
69
77
  resp = await asyncio.wait_for(
@@ -74,17 +82,6 @@ class ConfigFetchMixin(ScoringMixin):
74
82
  return self._index_configs(resp, id_of)
75
83
  except Exception as e:
76
84
  logger.debug(f"{label} REST bulk fetch failed: {e}")
77
-
78
- for ws_type in ws_types:
79
- try:
80
- ws_resp = await asyncio.wait_for(
81
- self.client.send_websocket_message({"type": ws_type}),
82
- timeout=BULK_WEBSOCKET_TIMEOUT,
83
- )
84
- if isinstance(ws_resp, dict) and ws_resp.get("success"):
85
- return self._index_configs(ws_resp.get("result", []), id_of)
86
- except Exception as e:
87
- logger.debug(f"{label} WebSocket bulk fetch ({ws_type}) failed: {e}")
88
85
  return None
89
86
 
90
87
  async def _individual_fetch_budgeted(
@@ -212,7 +212,7 @@ class SceneSearchMixin(ConfigFetchMixin):
212
212
  config_time_budget: float | None = None,
213
213
  prefetched_registry: Any = None,
214
214
  ) -> tuple[list[dict[str, Any]], int, int, int, bool, int]:
215
- """Deep-search scenes: 3-tier strategy plus registry-walk augmentation.
215
+ """Deep-search scenes: two-tier strategy plus registry-walk augmentation.
216
216
 
217
217
  Scenes have no listing primitive, so entities are enumerated from
218
218
  get_states() and configs fetched per id. Returns the scene results plus
@@ -239,7 +239,6 @@ class SceneSearchMixin(ConfigFetchMixin):
239
239
  # Phase 2: bulk fetch
240
240
  configs = await self._bulk_fetch_configs(
241
241
  "/config/scene/config",
242
- ["config/scene/config/list", "scene/config/list"],
243
242
  lambda item: (
244
243
  item.get("id") or item.get("name", "").lower().replace(" ", "_")
245
244
  ),
@@ -222,6 +222,68 @@ class BlueprintTools:
222
222
  )
223
223
  return None # unreachable: exception_to_structured_error always raises
224
224
 
225
+ async def _save_blueprint(
226
+ self,
227
+ url: str,
228
+ domain: str,
229
+ path: str,
230
+ yaml_data: str,
231
+ overwrite: bool,
232
+ ) -> dict[str, Any]:
233
+ """Persist a validated blueprint via blueprint/save, raising on failure.
234
+
235
+ Returns the blueprint/save result payload (contains overrides_existing).
236
+ """
237
+ save_message: dict[str, Any] = {
238
+ "type": "blueprint/save",
239
+ "domain": domain,
240
+ "path": path,
241
+ "yaml": yaml_data,
242
+ "source_url": url,
243
+ }
244
+ # allow_override only exists on HA >= 2023.12 and the WS schema
245
+ # rejects unknown keys - only send it when actually overwriting
246
+ if overwrite:
247
+ save_message["allow_override"] = True
248
+
249
+ save_response = await self._client.send_websocket_message(save_message)
250
+
251
+ if not save_response.get("success"):
252
+ error = save_response.get("error", {})
253
+ save_error = (
254
+ error.get("message", str(error))
255
+ if isinstance(error, dict)
256
+ else str(error)
257
+ )
258
+
259
+ suggestions = [
260
+ "The blueprint was validated but could not be saved to disk",
261
+ "Use ha_get_blueprint() to check if it already exists",
262
+ ]
263
+
264
+ # Reachable despite the early exists check: a race between
265
+ # import and save, or an installed file that failed to load
266
+ # (core reports exists=false for those)
267
+ already_exists = "already exists" in save_error.lower()
268
+ if already_exists:
269
+ suggestions.insert(
270
+ 0,
271
+ "A blueprint with this path already exists - pass overwrite=true to re-import it",
272
+ )
273
+
274
+ raise_tool_error(
275
+ create_error_response(
276
+ ErrorCode.RESOURCE_ALREADY_EXISTS
277
+ if already_exists
278
+ else ErrorCode.SERVICE_CALL_FAILED,
279
+ save_error,
280
+ context={"url": url, "path": path},
281
+ suggestions=suggestions,
282
+ )
283
+ )
284
+
285
+ return save_response.get("result") or {}
286
+
225
287
  @tool(
226
288
  name="ha_import_blueprint",
227
289
  tags={"Blueprints"},
@@ -240,17 +302,29 @@ class BlueprintTools:
240
302
  description="URL to import blueprint from (GitHub, Home Assistant Community, or direct YAML URL)"
241
303
  ),
242
304
  ],
305
+ overwrite: Annotated[
306
+ bool,
307
+ Field(
308
+ description="Overwrite the blueprint if it is already installed (re-import). "
309
+ "Home Assistant reloads all automations/scripts using the blueprint.",
310
+ default=False,
311
+ ),
312
+ ] = False,
243
313
  ) -> dict[str, Any]:
244
314
  """
245
315
  Import a blueprint from a URL.
246
316
 
247
317
  Imports a blueprint from GitHub, Home Assistant Community forums,
248
- or any direct URL to a blueprint YAML file.
318
+ or any direct URL to a blueprint YAML file. Set overwrite=true to
319
+ re-import a blueprint that is already installed (equivalent to the
320
+ UI's "Re-import blueprint" action) - Home Assistant then reloads all
321
+ automations/scripts that use it.
249
322
 
250
323
  EXAMPLES:
251
324
  - Import from GitHub: ha_import_blueprint("https://github.com/user/repo/blob/main/blueprint.yaml")
252
325
  - Import from HA Community: ha_import_blueprint("https://community.home-assistant.io/t/motion-light/123456")
253
326
  - Import direct YAML: ha_import_blueprint("https://example.com/my-blueprint.yaml")
327
+ - Re-import an installed blueprint: ha_import_blueprint("https://example.com/my-blueprint.yaml", overwrite=True)
254
328
 
255
329
  SUPPORTED SOURCES:
256
330
  - GitHub repository URLs (will be converted to raw URLs)
@@ -260,6 +334,7 @@ class BlueprintTools:
260
334
  RETURNS:
261
335
  - Import result with the blueprint path where it was saved
262
336
  - Blueprint metadata (name, domain, description)
337
+ - overrides_existing: true when an installed blueprint was overwritten
263
338
  - Error details if import fails
264
339
  """
265
340
  try:
@@ -328,43 +403,50 @@ class BlueprintTools:
328
403
  if not suggested_filename.endswith((".yaml", ".yml")):
329
404
  suggested_filename = suggested_filename + ".yaml"
330
405
 
331
- # Save the blueprint to disk (blueprint/import only validates)
332
- save_response = await self._client.send_websocket_message(
333
- {
334
- "type": "blueprint/save",
335
- "domain": domain,
336
- "path": suggested_filename,
337
- "yaml": raw_data,
338
- "source_url": url,
339
- }
340
- )
341
-
342
- if not save_response.get("success"):
343
- error = save_response.get("error", {})
344
- save_error = (
345
- error.get("message", str(error))
346
- if isinstance(error, dict)
347
- else str(error)
406
+ # blueprint/save does not re-run these checks (currently the
407
+ # blueprint's min Home Assistant version) - without this gate an
408
+ # unsupported blueprint saves cleanly and reports success
409
+ validation_errors = result_data.get("validation_errors")
410
+ if validation_errors:
411
+ raise_tool_error(
412
+ create_error_response(
413
+ ErrorCode.VALIDATION_FAILED,
414
+ "Blueprint failed validation: "
415
+ + "; ".join(str(e) for e in validation_errors),
416
+ context={"url": url, "validation_errors": validation_errors},
417
+ suggestions=[
418
+ "The blueprint is not compatible with this Home Assistant installation",
419
+ "Update Home Assistant to satisfy the blueprint's minimum version requirement",
420
+ ],
421
+ )
348
422
  )
349
423
 
350
- suggestions = [
351
- "The blueprint was validated but could not be saved to disk",
352
- "Use ha_get_blueprint() to check if it already exists",
353
- ]
354
-
355
- if "already exists" in save_error.lower():
356
- suggestions.insert(0, "A blueprint with this path already exists")
357
-
424
+ # blueprint/import reports whether the target path is already
425
+ # installed - fail early with a re-import hint instead of letting
426
+ # blueprint/save reject the write
427
+ if result_data.get("exists") and not overwrite:
358
428
  raise_tool_error(
359
429
  create_error_response(
360
- ErrorCode.SERVICE_CALL_FAILED,
361
- save_error,
362
- context={"url": url, "path": suggested_filename},
363
- suggestions=suggestions,
430
+ ErrorCode.RESOURCE_ALREADY_EXISTS,
431
+ f"Blueprint already exists at '{suggested_filename}'. "
432
+ "Pass overwrite=true to re-import it.",
433
+ context={
434
+ "url": url,
435
+ "path": suggested_filename,
436
+ "domain": domain,
437
+ },
438
+ suggestions=[
439
+ "Call ha_import_blueprint with overwrite=true to update the installed blueprint",
440
+ "Use ha_get_blueprint() to inspect the currently installed version",
441
+ ],
364
442
  )
365
443
  )
366
444
 
367
- save_result = save_response.get("result") or {}
445
+ # Save the blueprint to disk (blueprint/import only validates)
446
+ save_result = await self._save_blueprint(
447
+ url, domain, suggested_filename, raw_data, overwrite
448
+ )
449
+ overrides_existing = save_result.get("overrides_existing", False)
368
450
 
369
451
  return {
370
452
  "success": True,
@@ -375,8 +457,12 @@ class BlueprintTools:
375
457
  "name": blueprint_meta.get("name"),
376
458
  "description": blueprint_meta.get("description"),
377
459
  },
378
- "overrides_existing": save_result.get("overrides_existing", False),
379
- "message": "Blueprint imported successfully. Use ha_get_blueprint() to see all installed blueprints.",
460
+ "overrides_existing": overrides_existing,
461
+ "message": (
462
+ "Blueprint re-imported successfully. Automations/scripts using it were reloaded."
463
+ if overrides_existing
464
+ else "Blueprint imported successfully. Use ha_get_blueprint() to see all installed blueprints."
465
+ ),
380
466
  }
381
467
 
382
468
  except ToolError:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ha-mcp-dev
3
- Version: 7.12.3.dev1949
3
+ Version: 7.13.0.dev1955
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