ha-mcp-dev 7.7.0.dev694__tar.gz → 7.7.0.dev696__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 (143) hide show
  1. {ha_mcp_dev-7.7.0.dev694/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.7.0.dev696}/PKG-INFO +3 -1
  2. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/README.md +2 -0
  3. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/pyproject.toml +1 -1
  4. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/server.py +7 -0
  5. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/settings_ui.py +8 -2
  6. ha_mcp_dev-7.7.0.dev696/src/ha_mcp/tools/tool_search_hint_middleware.py +99 -0
  7. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/tools/tools_config_helpers.py +60 -21
  8. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696/src/ha_mcp_dev.egg-info}/PKG-INFO +3 -1
  9. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp_dev.egg-info/SOURCES.txt +1 -0
  10. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/LICENSE +0 -0
  11. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/MANIFEST.in +0 -0
  12. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/setup.cfg +0 -0
  13. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/__init__.py +0 -0
  14. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/__main__.py +0 -0
  15. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/_pypi_marker +0 -0
  16. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/_version.py +0 -0
  17. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/auth/__init__.py +0 -0
  18. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/auth/consent_form.py +0 -0
  19. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/auth/provider.py +0 -0
  20. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/backup_manager.py +0 -0
  21. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/client/__init__.py +0 -0
  22. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/client/rest_client.py +0 -0
  23. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/client/supervisor_client.py +0 -0
  24. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/client/websocket_client.py +0 -0
  25. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/client/websocket_listener.py +0 -0
  26. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/config.py +0 -0
  27. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/dashboard_screenshot/__init__.py +0 -0
  28. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/dashboard_screenshot/capture.py +0 -0
  29. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/dashboard_screenshot/provision.py +0 -0
  30. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/errors.py +0 -0
  31. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/policy/__init__.py +0 -0
  32. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/policy/approval_queue.py +0 -0
  33. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/policy/evaluator.py +0 -0
  34. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/policy/handlers.py +0 -0
  35. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/policy/middleware.py +0 -0
  36. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/policy/model.py +0 -0
  37. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/policy/persistence.py +0 -0
  38. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/policy/value_sources.py +0 -0
  39. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/py.typed +0 -0
  40. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/read_only.py +0 -0
  41. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
  42. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
  43. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
  44. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
  45. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
  46. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
  47. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
  48. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
  49. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
  50. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
  51. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
  52. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
  53. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
  54. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
  55. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
  56. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
  57. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
  58. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
  59. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
  60. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
  61. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
  62. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/yaml-only-integrations.md +0 -0
  63. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/settings.css +0 -0
  64. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/settings.js +0 -0
  65. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/smoke_test.py +0 -0
  66. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
  67. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/tools/__init__.py +0 -0
  68. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/tools/auto_backup.py +0 -0
  69. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/tools/backup.py +0 -0
  70. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/tools/best_practice_checker.py +0 -0
  71. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/tools/config_entry_flow.py +0 -0
  72. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/tools/device_control.py +0 -0
  73. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/tools/enhanced.py +0 -0
  74. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/tools/helpers.py +0 -0
  75. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/tools/reference_validator.py +0 -0
  76. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/tools/registry.py +0 -0
  77. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/tools/smart_search/__init__.py +0 -0
  78. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/tools/smart_search/_base.py +0 -0
  79. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/tools/smart_search/_config.py +0 -0
  80. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/tools/smart_search/_deep.py +0 -0
  81. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/tools/smart_search/_entities.py +0 -0
  82. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/tools/smart_search/_fetch.py +0 -0
  83. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/tools/smart_search/_overview.py +0 -0
  84. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/tools/smart_search/_scenes.py +0 -0
  85. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/tools/smart_search/_scoring.py +0 -0
  86. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/tools/tools_addons.py +0 -0
  87. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/tools/tools_areas.py +0 -0
  88. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/tools/tools_blueprints.py +0 -0
  89. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/tools/tools_bug_report.py +0 -0
  90. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/tools/tools_calendar.py +0 -0
  91. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/tools/tools_camera.py +0 -0
  92. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/tools/tools_categories.py +0 -0
  93. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/tools/tools_code.py +0 -0
  94. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/tools/tools_config_automations.py +0 -0
  95. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
  96. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
  97. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
  98. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/tools/tools_dashboard_screenshot.py +0 -0
  99. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/tools/tools_energy.py +0 -0
  100. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/tools/tools_entities.py +0 -0
  101. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/tools/tools_filesystem.py +0 -0
  102. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/tools/tools_groups.py +0 -0
  103. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/tools/tools_hacs.py +0 -0
  104. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/tools/tools_history.py +0 -0
  105. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/tools/tools_integrations.py +0 -0
  106. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/tools/tools_labels.py +0 -0
  107. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/tools/tools_mcp_component.py +0 -0
  108. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/tools/tools_registry.py +0 -0
  109. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/tools/tools_resources.py +0 -0
  110. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/tools/tools_search.py +0 -0
  111. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/tools/tools_service.py +0 -0
  112. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/tools/tools_services.py +0 -0
  113. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/tools/tools_system.py +0 -0
  114. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/tools/tools_themes.py +0 -0
  115. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/tools/tools_todo.py +0 -0
  116. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/tools/tools_traces.py +0 -0
  117. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/tools/tools_updates.py +0 -0
  118. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/tools/tools_utility.py +0 -0
  119. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
  120. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
  121. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/tools/tools_zones.py +0 -0
  122. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/tools/util_helpers.py +0 -0
  123. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/tools/validation_middleware.py +0 -0
  124. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/transforms/__init__.py +0 -0
  125. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/transforms/categorized_search.py +0 -0
  126. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
  127. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/utils/__init__.py +0 -0
  128. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/utils/config_hash.py +0 -0
  129. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/utils/data_paths.py +0 -0
  130. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/utils/domain_handlers.py +0 -0
  131. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/utils/fuzzy_search.py +0 -0
  132. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
  133. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/utils/operation_manager.py +0 -0
  134. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/utils/python_sandbox.py +0 -0
  135. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/utils/skill_loader.py +0 -0
  136. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp/utils/usage_logger.py +0 -0
  137. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
  138. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
  139. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
  140. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
  141. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/tests/__init__.py +0 -0
  142. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/tests/test_constants.py +0 -0
  143. {ha_mcp_dev-7.7.0.dev694 → ha_mcp_dev-7.7.0.dev696}/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.7.0.dev694
3
+ Version: 7.7.0.dev696
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
@@ -349,6 +349,8 @@ The proxy split lets MCP clients apply different permission policies per categor
349
349
 
350
350
  Leave it off when using Claude Sonnet/Opus or any client with deferred tool loading; the full catalog has no idle cost there and direct calls skip the search step. If you choose to use our toolsearch then you should disable the native Claude Opus/Sonnet toolsearch, which is called deferred tools in the settings.
351
351
 
352
+ > 🔄 **Refresh your client's tool list after changing this (or any) setting.** Toggling `ENABLE_TOOL_SEARCH` (or changing pinned/disabled tools, Read Only Mode, etc.) changes the tools the server exposes, but your AI client keeps serving its **cached** tool list until it re-fetches. Restarting the add-on or Home Assistant does **not** refresh the client — reconnect or refresh the MCP server in your client (e.g. re-add/refresh the connector in ChatGPT, or close and reopen Claude Desktop). If you skip this, tools shown as available will return `Unknown tool` when called.
353
+
352
354
  For the HA add-on, the same option is documented in [`homeassistant-addon/DOCS.md`](homeassistant-addon/DOCS.md#enable_tool_search) along with the in-add-on settings UI for fine-grained tool enable/disable/pin.
353
355
 
354
356
  ---
@@ -319,6 +319,8 @@ The proxy split lets MCP clients apply different permission policies per categor
319
319
 
320
320
  Leave it off when using Claude Sonnet/Opus or any client with deferred tool loading; the full catalog has no idle cost there and direct calls skip the search step. If you choose to use our toolsearch then you should disable the native Claude Opus/Sonnet toolsearch, which is called deferred tools in the settings.
321
321
 
322
+ > 🔄 **Refresh your client's tool list after changing this (or any) setting.** Toggling `ENABLE_TOOL_SEARCH` (or changing pinned/disabled tools, Read Only Mode, etc.) changes the tools the server exposes, but your AI client keeps serving its **cached** tool list until it re-fetches. Restarting the add-on or Home Assistant does **not** refresh the client — reconnect or refresh the MCP server in your client (e.g. re-add/refresh the connector in ChatGPT, or close and reopen Claude Desktop). If you skip this, tools shown as available will return `Unknown tool` when called.
323
+
322
324
  For the HA add-on, the same option is documented in [`homeassistant-addon/DOCS.md`](homeassistant-addon/DOCS.md#enable_tool_search) along with the in-add-on settings UI for fine-grained tool enable/disable/pin.
323
325
 
324
326
  ---
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ha-mcp-dev"
7
- version = "7.7.0.dev694"
7
+ version = "7.7.0.dev696"
8
8
  description = "Home Assistant MCP Server - Complete control of Home Assistant through MCP"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.13,<3.14"
@@ -236,6 +236,13 @@ class HomeAssistantSmartMCPServer(EnhancedToolsMixin):
236
236
 
237
237
  self.mcp.add_middleware(ValidationErrorMiddleware())
238
238
 
239
+ # Replace the opaque "Unknown tool" FastMCP raises when a client calls
240
+ # ha_search_tools / the ha_call_* proxies while Tool Search is off
241
+ # (stale client tool-list cache) with an actionable refresh hint.
242
+ from .tools.tool_search_hint_middleware import ToolSearchHintMiddleware
243
+
244
+ self.mcp.add_middleware(ToolSearchHintMiddleware())
245
+
239
246
  # Read Only Mode write blocker (discussion #1569) — always
240
247
  # installed, consults the live flag per call. Before
241
248
  # PolicyMiddleware so a write blocked by Read Only Mode never
@@ -812,6 +812,10 @@ _SETTINGS_HTML = (
812
812
  <span class="restart-notice-text" id="restartNoticeText">
813
813
  ⚠ Changes saved. Restart ha-mcp for them to take effect — disabled
814
814
  tools will be fully removed from the MCP tool list on next startup.
815
+ Then reconnect or refresh the MCP server in your AI client (e.g.
816
+ re-add/refresh the connector in ChatGPT, or close and reopen Claude
817
+ Desktop) so it reloads the tool list — restarting the add-on or Home
818
+ Assistant does NOT refresh your client's cached tool list.
815
819
  </span>
816
820
  <button class="restart-btn" id="restartBtn" style="display:none">Restart Add-on</button>
817
821
  </div>
@@ -857,8 +861,10 @@ _SETTINGS_HTML = (
857
861
  </div>
858
862
  <div class="panel" id="panel-server">
859
863
  <div class="features-sub">
860
- Tool Search, advanced settings. Changes require an MCP-host restart
861
- to take effect (close + reopen Claude Desktop, restart the add-on, etc.).
864
+ Tool Search, advanced settings. Changes take effect only after you
865
+ restart the add-on (applies the change server-side) AND reconnect or
866
+ refresh the MCP server in your AI client (reloads the tool list) — e.g.
867
+ re-add/refresh the connector in ChatGPT, or close + reopen Claude Desktop.
862
868
  </div>
863
869
 
864
870
  <!-- Two-step note + top Save button. The Save +
@@ -0,0 +1,99 @@
1
+ """FastMCP middleware: actionable error for stale tool-search proxy/search calls.
2
+
3
+ When ``enable_tool_search`` is off, the synthetic tool-search tools —
4
+ ``ha_search_tools`` and the ``ha_call_{read,write,delete}_tool`` proxies — are
5
+ never registered (the ``CategorizedSearchTransform`` is not installed). If a
6
+ client calls one of them anyway, FastMCP raises a bare ``NotFoundError``
7
+ ("Unknown tool: 'ha_search_tools'") with no recovery guidance.
8
+
9
+ That is exactly what happens when an MCP client that caches its tool list is
10
+ still advertising one captured from a previous session when Tool Search was on:
11
+ the client shows ``ha_search_tools`` as available, calls it, and the live
12
+ server — now in Tool-Search-off mode — rejects it. Restarting the add-on or
13
+ Home Assistant does not help because the stale list lives in the client, not
14
+ the server.
15
+
16
+ This middleware intercepts that one case and replaces the opaque "Unknown tool"
17
+ with a structured error that tells the user their tool list is stale and to
18
+ reconnect/refresh the MCP server. It only acts on a *top-level* resolution miss
19
+ for one of those four names while Tool Search is off; in every other case —
20
+ including a ``NotFoundError`` bubbling up from inside an executing tool — the
21
+ original error propagates unchanged.
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ import logging
27
+ from typing import Any
28
+
29
+ from fastmcp.exceptions import NotFoundError
30
+ from fastmcp.server.middleware.middleware import CallNext, Middleware, MiddlewareContext
31
+
32
+ from ..config import get_global_settings
33
+ from ..errors import ErrorCode, create_error_response
34
+ from ..policy.middleware import PROXY_META_TOOLS
35
+ from .helpers import raise_tool_error
36
+
37
+ logger = logging.getLogger(__name__)
38
+
39
+
40
+ class ToolSearchHintMiddleware(Middleware):
41
+ """Turn the opaque "Unknown tool" for tool-search synthetic tools into a
42
+ stale-tool-list hint when Tool Search is disabled."""
43
+
44
+ async def on_call_tool(
45
+ self, context: MiddlewareContext, call_next: CallNext
46
+ ) -> Any:
47
+ try:
48
+ return await call_next(context)
49
+ except NotFoundError as exc:
50
+ name = context.message.name
51
+ # Only rewrite a TOP-LEVEL "Unknown tool" miss for one of the four
52
+ # tool-search synthetic names while Tool Search is off:
53
+ # - match the dispatch-layer message so a NotFoundError bubbling up
54
+ # from INSIDE an executing tool (a different name) is never
55
+ # mislabeled as a stale cache; on any wording change this simply
56
+ # fails closed and re-raises the original error.
57
+ # - require Tool Search to be off (when on, the names resolve, so a
58
+ # miss would be a different, real problem we must not mask).
59
+ top_level_miss = str(exc) == f"Unknown tool: {name!r}"
60
+ if (
61
+ top_level_miss
62
+ and name in PROXY_META_TOOLS
63
+ and not get_global_settings().enable_tool_search
64
+ ):
65
+ logger.info(
66
+ "Stale tool-search call for %r while enable_tool_search is off "
67
+ "- returning refresh-your-tool-list hint",
68
+ name,
69
+ )
70
+ raise_tool_error(
71
+ create_error_response(
72
+ code=ErrorCode.RESOURCE_NOT_FOUND,
73
+ message=(
74
+ f"'{name}' only exists when Tool Search is enabled, and "
75
+ "Tool Search is currently OFF on this ha-mcp server. Your "
76
+ "MCP client is showing a cached tool list from when Tool "
77
+ "Search was on. After changing any ha-mcp setting (Tool "
78
+ "Search, pinned/disabled tools, etc.) the client must "
79
+ "reconnect or refresh the MCP server to re-fetch the "
80
+ "current tools — restarting the add-on or Home Assistant "
81
+ "does not refresh the client's cached list. With Tool "
82
+ f"Search off, every tool is available directly by name, "
83
+ f"so {name} is not needed."
84
+ ),
85
+ suggestions=[
86
+ (
87
+ "Reconnect or refresh the ha-mcp MCP server in your "
88
+ "client to reload the current tool list."
89
+ ),
90
+ (
91
+ "Then call the tool you need directly by its name "
92
+ "(with Tool Search off, ha_search_tools and the "
93
+ "ha_call_* proxies do not exist)."
94
+ ),
95
+ ],
96
+ context={"tool_name": name, "enable_tool_search": False},
97
+ )
98
+ )
99
+ raise
@@ -737,12 +737,15 @@ def _validate_applicable_params(
737
737
 
738
738
  if helper_type in FLOW_HELPER_TYPES:
739
739
  # Flow types accept `config` (handled before this call) plus
740
- # cross-cutting params (name/helper_id/area_id/labels/category/wait).
741
- # Any simple-helper-typed param passed here is inapplicable.
740
+ # cross-cutting params (name/helper_id/area_id/labels/category/icon/wait).
741
+ # `icon` is applied to the resulting entity(ies) via the entity registry
742
+ # (like area_id/labels), so it is allowed even though the config-flow
743
+ # form has no icon field. Any other simple-helper-typed param passed
744
+ # here is inapplicable.
742
745
  inapplicable.extend(
743
746
  param_name
744
747
  for param_name in _ALL_TYPED_PARAMS
745
- if passed.get(param_name) is not None
748
+ if param_name != "icon" and passed.get(param_name) is not None
746
749
  )
747
750
  else:
748
751
  applicable = _TYPE_TYPED_PARAMS.get(helper_type, frozenset())
@@ -760,7 +763,7 @@ def _validate_applicable_params(
760
763
  if helper_type in FLOW_HELPER_TYPES:
761
764
  applicable_msg = (
762
765
  "config (see data_schema on a validation error for the field set), "
763
- "name, helper_id, area_id, labels, category, wait"
766
+ "name, helper_id, area_id, labels, category, icon, wait"
764
767
  )
765
768
  else:
766
769
  type_specific = sorted(_TYPE_TYPED_PARAMS.get(helper_type, frozenset()))
@@ -1453,7 +1456,11 @@ async def _get_entities_for_config_entry(
1453
1456
 
1454
1457
 
1455
1458
  async def _entity_registry_update_coro(
1456
- client: Any, entity_id: str, area_id: str | None, labels: list[str] | None
1459
+ client: Any,
1460
+ entity_id: str,
1461
+ area_id: str | None,
1462
+ labels: list[str] | None,
1463
+ icon: str | None = None,
1457
1464
  ) -> Any:
1458
1465
  """Build and send a config/entity_registry/update WS message."""
1459
1466
  update_message: dict[str, Any] = {
@@ -1464,6 +1471,8 @@ async def _entity_registry_update_coro(
1464
1471
  update_message["area_id"] = area_id if area_id else None
1465
1472
  if labels is not None:
1466
1473
  update_message["labels"] = labels
1474
+ if icon is not None:
1475
+ update_message["icon"] = icon if icon else None
1467
1476
  return await client.send_websocket_message(update_message)
1468
1477
 
1469
1478
 
@@ -1482,6 +1491,7 @@ def _process_reg_update_result(
1482
1491
  reg_result: Any,
1483
1492
  area_id: str | None,
1484
1493
  labels: list[str] | None,
1494
+ icon: str | None,
1485
1495
  applied: dict[str, Any],
1486
1496
  entity_id: str,
1487
1497
  warnings: list[str],
@@ -1495,9 +1505,21 @@ def _process_reg_update_result(
1495
1505
  applied["area_id"] = area_id if area_id else None
1496
1506
  if labels is not None:
1497
1507
  applied["labels"] = labels
1508
+ if icon is not None:
1509
+ applied["icon"] = icon if icon else None
1498
1510
  else:
1511
+ # area_id/labels/icon share one WS message, so a rejection of any
1512
+ # one sinks the others. Name the batched fields so the caller can
1513
+ # tell which touchups didn't land instead of guessing.
1514
+ sent_fields = [
1515
+ f
1516
+ for f, v in (("area_id", area_id), ("labels", labels), ("icon", icon))
1517
+ if v is not None
1518
+ ]
1519
+ field_note = f" (fields: {', '.join(sent_fields)})" if sent_fields else ""
1499
1520
  warnings.append(
1500
- f"{entity_id}: entity registry update failed: {_ws_error_msg(reg_result)}"
1521
+ f"{entity_id}: entity registry update failed{field_note}: "
1522
+ f"{_ws_error_msg(reg_result)}"
1501
1523
  )
1502
1524
 
1503
1525
 
@@ -1523,9 +1545,10 @@ async def _apply_registry_updates_to_entity(
1523
1545
  area_id: str | None,
1524
1546
  labels: list[str] | None,
1525
1547
  category: str | None,
1548
+ icon: str | None,
1526
1549
  warnings: list[str],
1527
1550
  ) -> dict[str, Any]:
1528
- """Apply area_id/labels (single WS call) and category (shared helper) to one entity.
1551
+ """Apply area_id/labels/icon (single WS call) and category (shared helper) to one entity.
1529
1552
 
1530
1553
  Appends human-readable warning strings to `warnings` on any failure.
1531
1554
  Returns a small dict summarizing what was applied (for result building).
@@ -1536,13 +1559,13 @@ async def _apply_registry_updates_to_entity(
1536
1559
  # `is not None` distinguishes "not provided" from "explicit clear" (empty
1537
1560
  # string / empty list). A transient raise on either call is captured via
1538
1561
  # return_exceptions so a multi-entity flow helper can still report partial success.
1539
- needs_registry = area_id is not None or labels is not None
1562
+ needs_registry = area_id is not None or labels is not None or icon is not None
1540
1563
  needs_category = bool(category)
1541
1564
  if not (needs_registry or needs_category):
1542
1565
  return applied
1543
1566
 
1544
1567
  reg_task = (
1545
- _entity_registry_update_coro(client, entity_id, area_id, labels)
1568
+ _entity_registry_update_coro(client, entity_id, area_id, labels, icon)
1546
1569
  if needs_registry
1547
1570
  else None
1548
1571
  )
@@ -1557,7 +1580,7 @@ async def _apply_registry_updates_to_entity(
1557
1580
  cat_result = raw_results.pop(0) if needs_category else None
1558
1581
 
1559
1582
  _process_reg_update_result(
1560
- reg_result, area_id, labels, applied, entity_id, warnings
1583
+ reg_result, area_id, labels, icon, applied, entity_id, warnings
1561
1584
  )
1562
1585
  _process_cat_apply_result(cat_result, entity_id, applied, warnings)
1563
1586
  return applied
@@ -1772,31 +1795,45 @@ async def _apply_flow_registry_updates(
1772
1795
  area_id: str | None,
1773
1796
  labels_list: list[str] | None,
1774
1797
  category: str | None,
1798
+ icon: str | None,
1775
1799
  extras: dict[str, Any],
1776
1800
  warnings: list[str],
1777
1801
  ) -> None:
1778
- """Apply area/labels/category to every entity from a flow helper, in parallel."""
1802
+ """Apply area/labels/category/icon to every entity from a flow helper, in parallel."""
1779
1803
  if not (
1780
1804
  entity_ids
1781
- and (area_id is not None or labels_list is not None or category is not None)
1805
+ and (
1806
+ area_id is not None
1807
+ or labels_list is not None
1808
+ or category is not None
1809
+ or icon is not None
1810
+ )
1782
1811
  ):
1783
1812
  return
1784
1813
  applied_per_entity = list(
1785
1814
  await asyncio.gather(
1786
1815
  *(
1787
1816
  _apply_registry_updates_to_entity(
1788
- client, eid, area_id, labels_list, category, warnings
1817
+ client, eid, area_id, labels_list, category, icon, warnings
1789
1818
  )
1790
1819
  for eid in entity_ids
1791
1820
  )
1792
1821
  )
1793
1822
  )
1794
- if area_id is not None:
1823
+ # Echo a top-level convenience key only when it was actually applied to at
1824
+ # least one entity. The per-entity ``applied`` dicts are the source of
1825
+ # truth — a failed entity_registry/update leaves its key out and records a
1826
+ # warning instead — so echoing straight from the inputs would assert
1827
+ # success that the accompanying warnings contradict.
1828
+ applied_keys = {k for entry in applied_per_entity for k in entry}
1829
+ if area_id is not None and "area_id" in applied_keys:
1795
1830
  extras["area_id"] = area_id if area_id else None
1796
- if labels_list is not None:
1831
+ if labels_list is not None and "labels" in applied_keys:
1797
1832
  extras["labels"] = labels_list
1798
- if category:
1833
+ if category and "category" in applied_keys:
1799
1834
  extras["category"] = category
1835
+ if icon is not None and "icon" in applied_keys:
1836
+ extras["icon"] = icon if icon else None
1800
1837
  extras["applied"] = applied_per_entity
1801
1838
 
1802
1839
 
@@ -1810,16 +1847,17 @@ async def _handle_flow_helper(
1810
1847
  labels: str | list[str] | None,
1811
1848
  category: str | None,
1812
1849
  wait: bool,
1850
+ icon: str | None = None,
1813
1851
  action: str | None = None,
1814
1852
  ) -> dict[str, Any]:
1815
1853
  """Create or update a flow-based helper and apply registry updates to all entities.
1816
1854
 
1817
1855
  Routes between create_flow_helper and update_flow_helper based on helper_id,
1818
1856
  then resolves the resulting config_entry_id to its entity(ies) and applies
1819
- area_id / labels / category across the full set.
1857
+ area_id / labels / category / icon across the full set.
1820
1858
 
1821
- For utility_meter with tariffs, this means the same label/area is applied
1822
- to every tariff sensor (and the select entity) uniformly.
1859
+ For utility_meter with tariffs, this means the same label/area/icon is
1860
+ applied to every tariff sensor (and the select entity) uniformly.
1823
1861
 
1824
1862
  `action` may be passed by the caller (Bug 11 explicit-intent path) — when
1825
1863
  None, falls back to the legacy implicit discriminator (presence of
@@ -1903,7 +1941,7 @@ async def _handle_flow_helper(
1903
1941
  extras["updated"] = True
1904
1942
 
1905
1943
  await _apply_flow_registry_updates(
1906
- client, entity_ids, area_id, labels_list, category, extras, warnings
1944
+ client, entity_ids, area_id, labels_list, category, icon, extras, warnings
1907
1945
  )
1908
1946
 
1909
1947
  return _helper_response(
@@ -3886,7 +3924,8 @@ class HelperConfigTools:
3886
3924
  labels,
3887
3925
  category,
3888
3926
  wait,
3889
- action,
3927
+ icon=icon,
3928
+ action=action,
3890
3929
  )
3891
3930
  _attach_helper_skill(flow_response, MandatoryBPS)
3892
3931
  return flow_response
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ha-mcp-dev
3
- Version: 7.7.0.dev694
3
+ Version: 7.7.0.dev696
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
@@ -349,6 +349,8 @@ The proxy split lets MCP clients apply different permission policies per categor
349
349
 
350
350
  Leave it off when using Claude Sonnet/Opus or any client with deferred tool loading; the full catalog has no idle cost there and direct calls skip the search step. If you choose to use our toolsearch then you should disable the native Claude Opus/Sonnet toolsearch, which is called deferred tools in the settings.
351
351
 
352
+ > 🔄 **Refresh your client's tool list after changing this (or any) setting.** Toggling `ENABLE_TOOL_SEARCH` (or changing pinned/disabled tools, Read Only Mode, etc.) changes the tools the server exposes, but your AI client keeps serving its **cached** tool list until it re-fetches. Restarting the add-on or Home Assistant does **not** refresh the client — reconnect or refresh the MCP server in your client (e.g. re-add/refresh the connector in ChatGPT, or close and reopen Claude Desktop). If you skip this, tools shown as available will return `Unknown tool` when called.
353
+
352
354
  For the HA add-on, the same option is documented in [`homeassistant-addon/DOCS.md`](homeassistant-addon/DOCS.md#enable_tool_search) along with the in-add-on settings UI for fine-grained tool enable/disable/pin.
353
355
 
354
356
  ---
@@ -68,6 +68,7 @@ src/ha_mcp/tools/enhanced.py
68
68
  src/ha_mcp/tools/helpers.py
69
69
  src/ha_mcp/tools/reference_validator.py
70
70
  src/ha_mcp/tools/registry.py
71
+ src/ha_mcp/tools/tool_search_hint_middleware.py
71
72
  src/ha_mcp/tools/tools_addons.py
72
73
  src/ha_mcp/tools/tools_areas.py
73
74
  src/ha_mcp/tools/tools_blueprints.py