ha-mcp-dev 7.14.2.dev2093__tar.gz → 7.14.2.dev2095__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 (188) hide show
  1. {ha_mcp_dev-7.14.2.dev2093/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.14.2.dev2095}/PKG-INFO +1 -1
  2. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/pyproject.toml +1 -1
  3. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/dashboard_screenshot/paths.py +66 -40
  4. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_bug_report.py +36 -4
  5. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_config_dashboards.py +165 -36
  6. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
  7. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/LICENSE +0 -0
  8. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/MANIFEST.in +0 -0
  9. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/README.md +0 -0
  10. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/setup.cfg +0 -0
  11. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/__init__.py +0 -0
  12. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/__main__.py +0 -0
  13. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/_pypi_marker +0 -0
  14. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/_version.py +0 -0
  15. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/auth/__init__.py +0 -0
  16. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/auth/consent_form.py +0 -0
  17. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/auth/provider.py +0 -0
  18. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/backup_manager.py +0 -0
  19. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/browser_landing.py +0 -0
  20. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/client/__init__.py +0 -0
  21. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/client/rest_client.py +0 -0
  22. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/client/supervisor_client.py +0 -0
  23. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/client/websocket_client.py +0 -0
  24. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/client/websocket_listener.py +0 -0
  25. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/config.py +0 -0
  26. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/dashboard_screenshot/__init__.py +0 -0
  27. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/dashboard_screenshot/capture.py +0 -0
  28. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/dashboard_screenshot/content.py +0 -0
  29. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/dashboard_screenshot/provision.py +0 -0
  30. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/dashboard_screenshot/theme_guard.py +0 -0
  31. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/errors.py +0 -0
  32. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/llm_exposure.py +0 -0
  33. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/policy/__init__.py +0 -0
  34. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/policy/approval_queue.py +0 -0
  35. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/policy/evaluator.py +0 -0
  36. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/policy/handlers.py +0 -0
  37. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/policy/middleware.py +0 -0
  38. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/policy/model.py +0 -0
  39. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/policy/persistence.py +0 -0
  40. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/policy/value_sources.py +0 -0
  41. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/py.typed +0 -0
  42. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/read_only.py +0 -0
  43. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
  44. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
  45. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
  46. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
  47. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
  48. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
  49. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
  50. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
  51. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
  52. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
  53. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
  54. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
  55. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/appdaemon.md +0 -0
  56. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
  57. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/blueprint-guide.md +0 -0
  58. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
  59. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
  60. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
  61. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
  62. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
  63. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
  64. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
  65. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/scenes.md +0 -0
  66. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
  67. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/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.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/server.py +0 -0
  69. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/settings_ui/__init__.py +0 -0
  70. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/settings_ui/_handlers_advanced.py +0 -0
  71. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/settings_ui/_handlers_backups.py +0 -0
  72. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/settings_ui/_handlers_fs.py +0 -0
  73. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/settings_ui/_handlers_server.py +0 -0
  74. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/settings_ui/_handlers_theme.py +0 -0
  75. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/settings_ui/_handlers_tools.py +0 -0
  76. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/settings_ui/_i18n.py +0 -0
  77. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/settings_ui/_persistence.py +0 -0
  78. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/settings_ui/_supervisor.py +0 -0
  79. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/settings_ui/_theme.py +0 -0
  80. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/settings_ui/_tools_meta.py +0 -0
  81. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/settings_ui/locales/README.md +0 -0
  82. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/settings_ui/locales/de.json +0 -0
  83. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/settings_ui/locales/en.json +0 -0
  84. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/settings_ui/locales/ru.json +0 -0
  85. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/settings_ui/locales/zh-Hans.json +0 -0
  86. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/settings_ui/settings.css +0 -0
  87. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/settings_ui/settings.html +0 -0
  88. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/settings_ui/settings.js +0 -0
  89. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/smoke_test.py +0 -0
  90. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
  91. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/strict_bps.py +0 -0
  92. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/__init__.py +0 -0
  93. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/auto_backup.py +0 -0
  94. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/backup.py +0 -0
  95. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/best_practice_checker.py +0 -0
  96. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/component_api.py +0 -0
  97. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/component_config_reads.py +0 -0
  98. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/component_devices.py +0 -0
  99. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/component_registries.py +0 -0
  100. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/component_registry_lookup.py +0 -0
  101. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/config_entry_flow.py +0 -0
  102. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/device_control.py +0 -0
  103. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/enhanced.py +0 -0
  104. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/helpers.py +0 -0
  105. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/radio/__init__.py +0 -0
  106. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/radio/base.py +0 -0
  107. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/radio/matter.py +0 -0
  108. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/radio/thread.py +0 -0
  109. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/radio/zigbee.py +0 -0
  110. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/radio/zwave.py +0 -0
  111. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/reference_validator.py +0 -0
  112. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/registry.py +0 -0
  113. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/smart_search/__init__.py +0 -0
  114. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/smart_search/_base.py +0 -0
  115. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/smart_search/_config.py +0 -0
  116. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/smart_search/_deep.py +0 -0
  117. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/smart_search/_entities.py +0 -0
  118. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/smart_search/_fetch.py +0 -0
  119. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/smart_search/_overview.py +0 -0
  120. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/smart_search/_scenes.py +0 -0
  121. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/smart_search/_scoring.py +0 -0
  122. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tool_search_hint_middleware.py +0 -0
  123. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_addons.py +0 -0
  124. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_areas.py +0 -0
  125. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_blueprints.py +0 -0
  126. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_calendar.py +0 -0
  127. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_camera.py +0 -0
  128. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_categories.py +0 -0
  129. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_code.py +0 -0
  130. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_config_automations.py +0 -0
  131. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
  132. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
  133. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
  134. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_dashboard_screenshot.py +0 -0
  135. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_dev.py +0 -0
  136. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_energy.py +0 -0
  137. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_entities.py +0 -0
  138. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_filesystem.py +0 -0
  139. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_groups.py +0 -0
  140. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_hacs.py +0 -0
  141. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_history.py +0 -0
  142. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_integrations.py +0 -0
  143. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_labels.py +0 -0
  144. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_radio.py +0 -0
  145. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_registry.py +0 -0
  146. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_resources.py +0 -0
  147. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_search.py +0 -0
  148. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_service.py +0 -0
  149. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_services.py +0 -0
  150. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_system.py +0 -0
  151. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_themes.py +0 -0
  152. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_todo.py +0 -0
  153. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_traces.py +0 -0
  154. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_updates.py +0 -0
  155. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_utility.py +0 -0
  156. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
  157. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
  158. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_yaml_read.py +0 -0
  159. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/tools_zones.py +0 -0
  160. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/util_helpers.py +0 -0
  161. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/tools/validation_middleware.py +0 -0
  162. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/transforms/__init__.py +0 -0
  163. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/transforms/categorized_search.py +0 -0
  164. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
  165. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/transport_security.py +0 -0
  166. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/update_check.py +0 -0
  167. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/utils/__init__.py +0 -0
  168. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/utils/config_hash.py +0 -0
  169. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/utils/data_paths.py +0 -0
  170. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/utils/domain_handlers.py +0 -0
  171. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/utils/fuzzy_search.py +0 -0
  172. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
  173. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/utils/operation_manager.py +0 -0
  174. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/utils/python_sandbox.py +0 -0
  175. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/utils/skill_loader.py +0 -0
  176. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/utils/usage_logger.py +0 -0
  177. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/visibility/__init__.py +0 -0
  178. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/visibility/model.py +0 -0
  179. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/visibility/persistence.py +0 -0
  180. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp/visibility/resolver.py +0 -0
  181. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
  182. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
  183. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
  184. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
  185. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
  186. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/tests/__init__.py +0 -0
  187. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/tests/test_constants.py +0 -0
  188. {ha_mcp_dev-7.14.2.dev2093 → ha_mcp_dev-7.14.2.dev2095}/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.dev2093
3
+ Version: 7.14.2.dev2095
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.dev2093"
7
+ version = "7.14.2.dev2095"
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
@@ -347,42 +347,28 @@ async def fetch_dashboard_render_config(
347
347
  return cast(dict[str, Any], config)
348
348
 
349
349
 
350
- def resolve_dashboard_view(
350
+ def match_dashboard_view(
351
351
  dashboard_url_path: str,
352
352
  config: dict[str, Any],
353
- view_path: str | None,
354
- ) -> DashboardRenderTarget:
355
- """Resolve one named Lovelace view to a canonical frontend route."""
356
- base_path = dashboard_frontend_path(dashboard_url_path)
357
- if view_path is None:
358
- views = config.get("views")
359
- has_static_views = isinstance(views, list) and bool(views)
360
- if "strategy" in config:
361
- warning = (
362
- "No view_path was supplied; this strategy dashboard generates "
363
- "its views at runtime, so only the base route is available."
364
- )
365
- elif has_static_views:
366
- warning = (
367
- "No view_path was supplied; the dashboard base route renders "
368
- "the first view visible to Puppet's Home Assistant user."
369
- )
370
- else:
371
- warning = (
372
- "No view_path was supplied and the dashboard has no static views; "
373
- "only the base route is available."
374
- )
375
- return DashboardRenderTarget(
376
- dashboard_url_path=base_path,
377
- view_path=None,
378
- render_path=base_path,
379
- view_index=None,
380
- stable=False,
381
- warnings=(warning,),
382
- )
383
-
384
- cleaned_view_path = view_path
385
- if not cleaned_view_path.strip():
353
+ view_path: str,
354
+ *,
355
+ strategy_suggestions: tuple[str, ...] = (
356
+ "Use dashboard_path with the frontend route instead",
357
+ ),
358
+ ) -> tuple[int, dict[str, Any]]:
359
+ """Match one named Lovelace view by its configured ``views[].path``.
360
+
361
+ Shared by screenshot view resolution and get-mode view scoping so both
362
+ honour identical semantics: exact match on the configured path, with
363
+ structured errors for an empty path, a strategy dashboard (no static
364
+ views to match), and a missing/ambiguous path (the not-found error lists
365
+ the available paths). Returns ``(view_index, view)``.
366
+
367
+ ``strategy_suggestions`` lets each caller phrase the strategy-dashboard
368
+ remediation in its own vocabulary (the screenshot tools have a
369
+ ``dashboard_path`` route parameter; config scoping does not).
370
+ """
371
+ if not view_path.strip():
386
372
  raise_tool_error(
387
373
  create_error_response(
388
374
  ErrorCode.VALIDATION_INVALID_PARAMETER,
@@ -397,9 +383,9 @@ def resolve_dashboard_view(
397
383
  "Strategy dashboards do not expose static named view paths.",
398
384
  context={
399
385
  "dashboard_url_path": dashboard_url_path,
400
- "view_path": cleaned_view_path,
386
+ "view_path": view_path,
401
387
  },
402
- suggestions=["Use dashboard_path with the frontend route instead"],
388
+ suggestions=list(strategy_suggestions),
403
389
  )
404
390
  )
405
391
  views = config.get("views")
@@ -408,7 +394,7 @@ def resolve_dashboard_view(
408
394
  matches = [
409
395
  (index, view)
410
396
  for index, view in enumerate(views)
411
- if _configured_view_path(view) == cleaned_view_path
397
+ if _configured_view_path(view) == view_path
412
398
  ]
413
399
  if len(matches) != 1:
414
400
  available = [
@@ -420,17 +406,57 @@ def resolve_dashboard_view(
420
406
  raise_tool_error(
421
407
  create_error_response(
422
408
  ErrorCode.RESOURCE_NOT_FOUND,
423
- f"Dashboard view path '{cleaned_view_path}' is {reason}.",
409
+ f"Dashboard view path '{view_path}' is {reason}.",
424
410
  context={
425
411
  "dashboard_url_path": dashboard_url_path,
426
- "view_path": cleaned_view_path,
412
+ "view_path": view_path,
427
413
  "available_view_paths": available,
428
414
  },
429
415
  suggestions=["Use a view_path returned by ha_config_get_dashboard"],
430
416
  )
431
417
  )
418
+ return matches[0]
419
+
432
420
 
433
- view_index, _ = matches[0]
421
+ def resolve_dashboard_view(
422
+ dashboard_url_path: str,
423
+ config: dict[str, Any],
424
+ view_path: str | None,
425
+ ) -> DashboardRenderTarget:
426
+ """Resolve one named Lovelace view to a canonical frontend route."""
427
+ base_path = dashboard_frontend_path(dashboard_url_path)
428
+ if view_path is None:
429
+ views = config.get("views")
430
+ has_static_views = isinstance(views, list) and bool(views)
431
+ if "strategy" in config:
432
+ warning = (
433
+ "No view_path was supplied; this strategy dashboard generates "
434
+ "its views at runtime, so only the base route is available."
435
+ )
436
+ elif has_static_views:
437
+ warning = (
438
+ "No view_path was supplied; the dashboard base route renders "
439
+ "the first view visible to Puppet's Home Assistant user."
440
+ )
441
+ else:
442
+ warning = (
443
+ "No view_path was supplied and the dashboard has no static views; "
444
+ "only the base route is available."
445
+ )
446
+ return DashboardRenderTarget(
447
+ dashboard_url_path=base_path,
448
+ view_path=None,
449
+ render_path=base_path,
450
+ view_index=None,
451
+ stable=False,
452
+ warnings=(warning,),
453
+ )
454
+
455
+ view_index, _ = match_dashboard_view(dashboard_url_path, config, view_path)
456
+ cleaned_view_path = view_path
457
+ views = config.get("views")
458
+ if not isinstance(views, list):
459
+ views = []
434
460
  render_path = _safe_named_render_path(base_path, cleaned_view_path)
435
461
  if render_path is None:
436
462
  return _fallback_view_target(
@@ -34,7 +34,7 @@ from ..utils.usage_logger import (
34
34
  )
35
35
  from .component_api import get_component_caps
36
36
  from .helpers import log_tool_usage, register_tool_methods
37
- from .util_helpers import ANSI_ESCAPE_RE
37
+ from .util_helpers import ANSI_ESCAPE_RE, JSON_STRING_COERCION, project_fields
38
38
 
39
39
  logger = logging.getLogger(__name__)
40
40
 
@@ -676,10 +676,38 @@ class BugReportTools:
676
676
  ),
677
677
  ),
678
678
  ] = 10,
679
+ fields: Annotated[
680
+ str | list[str] | None,
681
+ JSON_STRING_COERCION,
682
+ Field(
683
+ default=None,
684
+ description=(
685
+ "Return only the specified top-level response keys — the "
686
+ "full response (both templates + logs + diagnostics, with "
687
+ "log content repeated across the raw keys and templates) "
688
+ "is very large. "
689
+ "None = full response. Typical for a runtime bug: "
690
+ "'runtime_bug_template,suggested_title,"
691
+ "runtime_bug_submit_url,duplicate_check_urls,"
692
+ "anonymization_guide,instructions'; for agent feedback "
693
+ "swap in agent_behavior_template and "
694
+ "agent_behavior_submit_url. The templates already embed "
695
+ "the relevant logs, so the raw log keys are only needed "
696
+ "for your own analysis. "
697
+ "Available keys: diagnostic_info, recent_logs, "
698
+ "startup_logs, addon_logs, core_error_log, log_count, "
699
+ "startup_log_count, formatted_report, "
700
+ "runtime_bug_template, agent_behavior_template, "
701
+ "anonymization_guide, suggested_title, "
702
+ "runtime_bug_submit_url, agent_behavior_submit_url, "
703
+ "duplicate_check_urls, missing_tool_hint, instructions."
704
+ ),
705
+ ),
706
+ ] = None,
679
707
  ctx: Context | None = None,
680
708
  ) -> dict[str, Any]:
681
709
  """
682
- Collect diagnostic information for filing issue reports or feedback.
710
+ Get diagnostic information and templates for filing issue reports or feedback.
683
711
 
684
712
  This tool generates templates for TWO types of reports:
685
713
  1. **Runtime Bug Report** - For ha-mcp errors, failures, unexpected behavior
@@ -709,7 +737,10 @@ class BugReportTools:
709
737
  - "That was inefficient"
710
738
 
711
739
  **OUTPUT:**
712
- Returns both templates plus diagnostic data. Key fields:
740
+ Returns both templates plus diagnostic data. The full response is
741
+ LARGE (the captured logs appear in the raw log keys AND inside each
742
+ template) — pass fields=... to fetch only the keys you need once you
743
+ know which template applies. Key fields:
713
744
  - `runtime_bug_template`, `agent_behavior_template` — pick based on context
714
745
  - `recent_logs`, `startup_logs` — captured ha-mcp tool/server log entries
715
746
  - `addon_logs` — addon container stdout/stderr (HA add-on installs only;
@@ -843,7 +874,7 @@ class BugReportTools:
843
874
  for keyword in search_keywords[:3] # Limit to top 3 keywords
844
875
  ]
845
876
 
846
- return {
877
+ result: dict[str, Any] = {
847
878
  "success": True,
848
879
  "diagnostic_info": diagnostic_info,
849
880
  "recent_logs": recent_logs,
@@ -934,6 +965,7 @@ class BugReportTools:
934
965
  "CRITICAL: Always ANONYMIZE the report BEFORE presenting it in markdown code blocks!"
935
966
  ),
936
967
  }
968
+ return project_fields(result, fields)
937
969
 
938
970
 
939
971
  def register_bug_report_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
@@ -37,6 +37,7 @@ from ..dashboard_screenshot.content import (
37
37
  from ..dashboard_screenshot.paths import (
38
38
  dashboard_frontend_path,
39
39
  dashboard_render_paths,
40
+ match_dashboard_view,
40
41
  resolve_dashboard_view,
41
42
  )
42
43
  from ..errors import ErrorCode, create_error_response
@@ -1507,19 +1508,21 @@ def _note_screenshot_ignored(
1507
1508
  options: _DashboardScreenshotOptions | None = None,
1508
1509
  mode: str,
1509
1510
  ) -> None:
1510
- """Warn when a screenshot was requested in a mode that can't render one.
1511
-
1512
- Screenshot options are only honoured in get mode. In list and search mode
1513
- they are accepted but inapplicable, so surface a
1514
- ``warnings`` entry rather than dropping the request as a silent no-op
1515
- (matches the warn-don't-fail contract the params document)."""
1511
+ """Warn when get-mode-only options are passed to a mode that ignores them.
1512
+
1513
+ Screenshot options — and ``view_path``, which is a get-mode scoping
1514
+ parameter that merely travels inside the options dataclass — are only
1515
+ honoured in get mode. In list and search mode they are accepted but
1516
+ inapplicable, so surface a ``warnings`` entry rather than dropping the
1517
+ request as a silent no-op (matches the warn-don't-fail contract the
1518
+ params document)."""
1516
1519
  capture_options = options or _DashboardScreenshotOptions(full_page=full_page)
1517
1520
  if include_screenshot or capture_options != _DashboardScreenshotOptions():
1518
1521
  result.setdefault("warnings", []).append(
1519
- f"include_screenshot and screenshot render options are ignored in {mode} "
1520
- "mode; call "
1522
+ f"include_screenshot, view_path, and screenshot render options are "
1523
+ f"ignored in {mode} mode; call "
1521
1524
  "ha_config_get_dashboard with a url_path (and no search criteria) "
1522
- "to get a screenshot."
1525
+ "to scope to a view or get a screenshot."
1523
1526
  )
1524
1527
 
1525
1528
 
@@ -1832,8 +1835,13 @@ class DashboardConfigTools:
1832
1835
  view_path: Annotated[
1833
1836
  str | None,
1834
1837
  Field(
1835
- description="With include_screenshot: stable Lovelace "
1836
- "views[].path to render. Omit to render the dashboard base route."
1838
+ description="Get mode: return ONLY the view whose "
1839
+ "Lovelace views[].path matches (response carries 'view' + "
1840
+ "'view_index' instead of the full 'config') — use this to keep "
1841
+ "multi-view dashboards from blowing up the response when you "
1842
+ "only need one view. Does not require any beta feature. With "
1843
+ "include_screenshot, also selects the view to render. Ignored "
1844
+ "in list/search mode. Omit for the full config."
1837
1845
  ),
1838
1846
  ] = None,
1839
1847
  mode: Annotated[
@@ -1880,6 +1888,13 @@ class DashboardConfigTools:
1880
1888
  MODE 3 — Get: Active when list_only=False and no search parameters are provided.
1881
1889
  Returns the full Lovelace dashboard config, defaulting to the
1882
1890
  main dashboard if url_path is omitted.
1891
+ Pass view_path=<views[].path> to return ONLY that view: the response
1892
+ then carries `view` + `view_index` instead of `config`, keeping the
1893
+ payload small on multi-view dashboards. `config_hash` still covers
1894
+ the FULL config, so a follow-up
1895
+ ha_config_set_dashboard(python_transform=...) addressing
1896
+ config['views'][view_index] validates unchanged. An unknown
1897
+ view_path errors and lists the available view paths.
1883
1898
 
1884
1899
  MODE 4 — Search all: mode="search" with query=<entity_id or text>
1885
1900
  Answers "which dashboards contain this entity/card" by walking every
@@ -1900,6 +1915,7 @@ class DashboardConfigTools:
1900
1915
  - List all dashboards: ha_config_get_dashboard(list_only=True)
1901
1916
  - Get default dashboard: ha_config_get_dashboard(url_path="default")
1902
1917
  - Get custom dashboard: ha_config_get_dashboard(url_path="lovelace-mobile")
1918
+ - Get one view only: ha_config_get_dashboard(url_path="lovelace-mobile", view_path="office")
1903
1919
  - Force reload: ha_config_get_dashboard(url_path="lovelace-home", force_reload=True)
1904
1920
  - Find cards by entity: ha_config_get_dashboard(url_path="my-dash", entity_id="light.living_room")
1905
1921
  - Find by wildcard: ha_config_get_dashboard(url_path="my-dash", entity_id="sensor.temperature_*")
@@ -1929,7 +1945,11 @@ class DashboardConfigTools:
1929
1945
  try:
1930
1946
  if mode == "search":
1931
1947
  # Cross-dashboard search takes precedence over every other mode.
1932
- return await self._get_dashboard_search_all_mode(query)
1948
+ return await self._get_dashboard_search_all_mode(
1949
+ query,
1950
+ include_screenshot=include_screenshot,
1951
+ screenshot_options=screenshot_options,
1952
+ )
1933
1953
 
1934
1954
  if list_only:
1935
1955
  return await self._get_dashboard_list_mode(
@@ -1972,6 +1992,7 @@ class DashboardConfigTools:
1972
1992
  url_path,
1973
1993
  resolved_url_path=resolved_url_path,
1974
1994
  force_reload=force_reload,
1995
+ view_path=view_path,
1975
1996
  include_screenshot=include_screenshot,
1976
1997
  screenshot_options=screenshot_options,
1977
1998
  )
@@ -2283,7 +2304,13 @@ class DashboardConfigTools:
2283
2304
  )
2284
2305
  return search_result
2285
2306
 
2286
- async def _get_dashboard_search_all_mode(self, query: str | None) -> dict[str, Any]:
2307
+ async def _get_dashboard_search_all_mode(
2308
+ self,
2309
+ query: str | None,
2310
+ *,
2311
+ include_screenshot: bool,
2312
+ screenshot_options: _DashboardScreenshotOptions,
2313
+ ) -> dict[str, Any]:
2287
2314
  """mode='search': find which storage dashboards contain ``query``.
2288
2315
 
2289
2316
  The component ``search`` walks every storage dashboard in one in-process
@@ -2325,6 +2352,12 @@ class DashboardConfigTools:
2325
2352
  f"Results capped at {_SEARCH_ALL_MATCH_CAP} matches; "
2326
2353
  "refine the query for a complete list."
2327
2354
  ]
2355
+ _note_screenshot_ignored(
2356
+ result,
2357
+ include_screenshot=include_screenshot,
2358
+ options=screenshot_options,
2359
+ mode="search",
2360
+ )
2328
2361
  return result
2329
2362
 
2330
2363
  async def _search_all_dashboards(
@@ -2434,6 +2467,7 @@ class DashboardConfigTools:
2434
2467
  *,
2435
2468
  resolved_url_path: list[str | None],
2436
2469
  force_reload: bool,
2470
+ view_path: str | None,
2437
2471
  include_screenshot: bool,
2438
2472
  screenshot_options: _DashboardScreenshotOptions,
2439
2473
  ) -> "dict[str, Any] | ToolResult":
@@ -2468,6 +2502,7 @@ class DashboardConfigTools:
2468
2502
  url_path,
2469
2503
  component_config,
2470
2504
  original_url_path=url_path,
2505
+ view_path=view_path,
2471
2506
  include_screenshot=include_screenshot,
2472
2507
  screenshot_options=screenshot_options,
2473
2508
  )
@@ -2518,6 +2553,7 @@ class DashboardConfigTools:
2518
2553
  url_path,
2519
2554
  config,
2520
2555
  original_url_path=original_url_path,
2556
+ view_path=view_path,
2521
2557
  include_screenshot=include_screenshot,
2522
2558
  screenshot_options=screenshot_options,
2523
2559
  )
@@ -2528,6 +2564,7 @@ class DashboardConfigTools:
2528
2564
  config: Any,
2529
2565
  *,
2530
2566
  original_url_path: str | None,
2567
+ view_path: str | None,
2531
2568
  include_screenshot: bool,
2532
2569
  screenshot_options: _DashboardScreenshotOptions,
2533
2570
  ) -> "dict[str, Any] | ToolResult":
@@ -2538,6 +2575,14 @@ class DashboardConfigTools:
2538
2575
  render paths, ``resolved_from`` (when the legacy lazy resolver
2539
2576
  canonicalised an internal id), the large-config disclosure hint, and an
2540
2577
  optional screenshot.
2578
+
2579
+ ``view_path`` scopes the payload to one view: the response carries
2580
+ ``view`` + ``view_index`` instead of ``config``. The scoped envelope
2581
+ deliberately has NO ``config`` key so the view object cannot be
2582
+ mistaken for a full config and pushed back through
2583
+ ``ha_config_set_dashboard(config=...)``, which would drop every other
2584
+ view; ``config_hash``/``config_size_bytes`` still describe the full
2585
+ config so ``python_transform`` optimistic locking works unchanged.
2541
2586
  """
2542
2587
  # Compute hash for optimistic locking in subsequent operations
2543
2588
  config_hash = compute_config_hash(config) if isinstance(config, dict) else None
@@ -2545,19 +2590,37 @@ class DashboardConfigTools:
2545
2590
  # Calculate config size for progressive disclosure hint
2546
2591
  config_size = len(json.dumps(config)) if isinstance(config, dict) else 0
2547
2592
 
2548
- get_result: dict[str, Any] = {
2549
- "success": True,
2550
- "action": "get",
2551
- "url_path": url_path,
2552
- "config": config,
2553
- "config_hash": config_hash,
2554
- "config_size_bytes": config_size,
2555
- }
2556
- _attach_dashboard_render_paths(
2557
- get_result,
2558
- url_path,
2559
- config if isinstance(config, dict) else None,
2560
- )
2593
+ if view_path is not None:
2594
+ get_result = self._scoped_view_get_result(
2595
+ url_path,
2596
+ config,
2597
+ view_path=view_path,
2598
+ config_hash=config_hash,
2599
+ config_size=config_size,
2600
+ )
2601
+ else:
2602
+ get_result = {
2603
+ "success": True,
2604
+ "action": "get",
2605
+ "url_path": url_path,
2606
+ "config": config,
2607
+ "config_hash": config_hash,
2608
+ "config_size_bytes": config_size,
2609
+ }
2610
+ _attach_dashboard_render_paths(
2611
+ get_result,
2612
+ url_path,
2613
+ config if isinstance(config, dict) else None,
2614
+ )
2615
+ # Add hint for large configs (progressive disclosure) - 10KB ≈ 2-3k tokens
2616
+ if config_size >= 10000:
2617
+ get_result["hint"] = (
2618
+ f"Large config ({config_size:,} bytes). Pass view_path=... "
2619
+ "to fetch a single view, or use "
2620
+ "ha_config_get_dashboard(entity_id=...) to find card positions, "
2621
+ "then ha_config_set_dashboard(python_transform=...) "
2622
+ "instead of full config replacement."
2623
+ )
2561
2624
  # Surface the original caller-passed identifier when the lazy
2562
2625
  # resolver canonicalised it (parity with delete_dashboard's
2563
2626
  # resolved_id field). Caller can use this to detect that their
@@ -2565,25 +2628,91 @@ class DashboardConfigTools:
2565
2628
  if original_url_path is not None and original_url_path != url_path:
2566
2629
  get_result["resolved_from"] = original_url_path
2567
2630
 
2568
- # Add hint for large configs (progressive disclosure) - 10KB ≈ 2-3k tokens
2569
- if config_size >= 10000:
2570
- get_result["hint"] = (
2571
- f"Large config ({config_size:,} bytes). For edits, use "
2572
- "ha_config_get_dashboard(entity_id=...) to find card positions, "
2573
- "then ha_config_set_dashboard(python_transform=...) "
2574
- "instead of full config replacement."
2575
- )
2576
-
2577
2631
  return await _maybe_attach_screenshot(
2578
2632
  get_result,
2579
2633
  url_path,
2580
2634
  include_screenshot,
2581
2635
  client=self._client,
2582
2636
  config=config if isinstance(config, dict) else None,
2583
- options=screenshot_options,
2637
+ # view_path doubles as the get-mode scoping parameter now, so a
2638
+ # scoped read without a screenshot request must not trip the
2639
+ # "screenshot render options are ignored" warning.
2640
+ options=(
2641
+ screenshot_options
2642
+ if include_screenshot
2643
+ else replace(screenshot_options, view_path=None)
2644
+ ),
2584
2645
  raise_on_failure=True,
2585
2646
  )
2586
2647
 
2648
+ def _scoped_view_get_result(
2649
+ self,
2650
+ url_path: str | None,
2651
+ config: Any,
2652
+ *,
2653
+ view_path: str,
2654
+ config_hash: str | None,
2655
+ config_size: int,
2656
+ ) -> dict[str, Any]:
2657
+ """Build the view-scoped get-mode envelope (issue #2010).
2658
+
2659
+ Raises the shared matcher's structured errors for an empty, unknown,
2660
+ or ambiguous ``view_path`` and for strategy dashboards (no static
2661
+ views). Pure config walking — works whether or not the dashboard
2662
+ screenshot beta feature is enabled.
2663
+ """
2664
+ if not isinstance(config, dict):
2665
+ raise_tool_error(
2666
+ create_error_response(
2667
+ ErrorCode.RESOURCE_NOT_FOUND,
2668
+ "Dashboard config is unavailable, so view_path cannot be resolved.",
2669
+ context={"url_path": url_path, "view_path": view_path},
2670
+ suggestions=["Retry without view_path to inspect the raw response"],
2671
+ )
2672
+ )
2673
+ view_index, view = match_dashboard_view(
2674
+ url_path or "default",
2675
+ config,
2676
+ view_path,
2677
+ strategy_suggestions=(
2678
+ "Omit view_path — strategy dashboards generate their views at "
2679
+ "runtime, so there is no static view config to return",
2680
+ ),
2681
+ )
2682
+ views = config.get("views")
2683
+ view_count = len(views) if isinstance(views, list) else 0
2684
+ get_result: dict[str, Any] = {
2685
+ "success": True,
2686
+ "action": "get",
2687
+ "url_path": url_path,
2688
+ "view_path": view_path,
2689
+ "view_index": view_index,
2690
+ "view_count": view_count,
2691
+ "view": view,
2692
+ "config_hash": config_hash,
2693
+ "config_size_bytes": config_size,
2694
+ "view_size_bytes": len(json.dumps(view)),
2695
+ "hint": (
2696
+ f"Scoped to view '{view_path}' — config['views'][{view_index}] "
2697
+ f"of {view_count} views. config_hash and config_size_bytes "
2698
+ "cover the FULL dashboard config, so "
2699
+ "ha_config_set_dashboard(python_transform=...) edits addressed "
2700
+ f"via config['views'][{view_index}] validate as-is. Do NOT "
2701
+ "pass this view object as the config parameter — that would "
2702
+ "replace the entire dashboard. Omit view_path for the full "
2703
+ "config."
2704
+ ),
2705
+ }
2706
+ _attach_dashboard_render_paths(get_result, url_path, config)
2707
+ render_paths = get_result.get("render_paths")
2708
+ if isinstance(render_paths, list):
2709
+ get_result["render_paths"] = [
2710
+ row
2711
+ for row in render_paths
2712
+ if isinstance(row, dict) and row.get("view_index") == view_index
2713
+ ]
2714
+ return get_result
2715
+
2587
2716
  @tool(
2588
2717
  name="ha_config_set_dashboard",
2589
2718
  tags={"Dashboards"},
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ha-mcp-dev
3
- Version: 7.14.2.dev2093
3
+ Version: 7.14.2.dev2095
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