ha-mcp-dev 7.14.2.dev2098__tar.gz → 7.14.2.dev2100__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.dev2098/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.14.2.dev2100}/PKG-INFO +1 -1
  2. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/pyproject.toml +1 -1
  3. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_search.py +209 -47
  4. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
  5. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/LICENSE +0 -0
  6. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/MANIFEST.in +0 -0
  7. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/README.md +0 -0
  8. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/setup.cfg +0 -0
  9. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/__init__.py +0 -0
  10. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/__main__.py +0 -0
  11. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/_pypi_marker +0 -0
  12. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/_version.py +0 -0
  13. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/auth/__init__.py +0 -0
  14. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/auth/consent_form.py +0 -0
  15. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/auth/provider.py +0 -0
  16. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/backup_manager.py +0 -0
  17. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/browser_landing.py +0 -0
  18. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/client/__init__.py +0 -0
  19. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/client/rest_client.py +0 -0
  20. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/client/supervisor_client.py +0 -0
  21. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/client/websocket_client.py +0 -0
  22. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/client/websocket_listener.py +0 -0
  23. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/config.py +0 -0
  24. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/dashboard_screenshot/__init__.py +0 -0
  25. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/dashboard_screenshot/capture.py +0 -0
  26. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/dashboard_screenshot/content.py +0 -0
  27. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/dashboard_screenshot/paths.py +0 -0
  28. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/dashboard_screenshot/provision.py +0 -0
  29. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/dashboard_screenshot/theme_guard.py +0 -0
  30. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/errors.py +0 -0
  31. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/llm_exposure.py +0 -0
  32. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/policy/__init__.py +0 -0
  33. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/policy/approval_queue.py +0 -0
  34. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/policy/evaluator.py +0 -0
  35. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/policy/handlers.py +0 -0
  36. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/policy/middleware.py +0 -0
  37. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/policy/model.py +0 -0
  38. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/policy/persistence.py +0 -0
  39. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/policy/value_sources.py +0 -0
  40. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/py.typed +0 -0
  41. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/read_only.py +0 -0
  42. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
  43. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
  44. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
  45. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
  46. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
  47. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
  48. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
  49. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
  50. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
  51. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
  52. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
  53. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
  54. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/appdaemon.md +0 -0
  55. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
  56. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/blueprint-guide.md +0 -0
  57. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
  58. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
  59. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
  60. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
  61. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
  62. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
  63. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
  64. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/scenes.md +0 -0
  65. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
  66. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/yaml-only-integrations.md +0 -0
  67. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/server.py +0 -0
  68. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/settings_ui/__init__.py +0 -0
  69. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/settings_ui/_handlers_advanced.py +0 -0
  70. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/settings_ui/_handlers_backups.py +0 -0
  71. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/settings_ui/_handlers_fs.py +0 -0
  72. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/settings_ui/_handlers_server.py +0 -0
  73. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/settings_ui/_handlers_theme.py +0 -0
  74. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/settings_ui/_handlers_tools.py +0 -0
  75. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/settings_ui/_i18n.py +0 -0
  76. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/settings_ui/_persistence.py +0 -0
  77. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/settings_ui/_supervisor.py +0 -0
  78. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/settings_ui/_theme.py +0 -0
  79. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/settings_ui/_tools_meta.py +0 -0
  80. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/settings_ui/locales/README.md +0 -0
  81. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/settings_ui/locales/de.json +0 -0
  82. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/settings_ui/locales/en.json +0 -0
  83. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/settings_ui/locales/ru.json +0 -0
  84. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/settings_ui/locales/zh-Hans.json +0 -0
  85. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/settings_ui/settings.css +0 -0
  86. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/settings_ui/settings.html +0 -0
  87. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/settings_ui/settings.js +0 -0
  88. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/smoke_test.py +0 -0
  89. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
  90. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/strict_bps.py +0 -0
  91. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/__init__.py +0 -0
  92. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/auto_backup.py +0 -0
  93. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/backup.py +0 -0
  94. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/best_practice_checker.py +0 -0
  95. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/component_api.py +0 -0
  96. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/component_config_reads.py +0 -0
  97. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/component_devices.py +0 -0
  98. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/component_registries.py +0 -0
  99. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/component_registry_lookup.py +0 -0
  100. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/config_entry_flow.py +0 -0
  101. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/device_control.py +0 -0
  102. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/enhanced.py +0 -0
  103. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/helpers.py +0 -0
  104. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/radio/__init__.py +0 -0
  105. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/radio/base.py +0 -0
  106. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/radio/matter.py +0 -0
  107. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/radio/thread.py +0 -0
  108. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/radio/zigbee.py +0 -0
  109. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/radio/zwave.py +0 -0
  110. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/reference_validator.py +0 -0
  111. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/registry.py +0 -0
  112. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/smart_search/__init__.py +0 -0
  113. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/smart_search/_base.py +0 -0
  114. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/smart_search/_config.py +0 -0
  115. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/smart_search/_deep.py +0 -0
  116. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/smart_search/_entities.py +0 -0
  117. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/smart_search/_fetch.py +0 -0
  118. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/smart_search/_overview.py +0 -0
  119. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/smart_search/_scenes.py +0 -0
  120. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/smart_search/_scoring.py +0 -0
  121. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tool_search_hint_middleware.py +0 -0
  122. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_addons.py +0 -0
  123. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_areas.py +0 -0
  124. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_blueprints.py +0 -0
  125. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_bug_report.py +0 -0
  126. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_calendar.py +0 -0
  127. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_camera.py +0 -0
  128. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_categories.py +0 -0
  129. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_code.py +0 -0
  130. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_config_automations.py +0 -0
  131. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
  132. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
  133. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
  134. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
  135. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_dashboard_screenshot.py +0 -0
  136. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_dev.py +0 -0
  137. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_energy.py +0 -0
  138. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_entities.py +0 -0
  139. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_filesystem.py +0 -0
  140. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_groups.py +0 -0
  141. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_hacs.py +0 -0
  142. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_history.py +0 -0
  143. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_integrations.py +0 -0
  144. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_labels.py +0 -0
  145. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_radio.py +0 -0
  146. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_registry.py +0 -0
  147. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_resources.py +0 -0
  148. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_service.py +0 -0
  149. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_services.py +0 -0
  150. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_system.py +0 -0
  151. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_themes.py +0 -0
  152. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_todo.py +0 -0
  153. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_traces.py +0 -0
  154. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_updates.py +0 -0
  155. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_utility.py +0 -0
  156. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
  157. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
  158. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_yaml_read.py +0 -0
  159. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/tools_zones.py +0 -0
  160. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/util_helpers.py +0 -0
  161. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/tools/validation_middleware.py +0 -0
  162. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/transforms/__init__.py +0 -0
  163. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/transforms/categorized_search.py +0 -0
  164. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
  165. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/transport_security.py +0 -0
  166. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/update_check.py +0 -0
  167. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/utils/__init__.py +0 -0
  168. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/utils/config_hash.py +0 -0
  169. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/utils/data_paths.py +0 -0
  170. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/utils/domain_handlers.py +0 -0
  171. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/utils/fuzzy_search.py +0 -0
  172. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
  173. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/utils/operation_manager.py +0 -0
  174. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/utils/python_sandbox.py +0 -0
  175. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/utils/skill_loader.py +0 -0
  176. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/utils/usage_logger.py +0 -0
  177. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/visibility/__init__.py +0 -0
  178. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/visibility/model.py +0 -0
  179. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/visibility/persistence.py +0 -0
  180. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp/visibility/resolver.py +0 -0
  181. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
  182. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
  183. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
  184. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
  185. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
  186. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/tests/__init__.py +0 -0
  187. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/tests/test_constants.py +0 -0
  188. {ha_mcp_dev-7.14.2.dev2098 → ha_mcp_dev-7.14.2.dev2100}/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.dev2098
3
+ Version: 7.14.2.dev2100
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.dev2098"
7
+ version = "7.14.2.dev2100"
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
@@ -181,7 +181,8 @@ _ALWAYS_KEEP_PROJECTION: frozenset[str] = frozenset(
181
181
  # search actually scanned — caller value beyond the input echo.
182
182
  "area_names",
183
183
  # Entity-branch internal mode label ("exact_match", "fuzzy_search",
184
- # "area_only", "area_filtered_query", "domain_listing"). E2E tests
184
+ # "area_only", "area_filtered_query", "domain_listing",
185
+ # "state_listing"). E2E tests
185
186
  # pin its presence at 17+ assertion sites — callers verifiably
186
187
  # rely on it to disambiguate which entity-search path produced
187
188
  # the result, so retained at the envelope top instead of stripped.
@@ -335,8 +336,10 @@ def _compute_eligibility(
335
336
  Returns ``(registry_eligible, body_eligible, body_skipped_by_intent_gate)``:
336
337
 
337
338
  - ``registry_eligible``: the entity-registry branch runs whenever any of
338
- ``query`` / ``domain_filter`` / ``area_filter`` is set, except when the
339
- caller pinned config-only via an explicit ``search_types``.
339
+ ``query`` / ``domain_filter`` / ``area_filter`` / ``state_filter`` is set,
340
+ except when the caller pinned config-only via an explicit ``search_types``.
341
+ ``state_filter`` alone is sufficient — it enumerates every entity in that
342
+ state (issue #2002), mirroring the ``domain_filter``-only listing mode.
340
343
  - ``body_eligible``: the config-body branch runs only when a ``query``
341
344
  term is set AND the caller's inputs do not signal entity-only intent.
342
345
  "Entity-only intent" = any of ``domain_filter`` / ``area_filter`` /
@@ -348,7 +351,9 @@ def _compute_eligibility(
348
351
  ``query`` + entity-filter without explicit pin). The orchestrator
349
352
  surfaces a warning in this case so callers can opt back in.
350
353
  """
351
- any_registry_input = bool(query_text or domain_filter_text or area_filter_text)
354
+ any_registry_input = bool(
355
+ query_text or domain_filter_text or area_filter_text or state_filter_text
356
+ )
352
357
  registry_eligible = any_registry_input and not explicit_config_only
353
358
  entity_intent_signal = bool(
354
359
  domain_filter_text or area_filter_text or state_filter_text
@@ -1248,13 +1253,30 @@ def _normalize_state_filter(state_filter: str | None) -> str | None:
1248
1253
  return state_filter
1249
1254
 
1250
1255
 
1256
+ def _state_matches(record: dict[str, Any], state_filter: str) -> bool:
1257
+ """True when ``record``'s state equals ``state_filter``, case-insensitively.
1258
+
1259
+ ``state_filter`` is already lowercased by ``_normalize_state_filter``; the
1260
+ record side is lowered here so an uppercase entity state (e.g. an
1261
+ input_select holding "Vacation") still matches the documented
1262
+ case-insensitive contract.
1263
+ """
1264
+ return (record.get("state") or "").lower() == state_filter
1265
+
1266
+
1251
1267
  def _validate_entity_search_params(
1252
1268
  query: str | None,
1253
1269
  domain_filter: str | None,
1254
1270
  area_filter: str | None,
1255
1271
  result_fields: Any,
1272
+ state_filter: str | None = None,
1256
1273
  ) -> tuple[str, str | None, str | None, list[str] | None]:
1257
- """Validate and normalise inputs for entity search; returns (query, domain_filter, area_filter, parsed_result_fields)."""
1274
+ """Validate and normalise inputs for entity search; returns (query, domain_filter, area_filter, parsed_result_fields).
1275
+
1276
+ ``state_filter`` is not returned (the caller normalises it separately via
1277
+ ``_normalize_state_filter``); it participates here only in the
1278
+ at-least-one-criterion check so a ``state_filter``-only call is accepted and
1279
+ enumerates every entity in that state (issue #2002)."""
1258
1280
  parsed_result_fields: list[str] | None = None
1259
1281
  if result_fields is not None:
1260
1282
  try:
@@ -1278,10 +1300,16 @@ def _validate_entity_search_params(
1278
1300
  domain_filter = domain_filter.strip().lower()
1279
1301
  if area_filter:
1280
1302
  area_filter = area_filter.strip()
1281
- if not query.strip() and not domain_filter and not area_filter:
1303
+ if (
1304
+ not query.strip()
1305
+ and not domain_filter
1306
+ and not area_filter
1307
+ and not _normalize_state_filter(state_filter)
1308
+ ):
1282
1309
  raise_tool_error(
1283
1310
  create_validation_error(
1284
- "At least one of 'query', 'domain_filter', or 'area_filter' must be set.",
1311
+ "At least one of 'query', 'domain_filter', 'area_filter', or "
1312
+ "'state_filter' must be set.",
1285
1313
  parameter="query",
1286
1314
  )
1287
1315
  )
@@ -1524,7 +1552,7 @@ async def _exact_match_search(
1524
1552
  results.append(match)
1525
1553
 
1526
1554
  if state_filter:
1527
- results = [r for r in results if r.get("state") == state_filter]
1555
+ results = [r for r in results if _state_matches(r, state_filter)]
1528
1556
 
1529
1557
  # Sort by score descending, tie-break on entity_id for stable
1530
1558
  # pagination when many results share a score (visible substring
@@ -1575,10 +1603,11 @@ class SearchTools:
1575
1603
  "script sequences, scene contents, helper bodies, "
1576
1604
  "dashboard cards) in one call. Use this for any "
1577
1605
  "find-something-in-HA question — entity OR config. "
1578
- "Omit `query` to enumerate by `domain_filter` and/or "
1579
- "`area_filter` alone (registry-listing mode); "
1580
- "configuration-body search is skipped in that mode "
1581
- "because there is no term to match against."
1606
+ "Omit `query` to enumerate by `domain_filter`, "
1607
+ "`area_filter`, and/or `state_filter` alone "
1608
+ "(registry-listing mode); configuration-body search is "
1609
+ "skipped in that mode because there is no term to match "
1610
+ "against."
1582
1611
  ),
1583
1612
  ),
1584
1613
  ] = None,
@@ -1691,7 +1720,10 @@ class SearchTools:
1691
1720
  default=None,
1692
1721
  description=(
1693
1722
  "Filter entity-registry results to a specific state "
1694
- '(e.g. "on", "off", "unavailable"). Case-insensitive.'
1723
+ '(e.g. "on", "off", "unavailable"). Case-insensitive. '
1724
+ "Can be used standalone (no query/domain/area) to enumerate "
1725
+ "every entity in that state; entity_total_matches reflects "
1726
+ "the filtered count."
1695
1727
  ),
1696
1728
  ),
1697
1729
  ] = None,
@@ -1757,8 +1789,8 @@ class SearchTools:
1757
1789
 
1758
1790
  Two surfaces run in parallel and return tagged results:
1759
1791
  - **entities**: entity-registry matches (entity_id, friendly name,
1760
- area). Filter with `domain_filter`/`area_filter`; omit `query` to
1761
- enumerate a domain/area.
1792
+ area). Filter with `domain_filter`/`area_filter`/`state_filter`;
1793
+ omit `query` to enumerate a domain, area, or state.
1762
1794
  - **automations / scripts / scenes / helpers / dashboards**: matches
1763
1795
  *inside* config definitions — triggers, actions, sequences, scene
1764
1796
  entity-sets, helper bodies, dashboard cards. Driven by `query`;
@@ -1800,6 +1832,7 @@ class SearchTools:
1800
1832
  - Which automations use an entity: ha_search("light.bed_light")
1801
1833
  - Scenes touching a light: ha_search("light.kitchen", search_types=["scene"])
1802
1834
  - Narrow the response to the entity bucket: ha_search("kitchen", fields=["entities"])
1835
+ - All unavailable entities: ha_search(state_filter="unavailable")
1803
1836
  """
1804
1837
  try:
1805
1838
  parsed_search_types = parse_string_list_param(search_types, "search_types")
@@ -1848,7 +1881,7 @@ class SearchTools:
1848
1881
  raise_tool_error(
1849
1882
  create_validation_error(
1850
1883
  "ha_search requires a non-empty query, or one of "
1851
- "domain_filter / area_filter to enumerate.",
1884
+ "domain_filter / area_filter / state_filter to enumerate.",
1852
1885
  parameter="query",
1853
1886
  )
1854
1887
  )
@@ -2128,8 +2161,8 @@ class SearchTools:
2128
2161
  default=None,
2129
2162
  description=(
2130
2163
  "Entity name to search for (fuzzy or exact match). "
2131
- "Omit to list entities; `domain_filter` or `area_filter` "
2132
- "must be set in that mode."
2164
+ "Omit to list entities; `domain_filter`, `area_filter`, "
2165
+ "or `state_filter` must be set in that mode."
2133
2166
  ),
2134
2167
  ),
2135
2168
  ] = None,
@@ -2215,8 +2248,10 @@ class SearchTools:
2215
2248
  "Filter results to entities in a specific state "
2216
2249
  '(e.g. "on", "off", "unavailable"). Case-insensitive — '
2217
2250
  "input is lowercased before matching. Applied server-side after "
2218
- "search results are collected. For exact-match and domain-listing "
2219
- "searches, total_matches reflects the filtered count. For fuzzy "
2251
+ "search results are collected. Can be used standalone (no "
2252
+ "query/domain/area) to enumerate every entity in that state. "
2253
+ "For exact-match, domain-listing, and state-listing searches, "
2254
+ "total_matches reflects the filtered count. For fuzzy "
2220
2255
  "searches, state_filter is page-only and total_matches remains "
2221
2256
  "unfiltered (see state_filter_note in the response). "
2222
2257
  "None = no state filter (default)."
@@ -2243,13 +2278,18 @@ class SearchTools:
2243
2278
  ) -> dict[str, Any]:
2244
2279
  """Search for entities (lights, sensors, switches, etc.) by name, domain, or area.
2245
2280
 
2281
+ Internal helper reached through the `ha_search` tool; the examples below
2282
+ use that tool as the entry point.
2283
+
2246
2284
  When NOT to use: for searching inside automation, script, helper, or dashboard
2247
2285
  *configurations* (e.g. which automations call a service or reference an entity),
2248
- use `ha_deep_search`.
2286
+ search config bodies via `ha_search(query=...)`.
2249
2287
 
2250
- To enumerate all entities of a domain, omit `query` and pass `domain_filter`. For
2251
- example, `ha_search_entities(domain_filter="calendar")` lists all calendars. At
2252
- least one of `query`, `domain_filter`, or `area_filter` must be set.
2288
+ To enumerate all entities of a domain, omit `query` and pass `domain_filter` —
2289
+ e.g. `ha_search(domain_filter="calendar")` lists all calendars. To enumerate
2290
+ every entity in a state, omit `query` and pass `state_filter` — e.g.
2291
+ `ha_search(state_filter="unavailable")`. At least one of `query`,
2292
+ `domain_filter`, `area_filter`, or `state_filter` must be set.
2253
2293
 
2254
2294
  ``prefetched_states`` / ``prefetched_registry`` are the orchestrator's
2255
2295
  shared snapshots; they only reach the regular (non-area, non-domain-only)
@@ -2257,7 +2297,7 @@ class SearchTools:
2257
2297
  """
2258
2298
  query, domain_filter, area_filter, parsed_result_fields = (
2259
2299
  _validate_entity_search_params(
2260
- query, domain_filter, area_filter, result_fields
2300
+ query, domain_filter, area_filter, result_fields, state_filter
2261
2301
  )
2262
2302
  )
2263
2303
  group_by_domain_bool = group_by_domain
@@ -2328,6 +2368,17 @@ class SearchTools:
2328
2368
  parsed_result_fields,
2329
2369
  )
2330
2370
 
2371
+ if state_filter and not domain_filter and (not query or not query.strip()):
2372
+ return await self._search_state_only(
2373
+ state_filter,
2374
+ limit,
2375
+ offset,
2376
+ include_hidden_bool,
2377
+ group_by_domain_bool,
2378
+ per_domain_limit_int,
2379
+ parsed_result_fields,
2380
+ )
2381
+
2331
2382
  return await self._search_regular(
2332
2383
  query,
2333
2384
  domain_filter,
@@ -2358,6 +2409,7 @@ class SearchTools:
2358
2409
  "query": query,
2359
2410
  "domain_filter": domain_filter,
2360
2411
  "area_filter": area_filter,
2412
+ "state_filter": state_filter,
2361
2413
  },
2362
2414
  )
2363
2415
  )
@@ -2369,11 +2421,12 @@ class SearchTools:
2369
2421
  "query": query,
2370
2422
  "domain_filter": domain_filter,
2371
2423
  "area_filter": area_filter,
2424
+ "state_filter": state_filter,
2372
2425
  },
2373
2426
  suggestions=[
2374
2427
  "Check Home Assistant connection",
2375
2428
  "Try simpler search terms",
2376
- "Check area/domain filter spelling",
2429
+ "Check area/domain/state filter spelling",
2377
2430
  ],
2378
2431
  )
2379
2432
  return None # unreachable: error helpers above always raise
@@ -2639,7 +2692,7 @@ class SearchTools:
2639
2692
  ]
2640
2693
 
2641
2694
  if state_filter:
2642
- results = [r for r in results if r.get("state") == state_filter]
2695
+ results = [r for r in results if _state_matches(r, state_filter)]
2643
2696
 
2644
2697
  pagination = _build_pagination_metadata(total_matches, offset, limit, results)
2645
2698
 
@@ -2720,7 +2773,7 @@ class SearchTools:
2720
2773
 
2721
2774
  all_results.sort(key=lambda x: (-x["score"], x["entity_id"]))
2722
2775
  if state_filter:
2723
- all_results = [r for r in all_results if r.get("state") == state_filter]
2776
+ all_results = [r for r in all_results if _state_matches(r, state_filter)]
2724
2777
  paginated = all_results[offset : offset + limit]
2725
2778
 
2726
2779
  area_search_data: dict[str, Any] = {
@@ -2807,24 +2860,22 @@ class SearchTools:
2807
2860
  # No add_timezone_metadata — see _search_area_with_query.
2808
2861
  return empty_area_data
2809
2862
 
2810
- async def _search_domain_only(
2863
+ async def _fetch_listing_snapshot(
2811
2864
  self,
2812
- query: str | None,
2813
- domain_filter: str,
2814
- state_filter: str | None,
2815
- limit: int,
2816
- offset: int,
2817
- include_hidden_bool: bool,
2818
- group_by_domain_bool: bool,
2819
- per_domain_limit_int: int | None,
2820
- parsed_result_fields: list[str] | None,
2821
- ) -> dict[str, Any]:
2822
- """List all entities of a single domain (empty query + domain_filter)."""
2823
- # Fetch states + registry list in parallel. Registry-list failure is
2824
- # tolerated (we just lose the hidden filter); states-fetch failure is
2825
- # fatal — auth/connection errors must propagate. The device registry is
2826
- # gated: it only feeds the visibility area/label dimensions, so a
2827
- # default/area-free config skips the fetch entirely.
2865
+ ) -> tuple[list[dict[str, Any]], set[str], set[str], list[str]]:
2866
+ """Fetch states + registries and resolve hidden/visibility sets for listing modes.
2867
+
2868
+ Shared by ``_search_domain_only`` and ``_search_state_only``: both
2869
+ enumerate the full state machine and need the same ``/api/states``
2870
+ snapshot, registry-derived hidden ids, and visibility-excluded set.
2871
+ Returns ``(states, hidden_ids, visibility_hidden, visibility_warnings)``.
2872
+
2873
+ Fetches states + the entity registry in parallel. Registry-list failure is
2874
+ tolerated (we just lose the hidden filter); states-fetch failure is fatal —
2875
+ auth/connection errors must propagate. The device registry is gated: it
2876
+ only feeds the visibility area/label dimensions, so a default/area-free
2877
+ config skips the fetch entirely.
2878
+ """
2828
2879
  need_device = await device_registry_needed_for_visibility()
2829
2880
  fetch_coros: list[Any] = [
2830
2881
  self._client.get_states(),
@@ -2860,6 +2911,27 @@ class SearchTools:
2860
2911
  visibility_hidden, visibility_warnings = await load_hidden_set(
2861
2912
  registry_result, states_result, self._client, device_result
2862
2913
  )
2914
+ return states_result, hidden_ids, visibility_hidden, visibility_warnings
2915
+
2916
+ async def _search_domain_only(
2917
+ self,
2918
+ query: str | None,
2919
+ domain_filter: str,
2920
+ state_filter: str | None,
2921
+ limit: int,
2922
+ offset: int,
2923
+ include_hidden_bool: bool,
2924
+ group_by_domain_bool: bool,
2925
+ per_domain_limit_int: int | None,
2926
+ parsed_result_fields: list[str] | None,
2927
+ ) -> dict[str, Any]:
2928
+ """List all entities of a single domain (empty query + domain_filter)."""
2929
+ (
2930
+ states_result,
2931
+ hidden_ids,
2932
+ visibility_hidden,
2933
+ visibility_warnings,
2934
+ ) = await self._fetch_listing_snapshot()
2863
2935
 
2864
2936
  # Filter by domain. Hidden entities are kept by default (with score
2865
2937
  # penalty applied below); ``include_hidden=False`` filters them out.
@@ -2895,7 +2967,7 @@ class SearchTools:
2895
2967
  scored_entities.sort(key=lambda x: (-x["score"], x["entity_id"]))
2896
2968
  if state_filter:
2897
2969
  scored_entities = [
2898
- e for e in scored_entities if e.get("state") == state_filter
2970
+ e for e in scored_entities if _state_matches(e, state_filter)
2899
2971
  ]
2900
2972
  results = scored_entities[offset : offset + limit]
2901
2973
 
@@ -2925,6 +2997,96 @@ class SearchTools:
2925
2997
  domain_list_data, [*visibility_warnings, *enrich_warnings]
2926
2998
  )
2927
2999
 
3000
+ async def _search_state_only(
3001
+ self,
3002
+ state_filter: str,
3003
+ limit: int,
3004
+ offset: int,
3005
+ include_hidden_bool: bool,
3006
+ group_by_domain_bool: bool,
3007
+ per_domain_limit_int: int | None,
3008
+ parsed_result_fields: list[str] | None,
3009
+ ) -> dict[str, Any]:
3010
+ """List all entities in a given state (state_filter with no query/domain/area).
3011
+
3012
+ Mirrors ``_search_domain_only`` but enumerates across every domain,
3013
+ filtering on exact state equality (``state_filter`` is already lowercased
3014
+ by ``_normalize_state_filter``) so a single call answers "all unavailable
3015
+ entities" (issue #2002). The state filter is applied before pagination so
3016
+ ``total_matches`` reflects the filtered count.
3017
+ """
3018
+ (
3019
+ states_result,
3020
+ hidden_ids,
3021
+ visibility_hidden,
3022
+ visibility_warnings,
3023
+ ) = await self._fetch_listing_snapshot()
3024
+
3025
+ # Exact state match — identical semantics to the exact/domain paths.
3026
+ # Hidden entities are kept by default (score-penalised below);
3027
+ # ``include_hidden=False`` filters them out. The visibility exclude is
3028
+ # applied before pagination so the counts below stay coherent.
3029
+ filtered_entities = [
3030
+ e
3031
+ for e in states_result
3032
+ if (eid := e.get("entity_id", "")) not in visibility_hidden
3033
+ and _state_matches(e, state_filter)
3034
+ and (include_hidden_bool or eid not in hidden_ids)
3035
+ ]
3036
+
3037
+ # Score: 100 baseline for state membership (exact, not fuzzy); penalised
3038
+ # for hidden entries so they sort below visible peers. ``domain`` is
3039
+ # derived per record since results span every domain.
3040
+ scored_entities = []
3041
+ for entity in filtered_entities:
3042
+ entity_id = entity.get("entity_id", "")
3043
+ attributes = entity.get("attributes", {})
3044
+ score = apply_hidden_penalty(
3045
+ 100, "_hidden" if entity_id in hidden_ids else None
3046
+ )
3047
+ scored_entities.append(
3048
+ {
3049
+ "entity_id": entity_id,
3050
+ "friendly_name": attributes.get("friendly_name", entity_id),
3051
+ "domain": entity_id.split(".")[0],
3052
+ "state": entity.get("state", "unknown"),
3053
+ "score": score,
3054
+ "match_type": "state_listing",
3055
+ }
3056
+ )
3057
+ scored_entities.sort(key=lambda x: (-x["score"], x["entity_id"]))
3058
+ results = scored_entities[offset : offset + limit]
3059
+
3060
+ state_list_data: dict[str, Any] = {
3061
+ "success": True,
3062
+ "query": None,
3063
+ "state_filter": state_filter,
3064
+ **_build_pagination_metadata(len(scored_entities), offset, limit, results),
3065
+ "results": results,
3066
+ "search_type": "state_listing",
3067
+ "note": (
3068
+ f"Listing all entities in state '{state_filter}' "
3069
+ "(state_filter with no query/domain/area)"
3070
+ ),
3071
+ }
3072
+
3073
+ enrich_warnings = await self._maybe_enrich_entity_records(
3074
+ results, parsed_result_fields
3075
+ )
3076
+ _apply_result_fields_to_response(state_list_data, parsed_result_fields)
3077
+ _apply_by_domain_grouping(
3078
+ state_list_data,
3079
+ results,
3080
+ group_by_domain_bool,
3081
+ per_domain_limit_int,
3082
+ parsed_result_fields,
3083
+ )
3084
+
3085
+ # No add_timezone_metadata — see _search_area_with_query.
3086
+ return merge_visibility_warnings(
3087
+ state_list_data, [*visibility_warnings, *enrich_warnings]
3088
+ )
3089
+
2928
3090
  async def _search_regular(
2929
3091
  self,
2930
3092
  query: str,
@@ -3013,7 +3175,7 @@ class SearchTools:
3013
3175
  # page-only — smart_entity_search already paginated internally, so
3014
3176
  # total_matches/has_more reflect the unfiltered dataset.
3015
3177
  if state_filter and "results" in result and search_type == "fuzzy_search":
3016
- filtered = [r for r in result["results"] if r.get("state") == state_filter]
3178
+ filtered = [r for r in result["results"] if _state_matches(r, state_filter)]
3017
3179
  result["results"] = filtered
3018
3180
  result["count"] = len(filtered)
3019
3181
  result["state_filter_note"] = (
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ha-mcp-dev
3
- Version: 7.14.2.dev2098
3
+ Version: 7.14.2.dev2100
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