ha-mcp-dev 7.9.0.dev737__tar.gz → 7.9.0.dev739__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 (145) hide show
  1. {ha_mcp_dev-7.9.0.dev737/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.9.0.dev739}/PKG-INFO +1 -1
  2. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/pyproject.toml +1 -1
  3. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/config.py +1 -1
  4. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/policy/persistence.py +1 -1
  5. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/read_only.py +1 -1
  6. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/settings_ui/__init__.py +86 -26
  7. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/settings_ui/settings.css +26 -9
  8. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/settings_ui/settings.html +34 -28
  9. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/settings_ui/settings.js +221 -85
  10. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/tools/tools_entities.py +38 -23
  11. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/transforms/__init__.py +2 -0
  12. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/transforms/categorized_search.py +11 -5
  13. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
  14. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/LICENSE +0 -0
  15. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/MANIFEST.in +0 -0
  16. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/README.md +0 -0
  17. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/setup.cfg +0 -0
  18. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/__init__.py +0 -0
  19. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/__main__.py +0 -0
  20. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/_pypi_marker +0 -0
  21. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/_version.py +0 -0
  22. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/auth/__init__.py +0 -0
  23. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/auth/consent_form.py +0 -0
  24. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/auth/provider.py +0 -0
  25. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/backup_manager.py +0 -0
  26. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/client/__init__.py +0 -0
  27. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/client/rest_client.py +0 -0
  28. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/client/supervisor_client.py +0 -0
  29. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/client/websocket_client.py +0 -0
  30. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/client/websocket_listener.py +0 -0
  31. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/dashboard_screenshot/__init__.py +0 -0
  32. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/dashboard_screenshot/capture.py +0 -0
  33. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/dashboard_screenshot/provision.py +0 -0
  34. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/errors.py +0 -0
  35. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/policy/__init__.py +0 -0
  36. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/policy/approval_queue.py +0 -0
  37. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/policy/evaluator.py +0 -0
  38. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/policy/handlers.py +0 -0
  39. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/policy/middleware.py +0 -0
  40. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/policy/model.py +0 -0
  41. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/policy/value_sources.py +0 -0
  42. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/py.typed +0 -0
  43. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
  44. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
  45. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
  46. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
  47. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
  48. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
  49. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
  50. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
  51. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
  52. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
  53. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
  54. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
  55. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
  56. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
  57. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
  58. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
  59. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
  60. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
  61. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
  62. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
  63. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
  64. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/yaml-only-integrations.md +0 -0
  65. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/server.py +0 -0
  66. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/smoke_test.py +0 -0
  67. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
  68. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/tools/__init__.py +0 -0
  69. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/tools/auto_backup.py +0 -0
  70. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/tools/backup.py +0 -0
  71. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/tools/best_practice_checker.py +0 -0
  72. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/tools/config_entry_flow.py +0 -0
  73. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/tools/device_control.py +0 -0
  74. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/tools/enhanced.py +0 -0
  75. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/tools/helpers.py +0 -0
  76. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/tools/reference_validator.py +0 -0
  77. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/tools/registry.py +0 -0
  78. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/tools/smart_search/__init__.py +0 -0
  79. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/tools/smart_search/_base.py +0 -0
  80. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/tools/smart_search/_config.py +0 -0
  81. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/tools/smart_search/_deep.py +0 -0
  82. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/tools/smart_search/_entities.py +0 -0
  83. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/tools/smart_search/_fetch.py +0 -0
  84. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/tools/smart_search/_overview.py +0 -0
  85. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/tools/smart_search/_scenes.py +0 -0
  86. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/tools/smart_search/_scoring.py +0 -0
  87. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/tools/tool_search_hint_middleware.py +0 -0
  88. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/tools/tools_addons.py +0 -0
  89. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/tools/tools_areas.py +0 -0
  90. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/tools/tools_blueprints.py +0 -0
  91. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/tools/tools_bug_report.py +0 -0
  92. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/tools/tools_calendar.py +0 -0
  93. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/tools/tools_camera.py +0 -0
  94. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/tools/tools_categories.py +0 -0
  95. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/tools/tools_code.py +0 -0
  96. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/tools/tools_config_automations.py +0 -0
  97. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
  98. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
  99. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
  100. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
  101. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/tools/tools_dashboard_screenshot.py +0 -0
  102. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/tools/tools_energy.py +0 -0
  103. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/tools/tools_filesystem.py +0 -0
  104. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/tools/tools_groups.py +0 -0
  105. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/tools/tools_hacs.py +0 -0
  106. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/tools/tools_history.py +0 -0
  107. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/tools/tools_integrations.py +0 -0
  108. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/tools/tools_labels.py +0 -0
  109. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/tools/tools_mcp_component.py +0 -0
  110. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/tools/tools_registry.py +0 -0
  111. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/tools/tools_resources.py +0 -0
  112. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/tools/tools_search.py +0 -0
  113. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/tools/tools_service.py +0 -0
  114. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/tools/tools_services.py +0 -0
  115. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/tools/tools_system.py +0 -0
  116. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/tools/tools_themes.py +0 -0
  117. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/tools/tools_todo.py +0 -0
  118. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/tools/tools_traces.py +0 -0
  119. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/tools/tools_updates.py +0 -0
  120. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/tools/tools_utility.py +0 -0
  121. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
  122. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
  123. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/tools/tools_zones.py +0 -0
  124. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/tools/util_helpers.py +0 -0
  125. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/tools/validation_middleware.py +0 -0
  126. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
  127. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/update_check.py +0 -0
  128. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/utils/__init__.py +0 -0
  129. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/utils/config_hash.py +0 -0
  130. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/utils/data_paths.py +0 -0
  131. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/utils/domain_handlers.py +0 -0
  132. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/utils/fuzzy_search.py +0 -0
  133. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
  134. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/utils/operation_manager.py +0 -0
  135. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/utils/python_sandbox.py +0 -0
  136. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/utils/skill_loader.py +0 -0
  137. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp/utils/usage_logger.py +0 -0
  138. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
  139. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
  140. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
  141. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
  142. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
  143. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/tests/__init__.py +0 -0
  144. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/tests/test_constants.py +0 -0
  145. {ha_mcp_dev-7.9.0.dev737 → ha_mcp_dev-7.9.0.dev739}/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.9.0.dev737
3
+ Version: 7.9.0.dev739
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.9.0.dev737"
7
+ version = "7.9.0.dev739"
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"
@@ -535,7 +535,7 @@ def validate_settings() -> tuple[bool, str | None]:
535
535
  RegistryFieldType = type[bool] | type[int] | type[float] | type[str]
536
536
 
537
537
  # Closed set of UI section names. The advanced renderer in
538
- # settings_ui.py picks a DOM container per section; a typo would render
538
+ # settings_ui/__init__.py picks a DOM container per section; a typo would render
539
539
  # the row into nothing.
540
540
  AdvancedSection = Literal[
541
541
  "connection",
@@ -1,4 +1,4 @@
1
- """Atomic load/save for tool_policy.json (mirrors tool_config.json pattern in settings_ui.py)."""
1
+ """Atomic load/save for tool_policy.json (mirrors tool_config.json pattern in settings_ui/__init__.py)."""
2
2
 
3
3
  import json
4
4
  import os
@@ -138,7 +138,7 @@ def _custom_tool_write(args: dict[str, Any]) -> str | None:
138
138
  # cache is only listable here). Everything NOT in this table and not
139
139
  # ``readOnlyHint=True`` is hidden and blocked outright.
140
140
  #
141
- # ``MANDATORY_TOOLS`` (settings_ui.py) intentionally needs no special
141
+ # ``MANDATORY_TOOLS`` (settings_ui/__init__.py) intentionally needs no special
142
142
  # case here: every mandatory tool is either ``readOnlyHint=True`` or
143
143
  # present in this table (``ha_manage_backup``). The e2e test
144
144
  # ``test_real_catalog_mandatory_tools_stay_available``
@@ -83,12 +83,13 @@ _PROCESS_STARTED_AT: float = time.time()
83
83
  logger = logging.getLogger(__name__)
84
84
 
85
85
  # Tools that are always enabled regardless of saved config — the server
86
- # strips them out of any disable list before applying. Three of these
86
+ # strips them out of any disable list before applying. Five of these
87
87
  # overlap with DEFAULT_PINNED_TOOLS in transforms/categorized_search.py
88
- # (ha_search, ha_get_overview, ha_report_issue); ha_get_state
89
- # is mandatory but not pinned-by-default because it is reachable via the
90
- # ha_call_read_tool proxy when tool search is on. Keep these lists in
91
- # sync where it matters and divergent where it matters — don't merge them.
88
+ # (ha_search, ha_get_overview, ha_report_issue, ha_get_skill_guide,
89
+ # ha_manage_backup); ha_get_state is mandatory but not pinned-by-default
90
+ # because it is reachable via the ha_call_read_tool proxy when tool search
91
+ # is on. Keep these lists in sync where it matters and divergent where it
92
+ # matters — don't merge them.
92
93
  MANDATORY_TOOLS: set[str] = {
93
94
  "ha_search",
94
95
  "ha_get_overview",
@@ -633,7 +634,7 @@ def apply_tool_visibility(
633
634
  _SETTINGS_JS_PATH = Path(__file__).parent / "settings.js"
634
635
  try:
635
636
  _settings_js_template = _SETTINGS_JS_PATH.read_text(encoding="utf-8")
636
- except FileNotFoundError as exc: # pragma: no cover - packaging guard
637
+ except OSError as exc: # pragma: no cover - packaging guard
637
638
  raise ImportError(
638
639
  f"settings.js missing at {_SETTINGS_JS_PATH}. It must ship in "
639
640
  "package-data (wheel), MANIFEST.in (sdist), and the PyInstaller datas "
@@ -641,9 +642,10 @@ except FileNotFoundError as exc: # pragma: no cover - packaging guard
641
642
  ) from exc
642
643
  # str.replace() silently no-ops on an absent token, and a *renamed* sentinel
643
644
  # (e.g. PINNED_DEFAULTS) slips past both the "__HA_MCP_" not-in test and the
644
- # node --check parse guard -- `const DEFAULT_PINNED = PINNED_DEFAULTS;` is valid
645
- # JS (only a runtime ReferenceError), so a drifted settings.js would ship a
646
- # broken page green. Assert both sentinels are present before substituting.
645
+ # esbuild/jsdom JS harness (tests/js/harness.mjs) -- `const DEFAULT_PINNED =
646
+ # PINNED_DEFAULTS;` is valid JS (only a runtime ReferenceError), so a drifted
647
+ # settings.js would ship a broken page green. Assert both sentinels are present
648
+ # before substituting.
647
649
  for _sentinel in ("__HA_MCP_DEFAULT_PINNED__", "__HA_MCP_MANDATORY__"):
648
650
  if _sentinel not in _settings_js_template:
649
651
  raise ImportError(
@@ -664,11 +666,11 @@ _SETTINGS_JS = _settings_js_template.replace(
664
666
  # read, no token substitution -- and is injected inline between the same
665
667
  # <style>/</style> tags so the served page stays byte-identical. It carries
666
668
  # the same import-time packaging dependency as settings.js, so the same
667
- # FileNotFoundError -> ImportError packaging guard applies.
669
+ # OSError -> ImportError packaging guard applies.
668
670
  _SETTINGS_CSS_PATH = Path(__file__).parent / "settings.css"
669
671
  try:
670
672
  _SETTINGS_CSS = _SETTINGS_CSS_PATH.read_text(encoding="utf-8")
671
- except FileNotFoundError as exc: # pragma: no cover - packaging guard
673
+ except OSError as exc: # pragma: no cover - packaging guard
672
674
  raise ImportError(
673
675
  f"settings.css missing at {_SETTINGS_CSS_PATH}. It must ship in "
674
676
  "package-data (wheel), MANIFEST.in (sdist), and the PyInstaller datas "
@@ -683,21 +685,23 @@ except FileNotFoundError as exc: # pragma: no cover - packaging guard
683
685
  # __HA_MCP_JS__ -> settings.js contents (inside <script>)
684
686
  # __HA_MCP_THEME_PREFS__ -> per-request server-seeded theme prefs JSON,
685
687
  # substituted in _render_settings_html()
686
- # Same import-time packaging dependency as settings.js/css (wheel package-data
687
- # + MANIFEST.in); see _SETTINGS_JS_PATH above.
688
+ # Same import-time packaging dependency as settings.js/css (wheel package-data,
689
+ # MANIFEST.in, PyInstaller datas) and the same OSError guard -- but this loader
690
+ # raises RuntimeError, not the ImportError that settings.js/css raise.
688
691
  _SETTINGS_HTML_PATH = Path(__file__).parent / "settings.html"
689
692
  try:
690
693
  _settings_html_template = _SETTINGS_HTML_PATH.read_text(encoding="utf-8")
691
694
  except OSError as exc: # pragma: no cover - packaging guard
692
695
  raise RuntimeError(
693
696
  f"settings.html missing at {_SETTINGS_HTML_PATH}. It must ship in "
694
- "the wheel (pyproject [tool.setuptools.package-data]) and the sdist "
695
- "(MANIFEST.in)."
697
+ "package-data (wheel), MANIFEST.in (sdist), and the PyInstaller datas "
698
+ "(binary) -- this is a packaging bug, not a usage error."
696
699
  ) from exc
697
700
 
698
701
  # Fail fast if a marker was renamed in settings.html but not here (or vice
699
702
  # versa) — str.replace() silently no-ops on an absent token, which would ship a
700
- # page missing its CSS or JS. Assert all three markers are present.
703
+ # page missing its CSS, JS, or server-seeded theme prefs. Assert all three
704
+ # markers are present.
701
705
  for _sentinel in ("__HA_MCP_CSS__", "__HA_MCP_JS__", "__HA_MCP_THEME_PREFS__"):
702
706
  if _sentinel not in _settings_html_template:
703
707
  raise RuntimeError(
@@ -771,10 +775,10 @@ def _build_stub_policy_handlers(*, data_dir: Path) -> dict[str, Any]:
771
775
  {
772
776
  "error": (
773
777
  "Tool security policies live approvals are not active. "
774
- "Either the feature is turned off in addon config, the "
778
+ "Either the feature is turned off in App (add-on) config, the "
775
779
  "settings UI is running in stdio-sidecar mode, or the "
776
780
  "policy package failed to import at startup. Check the "
777
- "addon log for ImportError / RuntimeError details if you "
781
+ "App (add-on) log for ImportError / RuntimeError details if you "
778
782
  "expected gating to be on."
779
783
  )
780
784
  },
@@ -1388,7 +1392,7 @@ def build_settings_handlers(
1388
1392
  "Unset DISABLED_TOOLS / PINNED_TOOLS first.",
1389
1393
  suggestions=[
1390
1394
  "Unset the DISABLED_TOOLS / PINNED_TOOLS environment "
1391
- "variables (or remove them from your addon/Docker "
1395
+ "variables (or remove them from your App (add-on)/Docker "
1392
1396
  "config), then restart to edit these tools from the UI.",
1393
1397
  ],
1394
1398
  context={"rejected": rejected},
@@ -1451,7 +1455,7 @@ def build_settings_handlers(
1451
1455
  return JSONResponse(
1452
1456
  create_error_response(
1453
1457
  ErrorCode.CONFIG_VALIDATION_FAILED,
1454
- "Restart only available when running as an add-on",
1458
+ "Restart only available when running as an App (add-on)",
1455
1459
  details="SUPERVISOR_TOKEN environment variable is not set",
1456
1460
  ),
1457
1461
  status_code=400,
@@ -1583,6 +1587,42 @@ def build_settings_handlers(
1583
1587
  }
1584
1588
  )
1585
1589
 
1590
+ async def _live_addon_options() -> dict[str, Any]:
1591
+ """Best-effort fetch of the add-on's current ``/data/options.json``.
1592
+
1593
+ Read-consistency with the add-on Configuration tab: the GET handlers
1594
+ below otherwise display boot-time env values, so a user who edits the
1595
+ add-on config (or saves from this web UI) WITHOUT restarting would see
1596
+ stale values. For add-on-synced / schema-backed fields the live value
1597
+ lives in Supervisor's options, so surface that — the latest SAVED
1598
+ value, which is what becomes active after restart (the existing
1599
+ "Restart required after save" banner conveys apply-on-restart).
1600
+
1601
+ Returns an empty dict outside add-on mode, without a live server, or
1602
+ on any fetch failure, so callers fall back to the boot-env value and
1603
+ the page never crashes or blanks a field.
1604
+ """
1605
+ if not is_running_in_addon() or server is None:
1606
+ return {}
1607
+ try:
1608
+ options, err = await _supervisor_fetch_current_options(
1609
+ server.settings.verify_ssl
1610
+ )
1611
+ except Exception as exc: # pragma: no cover - defensive belt-and-braces
1612
+ logger.debug(
1613
+ "Live add-on options fetch raised %s — using boot-env values",
1614
+ exc,
1615
+ )
1616
+ return {}
1617
+ if err is not None:
1618
+ logger.debug(
1619
+ "Live add-on options fetch failed (%s): %s — using boot-env values",
1620
+ err.kind,
1621
+ err.message,
1622
+ )
1623
+ return {}
1624
+ return options
1625
+
1586
1626
  async def _get_feature_flags(_: Request) -> JSONResponse:
1587
1627
  """Return live feature-flag values + per-field origin + editable flag.
1588
1628
 
@@ -1613,10 +1653,23 @@ def build_settings_handlers(
1613
1653
  )
1614
1654
 
1615
1655
  settings = get_global_settings()
1656
+ # Read-consistency with the add-on Configuration tab: a flag the user
1657
+ # saved in the Config tab is already in Supervisor's live options, but
1658
+ # the boot-env value goes stale until a restart. Surface the latest
1659
+ # SAVED value for any flag present in live_options (see
1660
+ # _live_addon_options) — not just origin=="addon" rows. On a dev add-on
1661
+ # a flag saved before restart has origin "default"/"file" (its env var
1662
+ # wasn't written at this boot), yet the fresh value is the one to show.
1663
+ # Only origin=="env" stays pinned (an explicit env var the user must
1664
+ # unset to change), and live_options is {} outside add-on mode so this
1665
+ # is a no-op standalone.
1666
+ live_options = await _live_addon_options()
1616
1667
  flags: dict[str, Any] = {}
1617
1668
  for field_name, env_name, ftype in FEATURE_FLAG_FIELDS:
1618
1669
  origin = get_feature_flag_origin(env_name)
1619
1670
  value = getattr(settings, field_name)
1671
+ if field_name in live_options and origin != "env":
1672
+ value = live_options[field_name]
1620
1673
  entry: dict[str, Any] = {
1621
1674
  "value": value,
1622
1675
  "origin": origin,
@@ -1796,7 +1849,7 @@ def build_settings_handlers(
1796
1849
  (
1797
1850
  f"{field_name!r} is locked by {origin}. "
1798
1851
  f"Adjust the {env_name} env var "
1799
- "(or addon configuration) instead."
1852
+ "(or App (add-on) configuration) instead."
1800
1853
  ),
1801
1854
  ),
1802
1855
  status_code=400,
@@ -1917,7 +1970,7 @@ def build_settings_handlers(
1917
1970
  "Supervisor helper returned ok=False with no error",
1918
1971
  suggestions=[
1919
1972
  "Check the Home Assistant Supervisor logs and "
1920
- + "the add-on logs for the underlying failure.",
1973
+ + "the App (add-on) logs for the underlying failure.",
1921
1974
  "Report this at "
1922
1975
  + "https://github.com/homeassistant-ai/ha-mcp/issues "
1923
1976
  + "if it persists. This indicates an internal bug.",
@@ -2286,6 +2339,11 @@ def build_settings_handlers(
2286
2339
  # called per field, and re-reading the file 17+ times would
2287
2340
  # produce duplicate WARNINGs on a corrupt file (one per field).
2288
2341
  overrides = _read_feature_flag_override_file()
2342
+ # Read-consistency with the add-on Configuration tab: addon-synced
2343
+ # fields (origin "addon") live in /data/options.json, so the boot-env
2344
+ # value goes stale after a config edit without a restart. Surface the
2345
+ # latest SAVED options value for those (see _live_addon_options).
2346
+ live_options = await _live_addon_options()
2289
2347
  fields: list[dict[str, Any]] = []
2290
2348
  for (
2291
2349
  fname,
@@ -2296,6 +2354,8 @@ def build_settings_handlers(
2296
2354
  ) in ADVANCED_SETTINGS_FIELDS:
2297
2355
  origin = _origin_for_advanced_field(env_name, overrides=overrides)
2298
2356
  value: Any = getattr(settings, fname, None)
2357
+ if origin == "addon" and fname in live_options:
2358
+ value = live_options[fname]
2299
2359
  # Mask the token: never echo the actual long-lived access
2300
2360
  # token to the UI. The OAuth-mode sentinel survives so
2301
2361
  # operators can tell connection mode at a glance.
@@ -2456,7 +2516,7 @@ def build_settings_handlers(
2456
2516
  create_error_response(
2457
2517
  ErrorCode.VALIDATION_INVALID_PARAMETER,
2458
2518
  f"{fname!r} is display-only. Modify via env var "
2459
- "or addon configuration.",
2519
+ "or App (add-on) configuration.",
2460
2520
  ),
2461
2521
  status_code=409,
2462
2522
  )
@@ -2475,11 +2535,11 @@ def build_settings_handlers(
2475
2535
  # add-on-aware copy instead of implying a lever exists.
2476
2536
  if addon_mode:
2477
2537
  message = (
2478
- f"{fname!r} is fixed by the add-on runtime and "
2538
+ f"{fname!r} is fixed by the App (add-on) runtime and "
2479
2539
  "cannot be changed from the web UI."
2480
2540
  )
2481
2541
  suggestions = [
2482
- "This value is baked into the add-on and is not "
2542
+ "This value is baked into the App (add-on) and is not "
2483
2543
  "exposed as an editable setting.",
2484
2544
  ]
2485
2545
  else:
@@ -2640,7 +2700,7 @@ def build_settings_handlers(
2640
2700
  "Supervisor helper returned ok=False with no error",
2641
2701
  suggestions=[
2642
2702
  "Check the Home Assistant Supervisor logs and "
2643
- + "the add-on logs for the underlying failure.",
2703
+ + "the App (add-on) logs for the underlying failure.",
2644
2704
  "Report this at "
2645
2705
  + "https://github.com/homeassistant-ai/ha-mcp/issues "
2646
2706
  + "if it persists. This indicates an internal bug.",
@@ -42,6 +42,7 @@
42
42
  /* App toolbar */
43
43
  --header-bg: #455a64;
44
44
  --header-text: #ffffff;
45
+ --header-shadow: 0 2px 4px rgba(0, 0, 0, 0.4);
45
46
 
46
47
  /* Cards */
47
48
  --card-shadow: none;
@@ -88,7 +89,7 @@
88
89
  position: sticky;
89
90
  top: 0;
90
91
  z-index: 10;
91
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.4);
92
+ box-shadow: var(--header-shadow);
92
93
  flex-shrink: 0;
93
94
  }
94
95
  .header-inner { display: flex; align-items: center; gap: 12px; }
@@ -148,11 +149,12 @@
148
149
  }
149
150
  @media (prefers-reduced-motion: reduce) {
150
151
  .ha-toast, .ha-toast.leaving { animation: none; }
152
+ *, *::before, *::after { transition-duration: 0.01ms !important; }
151
153
  }
152
154
 
153
155
  /* ── MAIN CONTENT AREA ───────────────────────────────────────────────── */
154
156
  main {
155
- max-width: 840px;
157
+ max-width: 1280px;
156
158
  margin: 0 auto;
157
159
  padding: 0 16px 32px;
158
160
  }
@@ -168,7 +170,7 @@
168
170
 
169
171
  /* ── VERSION FOOTER ──────────────────────────────────────────────────── */
170
172
  .version-footer {
171
- margin-top: 32px; padding: 12px 0; font-size: 0.75rem;
173
+ max-width: 1280px; margin: 32px auto 0; padding: 12px 0; font-size: 0.75rem;
172
174
  color: var(--text-secondary); text-align: center;
173
175
  border-top: 1px solid var(--border); opacity: 0.7;
174
176
  }
@@ -359,6 +361,7 @@
359
361
  .badge.write { background: var(--badge-write-bg); color: var(--badge-write-text); }
360
362
  .badge.destructive { background: var(--badge-destructive-bg); color: var(--badge-destructive-text); }
361
363
  .badge.mandatory { background: var(--badge-mandatory-bg); color: var(--badge-mandatory-text); }
364
+ .badge.unknown { background: var(--surface-hover); color: var(--text-secondary); }
362
365
 
363
366
  /* ── TOGGLE GROUPS & SWITCH ──────────────────────────────────────────── */
364
367
  .tool-toggles { display: flex; gap: 16px; align-items: center; }
@@ -758,18 +761,32 @@
758
761
 
759
762
  /* ── THEME CONTROL (in header) ───────────────────────────────────────── */
760
763
  .theme-control { display: inline-flex; align-items: center; gap: 6px; }
761
- .theme-control-label { font-size: 0.75rem; color: rgba(255, 255, 255, 0.6); }
764
+ .theme-control-label { font-size: 0.75rem; color: var(--header-text); opacity: 0.7; }
762
765
  .theme-toggle {
763
- background: rgba(255, 255, 255, 0.12); color: #fff;
764
- border: 1px solid rgba(255, 255, 255, 0.2); border-radius: 4px;
765
- padding: 4px 8px; font-size: 0.8rem; font-family: inherit; cursor: pointer; outline: none;
766
- }
767
- .theme-toggle:focus { border-color: rgba(255, 255, 255, 0.6); }
766
+ background: color-mix(in srgb, var(--header-text) 12%, transparent);
767
+ color: var(--header-text);
768
+ border: 1px solid color-mix(in srgb, var(--header-text) 25%, transparent);
769
+ border-radius: 4px;
770
+ padding: 4px 8px; font-size: 0.8rem; font-family: inherit; cursor: pointer;
771
+ }
772
+ /* The closed control sits on the header and uses the header text colour, so
773
+ it stays legible whether the header is dark (dark theme) or light (light
774
+ theme). The native <option> popup follows the page color-scheme, not the
775
+ header, so pin it to the surface palette; that keeps the options legible
776
+ in every theme regardless of the header text colour. */
777
+ .theme-toggle option { background: var(--surface); color: var(--text); }
778
+ .theme-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
768
779
 
769
780
  /* ── LIGHT THEME ─────────────────────────────────────────────────────── */
770
781
  :root[data-theme="light"] {
771
782
  color-scheme: light;
772
783
  --bg: #fafafa;
784
+ /* Light header to match the original UI — the #1695 redesign left the
785
+ header dark (#455a64) in both themes, which looked out of place in
786
+ light mode. A white header + hairline divider matches the page. */
787
+ --header-bg: #ffffff;
788
+ --header-text: #212121;
789
+ --header-shadow: 0 1px 0 var(--border);
773
790
  --surface: #ffffff;
774
791
  --surface-hover: #f5f5f5;
775
792
  --text: #212121;
@@ -8,9 +8,10 @@
8
8
  /favicon.ico (which would 404 and log a console error, since the
9
9
  settings server serves no such asset in any deployment mode). -->
10
10
  <link rel="icon" href="data:,">
11
- <link rel="preconnect" href="https://fonts.googleapis.com">
12
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
13
- <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&family=Roboto+Mono&display=swap" rel="stylesheet">
11
+ <!-- Fonts are intentionally NOT loaded from a CDN: ha-mcp must stay self-contained
12
+ and work offline / air-gapped (no phoning out to Google on every load). The
13
+ font-family stack in settings.css falls back to Roboto-if-installed then the
14
+ system sans-serif. -->
14
15
  <script data-purpose="server-prefs" data-prefs="__HA_MCP_THEME_PREFS__">
15
16
  // #1574 review: theme/accessibility prefs also persist server-side
16
17
  // (theme_prefs.json) because stdio respawns its settings sidecar on a
@@ -161,44 +162,45 @@
161
162
  </div>
162
163
  <div class="restart-notice" id="restartNotice">
163
164
  <span class="restart-notice-text" id="restartNoticeText">
164
- ⚠ Changes saved. Restart ha-mcp for them to take effect. On next
165
- startup, ha-mcp fully removes disabled tools from the MCP tool list.
165
+ ⚠ Changes saved. Restart HA-MCP for them to take effect. On next
166
+ startup, HA-MCP fully removes disabled tools from the MCP tool list.
166
167
  Then reconnect or refresh the MCP server in your AI client (e.g.
167
- re-add/refresh the connector in ChatGPT, or close and reopen Claude
168
- Desktop) so it reloads the tool list. Restarting the add-on or Home
169
- Assistant does NOT refresh your client's cached tool list.
168
+ refresh tool list on claude.ai, re-add/refresh the connector in
169
+ ChatGPT, or close and reopen Claude Desktop) so it reloads the tool
170
+ list. Restarting the App (add-on) or Home Assistant does NOT refresh your
171
+ client's cached tool list.
170
172
  </span>
171
- <button class="restart-btn" id="restartBtn" style="display:none">Restart Add-on</button>
173
+ <button class="restart-btn" id="restartBtn" style="display:none">Restart App (add-on)</button>
172
174
  </div>
173
175
  <div class="panel active" id="panel-tools" role="tabpanel" aria-labelledby="tab-tools" tabindex="0">
174
176
  <div class="readonly-notice">
175
177
  Server-wide features (Tool Search, YAML config editing, filesystem
176
178
  tools, etc.) appear in both the <strong>Server Settings</strong>
177
- tab and the add-on Configuration page; they're the same settings
179
+ tab and the App (add-on) Configuration page; they're the same settings
178
180
  either way. Either surface stays in sync with the other after the
179
- addon restart. Changes require an MCP-host restart to apply.
181
+ App (add-on) restarts. Changes require HA-MCP to be restarted.
180
182
  </div>
181
183
  <div class="pin-notice show" id="pinNotice">
182
184
  Tools listed in the <code>DISABLED_TOOLS</code> or <code>PINNED_TOOLS</code>
183
185
  env vars are locked read-only; unset the env var to edit them here.
184
186
  Pin toggles only take effect when Tool Search is enabled (Server Settings tab
185
- or add-on Configuration page).
187
+ or App (add-on) Configuration page).
186
188
  </div>
187
189
  <div class="summary" id="summary"></div>
188
190
  <div class="feature-row" id="readOnlyModeRow">
189
191
  <div class="feature-info">
190
- <div class="feature-name">Read Only Mode</div>
192
+ <div class="feature-name" id="read-only-mode-label">Read Only Mode</div>
191
193
  <div class="feature-help">
192
194
  Turns off all write tools and blocks every tool from making
193
195
  write or destructive calls. Mixed read/write tools
194
- (backups, add-ons, energy preferences, voice pipelines, and code
196
+ (backups, Apps (add-ons), energy preferences, voice pipelines, and code
195
197
  mode when enabled) stay available with their write operations
196
198
  blocked.
197
199
  </div>
198
200
  </div>
199
201
  <div class="feature-control">
200
202
  <label class="switch">
201
- <input type="checkbox" id="read-only-mode-toggle">
203
+ <input type="checkbox" id="read-only-mode-toggle" aria-labelledby="read-only-mode-label">
202
204
  <span class="slider"></span>
203
205
  </label>
204
206
  </div>
@@ -207,7 +209,7 @@
207
209
  Could not read server settings. Read Only Mode status unknown; this
208
210
  view may not reflect what the server enforces.
209
211
  </div>
210
- <input type="search" class="search" id="search" placeholder="Search tools..."
212
+ <input type="search" class="search" id="search" placeholder="Search tools..." aria-label="Search tools"
211
213
  autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false">
212
214
  <div id="groups"></div>
213
215
  </div>
@@ -218,10 +220,10 @@
218
220
  </svg>
219
221
  <div class="ha-alert-body">
220
222
  Settings on this tab save automatically. Changes take effect only after
221
- you restart the add-on (applies them server-side) and reconnect or
223
+ you restart the App (add-on) (applies them server-side) and reconnect or
222
224
  refresh the MCP server in your AI client (reloads the tool list), e.g.
223
- re-add or refresh the connector in ChatGPT, or close and reopen Claude
224
- Desktop.
225
+ refresh tool list on claude.ai, re-add or refresh the connector in
226
+ ChatGPT, or close and reopen Claude Desktop.
225
227
  </div>
226
228
  </div>
227
229
 
@@ -273,8 +275,8 @@
273
275
  </div>
274
276
  </div>
275
277
  <div class="backup-filters">
276
- <input type="text" id="backupDomain" placeholder="Domain (e.g. automation)">
277
- <input type="text" id="backupEntity" placeholder="Entity ID">
278
+ <input type="text" id="backupDomain" placeholder="Domain (e.g. automation)" aria-label="Filter backups by domain">
279
+ <input type="text" id="backupEntity" placeholder="Entity ID" aria-label="Filter backups by entity ID">
278
280
  <button id="backupRefresh">Refresh</button>
279
281
  <button id="backupBulkDelete" class="danger">Bulk delete matching…</button>
280
282
  </div>
@@ -293,17 +295,17 @@
293
295
  <h3 style="font-size:1rem;margin-bottom:8px">Global settings</h3>
294
296
  <div class="feature-row">
295
297
  <div class="feature-info">
296
- <div class="feature-name">Enable Tool Security Policies</div>
298
+ <div class="feature-name" id="policy-master-label">Enable Tool Security Policies</div>
297
299
  <div class="feature-help">
298
300
  Master switch. Mirrors the toggle in Server Settings. Off by
299
- default. Toggle on and restart the addon to activate the
301
+ default. Toggle on and restart the App (add-on) to activate the
300
302
  gating middleware. While off, the rules below persist but
301
303
  aren't enforced.
302
304
  </div>
303
305
  </div>
304
306
  <div class="feature-control">
305
307
  <label class="switch">
306
- <input type="checkbox" id="policy-master-toggle">
308
+ <input type="checkbox" id="policy-master-toggle" aria-labelledby="policy-master-label">
307
309
  <span class="slider"></span>
308
310
  </label>
309
311
  </div>
@@ -314,7 +316,7 @@
314
316
  <div class="feature-help">How long the middleware waits for an approval before timing out.</div>
315
317
  </div>
316
318
  <div class="feature-control">
317
- <input type="number" id="policy-wait-seconds" min="5" max="600">
319
+ <input type="number" id="policy-wait-seconds" min="5" max="600" aria-label="Wait seconds (5-600)">
318
320
  </div>
319
321
  </div>
320
322
  <div class="feature-row">
@@ -323,7 +325,7 @@
323
325
  <div class="feature-help">How long a pending approval stays in the queue before expiring.</div>
324
326
  </div>
325
327
  <div class="feature-control">
326
- <input type="number" id="policy-ttl-minutes" min="1" max="60">
328
+ <input type="number" id="policy-ttl-minutes" min="1" max="60" aria-label="Approval TTL minutes (1-60)">
327
329
  </div>
328
330
  </div>
329
331
  <div style="margin-top:10px; display:flex; align-items:center; gap:12px">
@@ -403,14 +405,18 @@
403
405
  </div>
404
406
  </main>
405
407
  <div class="modal-backdrop" id="modalBackdrop">
406
- <div class="modal">
408
+ <div class="modal" role="dialog" aria-modal="true" aria-labelledby="modalTitle" tabindex="-1">
407
409
  <div class="modal-header">
408
410
  <span class="modal-title" id="modalTitle"></span>
409
- <button class="modal-close" id="modalClose">×</button>
411
+ <button class="modal-close" id="modalClose" aria-label="Close">×</button>
410
412
  </div>
411
413
  <div class="modal-body" id="modalBody"></div>
412
414
  </div>
413
415
  </div>
416
+ <!-- Static toast container: present in the DOM at load so the toast (which is
417
+ itself the ARIA live region) is appended into a stable parent instead of
418
+ being created from scratch on the first toast. -->
419
+ <div id="ha-toast-region"></div>
414
420
  <script>__HA_MCP_JS__</script>
415
421
  <footer id="versionFooter" class="version-footer">
416
422
  <span id="versionFooterText"></span>