ha-mcp-dev 7.14.2.dev2181__tar.gz → 7.14.2.dev2185__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 (192) hide show
  1. {ha_mcp_dev-7.14.2.dev2181/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.14.2.dev2185}/PKG-INFO +1 -1
  2. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/pyproject.toml +1 -1
  3. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/tools_service.py +147 -16
  4. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/util_helpers.py +11 -2
  5. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
  6. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/LICENSE +0 -0
  7. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/MANIFEST.in +0 -0
  8. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/README.md +0 -0
  9. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/setup.cfg +0 -0
  10. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/__init__.py +0 -0
  11. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/__main__.py +0 -0
  12. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/_pypi_marker +0 -0
  13. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/_version.py +0 -0
  14. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/auth/__init__.py +0 -0
  15. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/auth/consent_form.py +0 -0
  16. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/auth/provider.py +0 -0
  17. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/backup_manager.py +0 -0
  18. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/browser_landing.py +0 -0
  19. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/client/__init__.py +0 -0
  20. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/client/rest_client.py +0 -0
  21. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/client/supervisor_client.py +0 -0
  22. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/client/websocket_client.py +0 -0
  23. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/client/websocket_listener.py +0 -0
  24. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/config.py +0 -0
  25. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/dashboard_screenshot/__init__.py +0 -0
  26. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/dashboard_screenshot/capture.py +0 -0
  27. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/dashboard_screenshot/content.py +0 -0
  28. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/dashboard_screenshot/paths.py +0 -0
  29. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/dashboard_screenshot/provision.py +0 -0
  30. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/dashboard_screenshot/theme_guard.py +0 -0
  31. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/errors.py +0 -0
  32. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/llm_exposure.py +0 -0
  33. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/policy/__init__.py +0 -0
  34. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/policy/approval_queue.py +0 -0
  35. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/policy/evaluator.py +0 -0
  36. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/policy/handlers.py +0 -0
  37. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/policy/middleware.py +0 -0
  38. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/policy/model.py +0 -0
  39. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/policy/persistence.py +0 -0
  40. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/policy/value_sources.py +0 -0
  41. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/py.typed +0 -0
  42. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/read_only.py +0 -0
  43. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
  44. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
  45. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
  46. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
  47. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
  48. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
  49. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
  50. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
  51. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
  52. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
  53. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
  54. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
  55. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/appdaemon.md +0 -0
  56. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
  57. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/blueprint-guide.md +0 -0
  58. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
  59. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
  60. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
  61. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
  62. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
  63. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
  64. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
  65. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/scenes.md +0 -0
  66. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
  67. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/yaml-only-integrations.md +0 -0
  68. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/server.py +0 -0
  69. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/settings_ui/__init__.py +0 -0
  70. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/settings_ui/_handlers_advanced.py +0 -0
  71. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/settings_ui/_handlers_backups.py +0 -0
  72. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/settings_ui/_handlers_fs.py +0 -0
  73. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/settings_ui/_handlers_server.py +0 -0
  74. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/settings_ui/_handlers_theme.py +0 -0
  75. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/settings_ui/_handlers_tools.py +0 -0
  76. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/settings_ui/_i18n.py +0 -0
  77. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/settings_ui/_persistence.py +0 -0
  78. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/settings_ui/_supervisor.py +0 -0
  79. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/settings_ui/_theme.py +0 -0
  80. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/settings_ui/_tools_meta.py +0 -0
  81. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/settings_ui/locales/README.md +0 -0
  82. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/settings_ui/locales/de.json +0 -0
  83. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/settings_ui/locales/en.json +0 -0
  84. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/settings_ui/locales/es.json +0 -0
  85. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/settings_ui/locales/fr.json +0 -0
  86. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/settings_ui/locales/it.json +0 -0
  87. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/settings_ui/locales/ru.json +0 -0
  88. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/settings_ui/locales/zh-Hans.json +0 -0
  89. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/settings_ui/settings.css +0 -0
  90. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/settings_ui/settings.html +0 -0
  91. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/settings_ui/settings.js +0 -0
  92. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/smoke_test.py +0 -0
  93. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
  94. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/strict_bps.py +0 -0
  95. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/__init__.py +0 -0
  96. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/auto_backup.py +0 -0
  97. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/backup.py +0 -0
  98. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/best_practice_checker.py +0 -0
  99. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/component_api.py +0 -0
  100. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/component_config_reads.py +0 -0
  101. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/component_devices.py +0 -0
  102. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/component_registries.py +0 -0
  103. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/component_registry_lookup.py +0 -0
  104. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/config_entry_flow.py +0 -0
  105. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/device_control.py +0 -0
  106. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/helpers.py +0 -0
  107. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/radio/__init__.py +0 -0
  108. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/radio/base.py +0 -0
  109. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/radio/matter.py +0 -0
  110. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/radio/thread.py +0 -0
  111. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/radio/zigbee.py +0 -0
  112. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/radio/zwave.py +0 -0
  113. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/reference_validator.py +0 -0
  114. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/registry.py +0 -0
  115. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/smart_search/__init__.py +0 -0
  116. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/smart_search/_base.py +0 -0
  117. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/smart_search/_config.py +0 -0
  118. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/smart_search/_deep.py +0 -0
  119. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/smart_search/_entities.py +0 -0
  120. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/smart_search/_fetch.py +0 -0
  121. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/smart_search/_overview.py +0 -0
  122. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/smart_search/_scenes.py +0 -0
  123. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/smart_search/_scoring.py +0 -0
  124. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/tool_search_hint_middleware.py +0 -0
  125. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/tools_addons.py +0 -0
  126. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/tools_areas.py +0 -0
  127. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/tools_blueprints.py +0 -0
  128. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/tools_bug_report.py +0 -0
  129. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/tools_calendar.py +0 -0
  130. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/tools_camera.py +0 -0
  131. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/tools_categories.py +0 -0
  132. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/tools_code.py +0 -0
  133. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/tools_config_automations.py +0 -0
  134. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
  135. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
  136. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
  137. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
  138. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/tools_dashboard_screenshot.py +0 -0
  139. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/tools_dev.py +0 -0
  140. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/tools_energy.py +0 -0
  141. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/tools_entities.py +0 -0
  142. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/tools_filesystem.py +0 -0
  143. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/tools_groups.py +0 -0
  144. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/tools_hacs.py +0 -0
  145. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/tools_history.py +0 -0
  146. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/tools_integrations.py +0 -0
  147. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/tools_labels.py +0 -0
  148. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/tools_radio.py +0 -0
  149. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/tools_registry.py +0 -0
  150. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/tools_resources.py +0 -0
  151. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/tools_search.py +0 -0
  152. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/tools_services.py +0 -0
  153. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/tools_system.py +0 -0
  154. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/tools_themes.py +0 -0
  155. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/tools_todo.py +0 -0
  156. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/tools_traces.py +0 -0
  157. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/tools_updates.py +0 -0
  158. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/tools_utility.py +0 -0
  159. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
  160. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
  161. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/tools_yaml_read.py +0 -0
  162. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/tools_zones.py +0 -0
  163. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/tools/validation_middleware.py +0 -0
  164. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/transforms/__init__.py +0 -0
  165. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/transforms/categorized_search.py +0 -0
  166. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
  167. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/transport_security.py +0 -0
  168. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/update_check.py +0 -0
  169. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/utils/__init__.py +0 -0
  170. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/utils/config_hash.py +0 -0
  171. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/utils/config_write_lock.py +0 -0
  172. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/utils/data_paths.py +0 -0
  173. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/utils/domain_handlers.py +0 -0
  174. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/utils/fuzzy_search.py +0 -0
  175. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
  176. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/utils/operation_manager.py +0 -0
  177. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/utils/python_sandbox.py +0 -0
  178. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/utils/skill_loader.py +0 -0
  179. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/utils/usage_logger.py +0 -0
  180. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/visibility/__init__.py +0 -0
  181. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/visibility/enforcement.py +0 -0
  182. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/visibility/model.py +0 -0
  183. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/visibility/persistence.py +0 -0
  184. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp/visibility/resolver.py +0 -0
  185. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
  186. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
  187. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
  188. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
  189. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
  190. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/tests/__init__.py +0 -0
  191. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/tests/test_constants.py +0 -0
  192. {ha_mcp_dev-7.14.2.dev2181 → ha_mcp_dev-7.14.2.dev2185}/tests/test_env_manager.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ha-mcp-dev
3
- Version: 7.14.2.dev2181
3
+ Version: 7.14.2.dev2185
4
4
  Summary: Home Assistant MCP Server - Complete control of Home Assistant through MCP
5
5
  Author-email: Julien <github@qc-h.net>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ha-mcp-dev"
7
- version = "7.14.2.dev2181"
7
+ version = "7.14.2.dev2185"
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
@@ -151,6 +151,32 @@ _NON_STATE_CHANGING_DOMAINS = {
151
151
  "system_log",
152
152
  }
153
153
 
154
+ #: Emitted when a ``return_response=true`` reply is not HA's
155
+ #: ``{"changed_states": [...], "service_response": ...}`` envelope. Without it an
156
+ #: empty ``result`` reads as an affirmative "no entity states changed" rather than
157
+ #: "the records could not be separated out". Module-level so the tests that
158
+ #: pin the behaviour assert against this exact string instead of a prose fragment
159
+ #: that goes tautologically green when the wording is edited.
160
+ _NON_ENVELOPE_WARNING = (
161
+ "Home Assistant's return_response reply did not match the expected "
162
+ "{changed_states, service_response} envelope, so no changed-state records "
163
+ "could be separated from the response data. An empty 'result' here does NOT "
164
+ "mean nothing changed — the whole reply is reported under 'service_response'."
165
+ )
166
+
167
+ #: Emitted when the component served a ``return_response`` call but returned no
168
+ #: ``service_response`` key. The component only sets that key for a non-null
169
+ #: response, so an absent key is ambiguous server-side: the service may genuinely
170
+ #: have returned nothing, or the component may have discarded the response on its
171
+ #: dispatched-but-unconfirmed path. Only raised alongside ``partial`` — the case
172
+ #: where the discard is actually possible — so a plain null response stays quiet.
173
+ _COMPONENT_RESPONSE_UNCONFIRMED_WARNING = (
174
+ "The service was dispatched but its confirmation did not arrive, and no "
175
+ "response data came back with it. A null 'service_response' here does NOT "
176
+ "prove the service returned nothing — the response may have been produced "
177
+ "and lost with the confirmation. Re-read state to confirm the outcome."
178
+ )
179
+
154
180
  # ``_SERVICE_TO_STATE`` (the service -> expected primary-state map) is the single
155
181
  # source of truth in ``util_helpers`` — imported above and shared with the bulk
156
182
  # consumer (``device_control``) so both write paths hand the component the same
@@ -343,9 +369,18 @@ class ServiceTools:
343
369
  service: str,
344
370
  entity_id: str | None,
345
371
  data: str | dict[str, Any] | None,
372
+ *,
373
+ return_response: bool = False,
346
374
  ) -> dict[str, Any]:
347
- """Build a partial-success response for service call timeouts."""
348
- return {
375
+ """Build a partial-success response for service call timeouts.
376
+
377
+ When ``return_response`` was requested the key is emitted here too, so a
378
+ caller never has to branch on whether the key exists: every successful
379
+ reply carries it. It is necessarily null — the reply never arrived — and
380
+ that null proves nothing about what the service returned, so it comes
381
+ with the same ambiguity warning the component's unconfirmed path uses.
382
+ """
383
+ response: dict[str, Any] = {
349
384
  "success": True,
350
385
  "partial": True,
351
386
  "domain": domain,
@@ -366,6 +401,10 @@ class ServiceTools:
366
401
  "The service was dispatched and may still be executing."
367
402
  ],
368
403
  }
404
+ if return_response:
405
+ response["service_response"] = None
406
+ response["warnings"].append(_COMPONENT_RESPONSE_UNCONFIRMED_WARNING)
407
+ return response
369
408
 
370
409
  async def _capture_initial_state(self, entity_id: str | None) -> str | None:
371
410
  """Capture the current state of an entity before a service call."""
@@ -406,6 +445,54 @@ class ServiceTools:
406
445
  f"Service executed but state verification failed: {e}"
407
446
  )
408
447
 
448
+ @staticmethod
449
+ def _split_return_response_envelope(
450
+ result: Any, *, return_response: bool
451
+ ) -> tuple[Any, Any, bool, list[str]]:
452
+ """Split HA's reply into (changed states, response, present, warnings).
453
+
454
+ HA answers ``return_response=true`` with an envelope
455
+ ``{"changed_states": [...], "service_response": ...}``. The response data
456
+ belongs at the top level of ha_call_service's reply exactly ONCE — the
457
+ placement the component path (``_build_component_call_response``) already
458
+ uses — so it is peeled off here, BEFORE projection, leaving only the
459
+ changed states to project into ``result``. Returning it in both places
460
+ shipped it twice, byte-identical, doubling its token cost (issue #2085).
461
+
462
+ A legitimately null ``service_response`` is still reported as present
463
+ (``True``) so the caller emits the key.
464
+
465
+ With ``return_response`` false there is no envelope to split: HA returns a
466
+ plain changed-states list, so it passes through untouched and no
467
+ ``service_response`` key is emitted (``present`` False). That is the
468
+ overwhelming majority of calls.
469
+
470
+ Anything else — a non-dict reply, a missing key, or a ``changed_states``
471
+ that is not a list — means a non-conforming responder (HA core always
472
+ sends both keys, with a list). Every such shape takes ONE path: the whole
473
+ reply becomes the response data, ``result`` stays empty, and a warning
474
+ says so. Uniformity is the point — peeling a recognised key out of an
475
+ unrecognised envelope would silently discard whatever else it carried,
476
+ and an empty ``result`` would otherwise read as an affirmative "no entity
477
+ states changed". A caller that asked for response data must never get back
478
+ neither the data nor an explanation.
479
+ """
480
+ if not return_response:
481
+ return result, None, False, []
482
+ if (
483
+ isinstance(result, dict)
484
+ and "service_response" in result
485
+ and isinstance(result.get("changed_states"), list)
486
+ ):
487
+ return result["changed_states"], result["service_response"], True, []
488
+ # Any other shape is non-conforming. Hand the WHOLE reply back as the
489
+ # response data rather than guessing which part is which: splitting one
490
+ # recognised key out of an unrecognised envelope discards the rest (a
491
+ # non-list ``changed_states`` would vanish entirely) and would make the
492
+ # warning's "the whole reply is reported under 'service_response'" a lie.
493
+ # Projecting nothing into ``result`` keeps the records from shipping twice.
494
+ return [], result, True, [_NON_ENVELOPE_WARNING]
495
+
409
496
  @staticmethod
410
497
  def _project_service_result(
411
498
  result: Any,
@@ -420,6 +507,10 @@ class ServiceTools:
420
507
  Issue #1446. Precedence:
421
508
 
422
509
  - ``verbose=True``: bypass every transformation; return ``result`` as-is.
510
+ (``result`` here is always changed-state records, never an envelope:
511
+ the legacy path splits the envelope off before calling this — see
512
+ ``_split_return_response_envelope`` — and the component path passes
513
+ transition ``new_state``s, which are never enveloped to begin with.)
423
514
  - Explicit ``fields`` or ``attribute_keys``: apply per-record projection
424
515
  via ``project_entity_record`` to every record. No compaction; this is
425
516
  the power-user path.
@@ -470,6 +561,7 @@ class ServiceTools:
470
561
  service: str,
471
562
  entity_id: str | None,
472
563
  data: str | dict[str, Any] | None,
564
+ return_response: bool = False,
473
565
  ) -> dict[str, Any]:
474
566
  """Handle a HomeAssistantConnectionError raised while calling a service.
475
567
 
@@ -481,7 +573,9 @@ class ServiceTools:
481
573
  # mean the service was dispatched but HA didn't respond in time.
482
574
  # The operation is likely still running (e.g., update.install, long automations).
483
575
  if isinstance(error.__cause__, httpx.TimeoutException):
484
- return self._build_timeout_response(domain, service, entity_id, data)
576
+ return self._build_timeout_response(
577
+ domain, service, entity_id, data, return_response=return_response
578
+ )
485
579
  # Non-timeout connection errors are real failures
486
580
  exception_to_structured_error(
487
581
  error,
@@ -892,8 +986,23 @@ class ServiceTools:
892
986
  }
893
987
  if projection_warnings:
894
988
  response.setdefault("warnings", []).extend(projection_warnings)
895
- if return_response and component_result.get("service_response") is not None:
896
- response["service_response"] = component_result["service_response"]
989
+ if return_response:
990
+ # Emit the key whenever it was requested, even for a null response
991
+ # the legacy path does the same (``_split_return_response_envelope``),
992
+ # and gating on ``is not None`` made the two paths answer the same
993
+ # call with different shapes. The component only sets the key for a
994
+ # non-null response, so a null one arrives as an absent key here.
995
+ response["service_response"] = component_result.get("service_response")
996
+ # ...which makes an absent key ambiguous: genuinely-null response, or
997
+ # one the component produced and dropped with the confirmation on its
998
+ # dispatched-unconfirmed path. Only the latter is possible when
999
+ # ``partial``, so warn there rather than on every null.
1000
+ if "service_response" not in component_result and component_result.get(
1001
+ "partial"
1002
+ ):
1003
+ response.setdefault("warnings", []).append(
1004
+ _COMPONENT_RESPONSE_UNCONFIRMED_WARNING
1005
+ )
897
1006
  if should_wait:
898
1007
  if component_result.get("partial"):
899
1008
  # Dispatched, but the confirming state_changed did not arrive within
@@ -965,7 +1074,9 @@ class ServiceTools:
965
1074
  return_response=return_response,
966
1075
  )
967
1076
  if isinstance(component_result, _AmbiguousDispatch):
968
- return self._build_timeout_response(domain, service, entity_id, data)
1077
+ return self._build_timeout_response(
1078
+ domain, service, entity_id, data, return_response=return_response
1079
+ )
969
1080
  if component_result is None:
970
1081
  return None
971
1082
  return self._build_component_call_response(
@@ -1003,9 +1114,12 @@ class ServiceTools:
1003
1114
  bool,
1004
1115
  Field(
1005
1116
  description=(
1006
- "Return HA's raw service response unchanged (default: False). "
1007
- "Use as an escape hatch when you need the full propagation "
1008
- "chain or raw attribute payload (debug / inspection). "
1117
+ "Return HA's raw changed-state records unchanged (default: "
1118
+ "False). Use as an escape hatch when you need the full "
1119
+ "propagation chain or raw attribute payload (debug / "
1120
+ "inspection). With return_response=True the response data "
1121
+ "still surfaces once as the top-level service_response key, "
1122
+ "never nested in result. "
1009
1123
  "WARNING: brings back token-bloat for nested-group targets — "
1010
1124
  "prefer result_fields / result_attribute_keys for targeted control."
1011
1125
  ),
@@ -1083,8 +1197,12 @@ class ServiceTools:
1083
1197
  to the targeted entity's record (drops parent-group propagation) and
1084
1198
  stripped of ``context`` / ``last_*`` metadata and heavy attribute
1085
1199
  lists (``effect_list``, ``hue_scenes``). Escape hatches: ``verbose=True``
1086
- for the raw HA response, or ``result_fields`` / ``result_attribute_keys``
1087
- for explicit per-record projection (mirrors ``ha_get_state``).
1200
+ for the raw changed-state records, or ``result_fields`` /
1201
+ ``result_attribute_keys`` for explicit per-record projection (mirrors
1202
+ ``ha_get_state``).
1203
+ - **return_response** (default False): the service's response data is
1204
+ returned once, as the top-level ``service_response`` key — never nested
1205
+ inside ``result``, which carries the changed entity states.
1088
1206
 
1089
1207
  **For detailed service documentation, use ha_get_skill_guide.**
1090
1208
 
@@ -1177,6 +1295,15 @@ class ServiceTools:
1177
1295
  domain, service, service_data, return_response=return_response_bool
1178
1296
  )
1179
1297
 
1298
+ # Peel the return_response envelope apart BEFORE projection so the
1299
+ # response data is emitted once, top-level, and only the changed
1300
+ # states reach ``result`` (issue #2085).
1301
+ result, service_response, has_response_envelope, envelope_warnings = (
1302
+ self._split_return_response_envelope(
1303
+ result, return_response=return_response_bool
1304
+ )
1305
+ )
1306
+
1180
1307
  projected_result, projection_warnings = self._project_service_result(
1181
1308
  result,
1182
1309
  entity_id=entity_id,
@@ -1194,12 +1321,12 @@ class ServiceTools:
1194
1321
  "result": projected_result,
1195
1322
  "message": f"Successfully executed {domain}.{service}",
1196
1323
  }
1197
- if projection_warnings:
1198
- response.setdefault("warnings", []).extend(projection_warnings)
1324
+ call_warnings = [*projection_warnings, *envelope_warnings]
1325
+ if call_warnings:
1326
+ response.setdefault("warnings", []).extend(call_warnings)
1199
1327
 
1200
- # If return_response was requested, include the service_response key prominently
1201
- if return_response_bool and isinstance(result, dict):
1202
- response["service_response"] = result.get("service_response", result)
1328
+ if has_response_envelope:
1329
+ response["service_response"] = service_response
1203
1330
 
1204
1331
  # Wait for entity state to change
1205
1332
  if should_wait and entity_id is not None:
@@ -1218,6 +1345,10 @@ class ServiceTools:
1218
1345
  service=service,
1219
1346
  entity_id=entity_id,
1220
1347
  data=data,
1348
+ # The parameter, not ``return_response_bool`` — that local is
1349
+ # bound inside the try and would be unbound if the error fired
1350
+ # before it.
1351
+ return_response=return_response,
1221
1352
  )
1222
1353
  except ToolError:
1223
1354
  raise
@@ -346,8 +346,9 @@ def compact_service_result(
346
346
  3. Drop known-heavy attribute keys (``effect_list``, ``hue_scenes``) from
347
347
  every record's ``attributes`` dict.
348
348
 
349
- Returns ``result`` unchanged when not a list (e.g. dict from
350
- ``return_response=True`` services), or when the list is empty.
349
+ Returns ``result`` unchanged when it is not a list (defensive every
350
+ ha_call_service path now projects a changed-state list), or when the list
351
+ is empty.
351
352
  """
352
353
  if not isinstance(result, list) or not result:
353
354
  return result
@@ -496,6 +497,14 @@ def unwrap_service_response(result: dict[str, Any]) -> dict[str, Any]:
496
497
  HA's call_service with return_response wraps results in
497
498
  {"changed_states": [...], "service_response": {...}}.
498
499
  Returns service_response if present and is a dict, otherwise the original result.
500
+
501
+ Deliberately NOT the same rule as ``ServiceTools._split_return_response_envelope``,
502
+ which powers ha_call_service: that one returns the response whatever its type and
503
+ reports the whole reply only when the key is absent. The two disagree solely for a
504
+ NON-DICT ``service_response`` (this helper hands back the envelope, the split hands
505
+ back the value). Consumers here read component services that always answer with a
506
+ dict, so the divergence is unreachable — but do not "align" one to the other
507
+ without checking those ~20 call sites.
499
508
  """
500
509
  sr = result.get("service_response")
501
510
  return sr if isinstance(sr, dict) else result
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ha-mcp-dev
3
- Version: 7.14.2.dev2181
3
+ Version: 7.14.2.dev2185
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