ha-mcp-dev 7.7.0.dev686__tar.gz → 7.7.0.dev688__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 (142) hide show
  1. {ha_mcp_dev-7.7.0.dev686/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.7.0.dev688}/PKG-INFO +4 -4
  2. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/README.md +3 -3
  3. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/pyproject.toml +1 -1
  4. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/tools/tools_filesystem.py +8 -5
  5. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/tools/tools_system.py +48 -2
  6. ha_mcp_dev-7.7.0.dev688/src/ha_mcp/tools/tools_themes.py +191 -0
  7. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/tools/tools_yaml_config.py +33 -7
  8. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/tools/util_helpers.py +17 -0
  9. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688/src/ha_mcp_dev.egg-info}/PKG-INFO +4 -4
  10. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp_dev.egg-info/SOURCES.txt +1 -0
  11. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/LICENSE +0 -0
  12. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/MANIFEST.in +0 -0
  13. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/setup.cfg +0 -0
  14. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/__init__.py +0 -0
  15. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/__main__.py +0 -0
  16. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/_pypi_marker +0 -0
  17. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/_version.py +0 -0
  18. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/auth/__init__.py +0 -0
  19. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/auth/consent_form.py +0 -0
  20. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/auth/provider.py +0 -0
  21. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/backup_manager.py +0 -0
  22. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/client/__init__.py +0 -0
  23. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/client/rest_client.py +0 -0
  24. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/client/supervisor_client.py +0 -0
  25. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/client/websocket_client.py +0 -0
  26. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/client/websocket_listener.py +0 -0
  27. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/config.py +0 -0
  28. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/dashboard_screenshot/__init__.py +0 -0
  29. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/dashboard_screenshot/capture.py +0 -0
  30. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/dashboard_screenshot/provision.py +0 -0
  31. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/errors.py +0 -0
  32. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/policy/__init__.py +0 -0
  33. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/policy/approval_queue.py +0 -0
  34. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/policy/evaluator.py +0 -0
  35. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/policy/handlers.py +0 -0
  36. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/policy/middleware.py +0 -0
  37. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/policy/model.py +0 -0
  38. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/policy/persistence.py +0 -0
  39. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/policy/value_sources.py +0 -0
  40. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/py.typed +0 -0
  41. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/read_only.py +0 -0
  42. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
  43. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
  44. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
  45. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
  46. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
  47. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
  48. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
  49. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
  50. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
  51. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
  52. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
  53. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
  54. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
  55. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
  56. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
  57. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
  58. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
  59. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
  60. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
  61. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
  62. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
  63. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/yaml-only-integrations.md +0 -0
  64. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/server.py +0 -0
  65. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/settings.css +0 -0
  66. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/settings.js +0 -0
  67. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/settings_ui.py +0 -0
  68. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/smoke_test.py +0 -0
  69. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
  70. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/tools/__init__.py +0 -0
  71. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/tools/auto_backup.py +0 -0
  72. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/tools/backup.py +0 -0
  73. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/tools/best_practice_checker.py +0 -0
  74. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/tools/config_entry_flow.py +0 -0
  75. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/tools/device_control.py +0 -0
  76. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/tools/enhanced.py +0 -0
  77. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/tools/helpers.py +0 -0
  78. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/tools/reference_validator.py +0 -0
  79. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/tools/registry.py +0 -0
  80. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/tools/smart_search/__init__.py +0 -0
  81. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/tools/smart_search/_base.py +0 -0
  82. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/tools/smart_search/_config.py +0 -0
  83. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/tools/smart_search/_deep.py +0 -0
  84. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/tools/smart_search/_entities.py +0 -0
  85. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/tools/smart_search/_fetch.py +0 -0
  86. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/tools/smart_search/_overview.py +0 -0
  87. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/tools/smart_search/_scenes.py +0 -0
  88. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/tools/smart_search/_scoring.py +0 -0
  89. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/tools/tools_addons.py +0 -0
  90. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/tools/tools_areas.py +0 -0
  91. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/tools/tools_blueprints.py +0 -0
  92. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/tools/tools_bug_report.py +0 -0
  93. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/tools/tools_calendar.py +0 -0
  94. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/tools/tools_camera.py +0 -0
  95. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/tools/tools_categories.py +0 -0
  96. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/tools/tools_code.py +0 -0
  97. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/tools/tools_config_automations.py +0 -0
  98. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
  99. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
  100. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
  101. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
  102. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/tools/tools_dashboard_screenshot.py +0 -0
  103. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/tools/tools_energy.py +0 -0
  104. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/tools/tools_entities.py +0 -0
  105. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/tools/tools_groups.py +0 -0
  106. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/tools/tools_hacs.py +0 -0
  107. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/tools/tools_history.py +0 -0
  108. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/tools/tools_integrations.py +0 -0
  109. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/tools/tools_labels.py +0 -0
  110. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/tools/tools_mcp_component.py +0 -0
  111. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/tools/tools_registry.py +0 -0
  112. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/tools/tools_resources.py +0 -0
  113. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/tools/tools_search.py +0 -0
  114. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/tools/tools_service.py +0 -0
  115. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/tools/tools_services.py +0 -0
  116. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/tools/tools_todo.py +0 -0
  117. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/tools/tools_traces.py +0 -0
  118. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/tools/tools_updates.py +0 -0
  119. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/tools/tools_utility.py +0 -0
  120. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
  121. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/tools/tools_zones.py +0 -0
  122. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/tools/validation_middleware.py +0 -0
  123. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/transforms/__init__.py +0 -0
  124. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/transforms/categorized_search.py +0 -0
  125. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
  126. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/utils/__init__.py +0 -0
  127. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/utils/config_hash.py +0 -0
  128. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/utils/data_paths.py +0 -0
  129. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/utils/domain_handlers.py +0 -0
  130. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/utils/fuzzy_search.py +0 -0
  131. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
  132. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/utils/operation_manager.py +0 -0
  133. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/utils/python_sandbox.py +0 -0
  134. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/utils/skill_loader.py +0 -0
  135. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp/utils/usage_logger.py +0 -0
  136. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
  137. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
  138. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
  139. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
  140. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/tests/__init__.py +0 -0
  141. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/tests/test_constants.py +0 -0
  142. {ha_mcp_dev-7.7.0.dev686 → ha_mcp_dev-7.7.0.dev688}/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.dev686
3
+ Version: 7.7.0.dev688
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
@@ -38,7 +38,7 @@ Dynamic: license-file
38
38
  <!-- mcp-name: io.github.homeassistant-ai/ha-mcp -->
39
39
 
40
40
  <p align="center">
41
- <img src="https://img.shields.io/badge/tools-83-blue" alt="95+ Tools">
41
+ <img src="https://img.shields.io/badge/tools-84-blue" alt="95+ Tools">
42
42
  <a href="https://github.com/homeassistant-ai/ha-mcp/releases"><img src="https://img.shields.io/github/v/release/homeassistant-ai/ha-mcp" alt="Release"></a>
43
43
  <a href="https://github.com/homeassistant-ai/ha-mcp/actions/workflows/e2e-tests.yml"><img src="https://img.shields.io/github/actions/workflow/status/homeassistant-ai/ha-mcp/e2e-tests.yml?branch=master&label=E2E%20Tests" alt="E2E Tests"></a>
44
44
  <a href="LICENSE.md"><img src="https://img.shields.io/github/license/homeassistant-ai/ha-mcp.svg" alt="License"></a>
@@ -209,7 +209,7 @@ Spend less time configuring, more time enjoying your smart home.
209
209
  <details>
210
210
  <!-- TOOLS_TABLE_START -->
211
211
 
212
- <summary><b>Complete Tool List (83 tools)</b></summary>
212
+ <summary><b>Complete Tool List (84 tools)</b></summary>
213
213
 
214
214
  | Category | Tools |
215
215
  |----------|-------|
@@ -236,7 +236,7 @@ Spend less time configuring, more time enjoying your smart home.
236
236
  | **Scripts** | `ha_config_get_script`, `ha_config_remove_script`, `ha_config_set_script` |
237
237
  | **Search & Discovery** | `ha_get_overview`, `ha_get_state`, `ha_search` |
238
238
  | **Service & Device Control** | `ha_bulk_control`, `ha_call_event`, `ha_call_service`, `ha_get_operation_status`, `ha_list_services` |
239
- | **System** | `ha_config_set_yaml` *(beta)*, `ha_get_updates`, `ha_manage_backup`, `ha_manage_custom_tool` *(beta)*, `ha_reload_core`, `ha_restart` |
239
+ | **System** | `ha_config_set_yaml` *(beta)*, `ha_get_updates`, `ha_manage_backup`, `ha_manage_custom_tool` *(beta)*, `ha_manage_theme`, `ha_reload_core`, `ha_restart` |
240
240
  | **Todo Lists** | `ha_get_todo`, `ha_remove_todo_item`, `ha_set_todo_item` |
241
241
  | **Utilities** | `ha_eval_template`, `ha_install_mcp_tools` *(beta)*, `ha_report_issue` |
242
242
  | **Zones** | `ha_get_zone`, `ha_remove_zone`, `ha_set_zone` |
@@ -8,7 +8,7 @@
8
8
  <!-- mcp-name: io.github.homeassistant-ai/ha-mcp -->
9
9
 
10
10
  <p align="center">
11
- <img src="https://img.shields.io/badge/tools-83-blue" alt="95+ Tools">
11
+ <img src="https://img.shields.io/badge/tools-84-blue" alt="95+ Tools">
12
12
  <a href="https://github.com/homeassistant-ai/ha-mcp/releases"><img src="https://img.shields.io/github/v/release/homeassistant-ai/ha-mcp" alt="Release"></a>
13
13
  <a href="https://github.com/homeassistant-ai/ha-mcp/actions/workflows/e2e-tests.yml"><img src="https://img.shields.io/github/actions/workflow/status/homeassistant-ai/ha-mcp/e2e-tests.yml?branch=master&label=E2E%20Tests" alt="E2E Tests"></a>
14
14
  <a href="LICENSE.md"><img src="https://img.shields.io/github/license/homeassistant-ai/ha-mcp.svg" alt="License"></a>
@@ -179,7 +179,7 @@ Spend less time configuring, more time enjoying your smart home.
179
179
  <details>
180
180
  <!-- TOOLS_TABLE_START -->
181
181
 
182
- <summary><b>Complete Tool List (83 tools)</b></summary>
182
+ <summary><b>Complete Tool List (84 tools)</b></summary>
183
183
 
184
184
  | Category | Tools |
185
185
  |----------|-------|
@@ -206,7 +206,7 @@ Spend less time configuring, more time enjoying your smart home.
206
206
  | **Scripts** | `ha_config_get_script`, `ha_config_remove_script`, `ha_config_set_script` |
207
207
  | **Search & Discovery** | `ha_get_overview`, `ha_get_state`, `ha_search` |
208
208
  | **Service & Device Control** | `ha_bulk_control`, `ha_call_event`, `ha_call_service`, `ha_get_operation_status`, `ha_list_services` |
209
- | **System** | `ha_config_set_yaml` *(beta)*, `ha_get_updates`, `ha_manage_backup`, `ha_manage_custom_tool` *(beta)*, `ha_reload_core`, `ha_restart` |
209
+ | **System** | `ha_config_set_yaml` *(beta)*, `ha_get_updates`, `ha_manage_backup`, `ha_manage_custom_tool` *(beta)*, `ha_manage_theme`, `ha_reload_core`, `ha_restart` |
210
210
  | **Todo Lists** | `ha_get_todo`, `ha_remove_todo_item`, `ha_set_todo_item` |
211
211
  | **Utilities** | `ha_eval_template`, `ha_install_mcp_tools` *(beta)*, `ha_report_issue` |
212
212
  | **Zones** | `ha_get_zone`, `ha_remove_zone`, `ha_set_zone` |
@@ -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.dev686"
7
+ version = "7.7.0.dev688"
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"
@@ -53,7 +53,11 @@ CALLER_TOKEN_BOOTSTRAP_SERVICE = "get_caller_token"
53
53
  # server-side behavior change requires it. Older components (no
54
54
  # ``version`` in the get_caller_token response, or a version below this)
55
55
  # get an actionable "update via HACS" error.
56
- MIN_COMPONENT_VERSION = "0.7.0"
56
+ # 0.8.0: ``ha_config_set_yaml`` now depends on the component's
57
+ # ``themes/*.yaml`` yaml_path scope; a <0.8.0 component reaches the old
58
+ # handler and rejects ``themes/<name>.yaml`` with a misleading "not
59
+ # allowed" message instead of this actionable update prompt.
60
+ MIN_COMPONENT_VERSION = "0.8.0"
57
61
 
58
62
 
59
63
  def _version_tuple(version: str) -> tuple[int, ...]:
@@ -579,7 +583,6 @@ class FilesystemTools:
579
583
 
580
584
  Creates or updates files in restricted directories only. This is useful for:
581
585
  - Creating custom CSS/JS for dashboards
582
- - Adding theme files
583
586
  - Creating Jinja2 templates
584
587
 
585
588
  **Allowed Write Directories:**
@@ -609,10 +612,10 @@ class FilesystemTools:
609
612
  overwrite=True
610
613
  )
611
614
 
612
- # Create a theme file
615
+ # Create a custom Jinja template file
613
616
  result = ha_write_file(
614
- path="themes/dark_blue.yaml",
615
- content="Dark Blue:\\n primary-color: '#1a237e'",
617
+ path="custom_templates/formatters.jinja",
618
+ content="{% macro shout(text) %}{{ text | upper }}{% endmacro %}",
616
619
  overwrite=False
617
620
  )
618
621
  ```
@@ -28,6 +28,7 @@ from .util_helpers import (
28
28
  fetch_integration_diagnostics,
29
29
  filter_active_repairs,
30
30
  parse_diagnostics_fields,
31
+ summarize_theme_listing,
31
32
  )
32
33
 
33
34
  logger = logging.getLogger(__name__)
@@ -340,6 +341,7 @@ class SystemTools:
340
341
  - "zha_network": ZHA Zigbee devices with radio signal summary (name, LQI, RSSI)
341
342
  - "zha_network_full": ZHA Zigbee devices with all device details (can be large on 100+ device networks; prefer "zha_network" for summary)
342
343
  - "zwave_network": Z-Wave JS network status and node summary (status, security, routing)
344
+ - "themes": Installed theme names and defaults (sorted list of theme names, count, default_theme, default_dark_theme)
343
345
  - "diagnostics": Per-integration diagnostics dump — integration-defined JSON
344
346
  (commonly includes redacted config, device list, state snapshots; exact
345
347
  top-level keys vary by integration). REQUIRES ``config_entry_id``. The
@@ -393,7 +395,13 @@ class SystemTools:
393
395
 
394
396
  # Sections that require the system_health WebSocket connection; the
395
397
  # REST-based sections (config_check, diagnostics) do not.
396
- ws_backed = {"repairs", "zha_network", "zha_network_full", "zwave_network"}
398
+ ws_backed = {
399
+ "repairs",
400
+ "zha_network",
401
+ "zha_network_full",
402
+ "zwave_network",
403
+ "themes",
404
+ }
397
405
 
398
406
  ws_client = None
399
407
 
@@ -441,6 +449,7 @@ class SystemTools:
441
449
  "zwave_network",
442
450
  "diagnostics",
443
451
  "config_check",
452
+ "themes",
444
453
  }
445
454
  unknown = includes - VALID_INCLUDES
446
455
  if unknown:
@@ -462,6 +471,7 @@ class SystemTools:
462
471
  want_repairs = "repairs" in includes
463
472
  want_zha = zha_full or zha_summary
464
473
  want_zwave = "zwave_network" in includes
474
+ want_themes = "themes" in includes
465
475
 
466
476
  if ws_client is None:
467
477
  # Health WebSocket unavailable: WS-backed sections can't run.
@@ -476,13 +486,15 @@ class SystemTools:
476
486
  result["zha_network"] = {"error": ws_error}
477
487
  if want_zwave:
478
488
  result["zwave_network"] = {"error": ws_error}
489
+ if want_themes:
490
+ result["themes"] = {"error": ws_error}
479
491
  unavailable = sorted(includes & ws_backed)
480
492
  if unavailable:
481
493
  result.setdefault("warnings", []).append(
482
494
  "These sections require the system_health WebSocket, "
483
495
  f"which is unavailable: {', '.join(unavailable)}"
484
496
  )
485
- want_repairs = want_zha = want_zwave = False
497
+ want_repairs = want_zha = want_zwave = want_themes = False
486
498
 
487
499
  sections: list[tuple[str, Coroutine[Any, Any, dict[str, Any]]]] = []
488
500
  if want_repairs:
@@ -501,6 +513,8 @@ class SystemTools:
501
513
  )
502
514
  if want_zwave:
503
515
  sections.append(("zwave_network", self._fetch_zwave_network(ws_client)))
516
+ if want_themes:
517
+ sections.append(("themes", self._fetch_themes(ws_client)))
504
518
 
505
519
  if sections:
506
520
  gathered = await asyncio.gather(
@@ -859,6 +873,38 @@ class SystemTools:
859
873
  )
860
874
  return zwave_network
861
875
 
876
+ @staticmethod
877
+ async def _fetch_themes(ws_client: Any) -> dict[str, Any]:
878
+ """Fetch installed theme names and defaults from Home Assistant.
879
+
880
+ Returns theme NAMES plus defaults, not the full per-theme CSS variable
881
+ dicts (installed community themes can carry hundreds of variables; this
882
+ section is a listing/verify surface, not a content dump).
883
+ """
884
+ themes_data: dict[str, Any] = {
885
+ "themes": [],
886
+ "count": 0,
887
+ "default_theme": None,
888
+ "default_dark_theme": None,
889
+ }
890
+ try:
891
+ themes_result = await ws_client.send_command("frontend/get_themes")
892
+ if themes_result.get("success"):
893
+ themes_data = summarize_theme_listing(themes_result.get("result") or {})
894
+ else:
895
+ err = themes_result.get("error") or {}
896
+ err_msg = (
897
+ err.get("message") if isinstance(err, dict) else str(err)
898
+ ) or "unknown error"
899
+ logger.warning(
900
+ "frontend/get_themes returned success=false: %s", err_msg
901
+ )
902
+ themes_data["error"] = f"Themes data not available: {err_msg}"
903
+ except Exception as e:
904
+ logger.warning("Failed to fetch themes: %s", e)
905
+ themes_data["error"] = f"Themes data not available: {e}"
906
+ return themes_data
907
+
862
908
  async def _fetch_config_check(self) -> dict[str, Any]:
863
909
  """Validate HA configuration via POST /config/core/check_config.
864
910
 
@@ -0,0 +1,191 @@
1
+ """Frontend theme management tools for Home Assistant.
2
+
3
+ Themes are YAML/file-based: Home Assistant itself exposes no API to create or
4
+ edit theme files. This module covers what CAN be managed at runtime - listing
5
+ the installed themes and selecting the backend default theme. Creating or
6
+ editing custom theme files goes through ha_config_set_yaml (beta, ha-mcp
7
+ custom component); installing community themes goes through HACS.
8
+ """
9
+
10
+ import logging
11
+ from typing import Annotated, Any, Literal
12
+
13
+ from fastmcp.exceptions import ToolError
14
+ from fastmcp.tools import tool
15
+ from pydantic import Field
16
+
17
+ from ..client.rest_client import HomeAssistantAPIError
18
+ from ..errors import ErrorCode, create_error_response
19
+ from .helpers import (
20
+ exception_to_structured_error,
21
+ log_tool_usage,
22
+ raise_tool_error,
23
+ register_tool_methods,
24
+ )
25
+ from .util_helpers import summarize_theme_listing, websocket_error_message
26
+
27
+ logger = logging.getLogger(__name__)
28
+
29
+ ThemeAction = Literal["list", "set"]
30
+
31
+
32
+ class ThemesTools:
33
+ """Frontend theme listing and selection tools."""
34
+
35
+ def __init__(self, client: Any) -> None:
36
+ self._client = client
37
+
38
+ async def _list_themes(self) -> dict[str, Any]:
39
+ """Fetch installed theme names and defaults via websocket."""
40
+ result = await self._client.send_websocket_message(
41
+ {"type": "frontend/get_themes"}
42
+ )
43
+ if not result.get("success"):
44
+ error_msg = websocket_error_message(result.get("error", "Operation failed"))
45
+ raise_tool_error(
46
+ create_error_response(
47
+ ErrorCode.SERVICE_CALL_FAILED,
48
+ f"Failed to list themes: {error_msg}",
49
+ context={"action": "list"},
50
+ )
51
+ )
52
+ return summarize_theme_listing(result.get("result") or {})
53
+
54
+ @tool(
55
+ name="ha_manage_theme",
56
+ tags={"System"},
57
+ annotations={
58
+ "destructiveHint": True,
59
+ "idempotentHint": True,
60
+ "readOnlyHint": False,
61
+ "title": "Manage Frontend Themes",
62
+ },
63
+ )
64
+ @log_tool_usage
65
+ async def ha_manage_theme(
66
+ self,
67
+ action: Annotated[
68
+ ThemeAction,
69
+ Field(
70
+ description=(
71
+ "Theme operation: list installed themes or set the default theme."
72
+ ),
73
+ ),
74
+ ],
75
+ theme_name: Annotated[
76
+ str | None,
77
+ Field(
78
+ description=(
79
+ "Theme name when action='set'. Must be an installed theme; "
80
+ "'default' restores the built-in theme, 'none' resets the "
81
+ "chosen mode to the built-in default."
82
+ ),
83
+ default=None,
84
+ ),
85
+ ] = None,
86
+ mode: Annotated[
87
+ Literal["light", "dark"] | None,
88
+ Field(
89
+ description=(
90
+ "Which mode the theme applies to when action='set'. "
91
+ "Defaults to light."
92
+ ),
93
+ default=None,
94
+ ),
95
+ ] = None,
96
+ ) -> dict[str, Any]:
97
+ """Manage Home Assistant frontend themes.
98
+
99
+ When NOT to use: themes are YAML files - Home Assistant has no API to
100
+ create or edit them. Installing community themes goes through HACS
101
+ (ha_manage_hacs); editing custom theme files goes through
102
+ ha_config_set_yaml (beta, edits themes/<name>.yaml keyed by theme name
103
+ and attempts an automatic theme reload).
104
+
105
+ When to use: action='list' discovers installed theme names and the
106
+ current defaults; action='set' selects the backend default theme
107
+ (optionally per light/dark mode).
108
+
109
+ Caveats: action='set' changes the backend-selected default only -
110
+ users who explicitly picked a theme in their profile keep their
111
+ choice. Theme names are validated by Home Assistant at call time.
112
+
113
+ EXAMPLES:
114
+ - List themes: ha_manage_theme(action="list")
115
+ - Set default theme: ha_manage_theme(action="set", theme_name="nord")
116
+ - Set dark-mode theme: ha_manage_theme(
117
+ action="set", theme_name="nord", mode="dark")
118
+ - Restore built-in default: ha_manage_theme(
119
+ action="set", theme_name="default")
120
+ """
121
+ try:
122
+ if action == "list":
123
+ return {"success": True, "data": await self._list_themes()}
124
+
125
+ if not theme_name:
126
+ raise_tool_error(
127
+ create_error_response(
128
+ ErrorCode.VALIDATION_MISSING_PARAMETER,
129
+ "theme_name is required when action='set'",
130
+ context={"action": action},
131
+ suggestions=[
132
+ "Call ha_manage_theme(action='list') to see "
133
+ "installed themes",
134
+ ],
135
+ )
136
+ )
137
+
138
+ service_data: dict[str, Any] = {"name": theme_name}
139
+ if mode is not None:
140
+ service_data["mode"] = mode
141
+ try:
142
+ await self._client.call_service("frontend", "set_theme", service_data)
143
+ except HomeAssistantAPIError as e:
144
+ # HA's REST layer collapses the service's validation detail
145
+ # ("Theme X not found") into a bare 400 - name the theme here.
146
+ raise_tool_error(
147
+ create_error_response(
148
+ ErrorCode.SERVICE_CALL_FAILED,
149
+ f"Failed to set theme '{theme_name}': {e}. "
150
+ "The theme may not be installed.",
151
+ context={
152
+ "action": action,
153
+ "theme_name": theme_name,
154
+ "mode": mode,
155
+ },
156
+ suggestions=[
157
+ "Call ha_manage_theme(action='list') to see "
158
+ "installed themes",
159
+ ],
160
+ )
161
+ )
162
+
163
+ # Re-read the defaults so the agent sees the effective state.
164
+ listing = await self._list_themes()
165
+ return {
166
+ "success": True,
167
+ "data": {
168
+ "theme": theme_name,
169
+ # HA applies the theme to light mode when mode is omitted.
170
+ "mode": mode or "light",
171
+ "default_theme": listing.get("default_theme"),
172
+ "default_dark_theme": listing.get("default_dark_theme"),
173
+ },
174
+ }
175
+ except ToolError:
176
+ raise
177
+ except Exception as e:
178
+ exception_to_structured_error(
179
+ e,
180
+ context={"action": action, "theme_name": theme_name, "mode": mode},
181
+ suggestions=[
182
+ "Call ha_manage_theme(action='list') to see installed themes",
183
+ "Verify the Home Assistant connection",
184
+ ],
185
+ )
186
+ return None # unreachable: exception_to_structured_error always raises
187
+
188
+
189
+ def register_themes_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
190
+ """Register frontend theme management tools."""
191
+ register_tool_methods(mcp, ThemesTools(client))
@@ -152,7 +152,9 @@ class YamlConfigTools:
152
152
  "For YAML-mode dashboards, "
153
153
  "use the dotted form 'lovelace.dashboards.<url_path>' where "
154
154
  "<url_path> is lowercase, hyphenated, and not a reserved HA "
155
- "route. No other multi-segment paths are supported. "
155
+ "route. For themes in themes/*.yaml, use the theme name "
156
+ "(simple name without dots; content is the mapping of "
157
+ "theme variables only, without the theme name). "
156
158
  "'automation', 'script', and 'scene' are accepted only when "
157
159
  "file is under packages/*.yaml; in configuration.yaml use "
158
160
  "the dedicated storage-mode tools "
@@ -188,7 +190,9 @@ class YamlConfigTools:
188
190
  default="configuration.yaml",
189
191
  description=(
190
192
  "Relative path to the YAML config file. Defaults to "
191
- "'configuration.yaml'. Also supports 'packages/*.yaml'."
193
+ "'configuration.yaml'. Also supports 'packages/*.yaml' and "
194
+ "'themes/*.yaml' (yaml_path is the theme name; "
195
+ "frontend.reload_themes is triggered automatically)."
192
196
  ),
193
197
  ),
194
198
  ] = "configuration.yaml",
@@ -207,7 +211,7 @@ class YamlConfigTools:
207
211
  Field(default=True),
208
212
  ] = True,
209
213
  ) -> dict[str, Any]:
210
- """Update raw YAML configuration in configuration.yaml or packages/*.yaml (LAST RESORT). MUST call ha_get_skill_guide first.
214
+ """Update raw YAML configuration in configuration.yaml, packages/*.yaml, or themes/*.yaml (LAST RESORT). MUST call ha_get_skill_guide first.
211
215
 
212
216
  **WARNING:** Destructive, disabled by default. Dedicated tools exist for
213
217
  almost every use case and should be preferred:
@@ -224,8 +228,13 @@ class YamlConfigTools:
224
228
  Intended for YAML-only integrations with no config-flow or API
225
229
  equivalent (command_line, rest, shell_command, notify platforms),
226
230
  for integrations with significant YAML-only configuration (knx
227
- entities in package files), and for registering YAML-mode dashboards via
228
- ``lovelace.dashboards.<url_path>`` (no other ``lovelace.*`` keys).
231
+ entities in package files), for registering YAML-mode dashboards via
232
+ ``lovelace.dashboards.<url_path>`` (no other ``lovelace.*`` keys),
233
+ and for editing theme files in ``themes/*.yaml`` (keyed by theme name;
234
+ ``frontend.reload_themes`` is triggered automatically so no restart is
235
+ needed). Themes only load when configuration.yaml carries the
236
+ ``frontend: themes:`` include (e.g. ``!include_dir_merge_named themes``);
237
+ this tool cannot add that include (``frontend`` is not an allowed key).
229
238
  Also accepts ``automation``, ``script``, and ``scene`` keys when
230
239
  ``file`` is a ``packages/*.yaml`` — for git-managed YAML configs
231
240
  that track these alongside templates and other YAML items. Writes
@@ -233,8 +242,13 @@ class YamlConfigTools:
233
242
  storage-mode and YAML-mode collections don't collide; use the
234
243
  dedicated storage-mode tools instead.
235
244
  Check ``post_action`` in the response: most keys need a full HA
236
- restart; template, mqtt, group, automation, script, and scene
237
- support reload. Preserves YAML comments and HA tags (``!include``,
245
+ restart. For ``themes/*.yaml`` this tool *performs* the reload itself
246
+ (``frontend.reload_themes``), so ``post_action`` is ``reload_performed``
247
+ (or ``reload_available`` plus ``reload_error`` if that reload failed).
248
+ For template, mqtt, group, automation, script, and scene it only
249
+ *advertises* the reload service (``post_action: reload_available`` with
250
+ a ``reload_service`` to call yourself); the edit is on disk but not yet
251
+ live. Preserves YAML comments and HA tags (``!include``,
238
252
  ``!secret``) on round-trip; ``replace`` swaps the subtree as-is.
239
253
 
240
254
  ``template-guidelines.md`` ships in this response under ``skill_content``
@@ -352,6 +366,18 @@ class YamlConfigTools:
352
366
  result = unwrap_service_response(result)
353
367
  if not result.get("success", True):
354
368
  raise_tool_error(result)
369
+ if "reload_error" in result:
370
+ # Theme edits trigger frontend.reload_themes in the
371
+ # component; the file write succeeded even when that
372
+ # reload failed, so degrade to a warning instead of
373
+ # masking the partial failure behind a bare success.
374
+ result.setdefault("warnings", []).append(
375
+ "The file was updated, but "
376
+ f"{result.get('reload_service', 'the reload service')} "
377
+ f"failed: {result['reload_error']}. Re-run the reload "
378
+ '(e.g. ha_reload_core(target="themes")) or restart '
379
+ "Home Assistant to apply the change."
380
+ )
355
381
  attach_skill_content(
356
382
  result,
357
383
  MandatoryBPS=MandatoryBPS,
@@ -36,6 +36,23 @@ def websocket_error_message(error: Any) -> str:
36
36
  return str(error)
37
37
 
38
38
 
39
+ def summarize_theme_listing(raw_themes: dict[str, Any]) -> dict[str, Any]:
40
+ """Summarize a ``frontend/get_themes`` result into names plus defaults.
41
+
42
+ Returns theme NAMES, not the full per-theme CSS variable dicts (installed
43
+ community themes can carry hundreds of variables; listings are a
44
+ discovery/verify surface, not a content dump).
45
+ """
46
+ themes_value = raw_themes.get("themes") or {}
47
+ theme_names = sorted(themes_value.keys() if isinstance(themes_value, dict) else [])
48
+ return {
49
+ "themes": theme_names,
50
+ "count": len(theme_names),
51
+ "default_theme": raw_themes.get("default_theme"),
52
+ "default_dark_theme": raw_themes.get("default_dark_theme"),
53
+ }
54
+
55
+
39
56
  def strip_internal_fields(obj: Any, _seen: set[int] | None = None) -> Any:
40
57
  """Remove leading-underscore keys from ``obj`` and any nested dicts
41
58
  or lists in place.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ha-mcp-dev
3
- Version: 7.7.0.dev686
3
+ Version: 7.7.0.dev688
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
@@ -38,7 +38,7 @@ Dynamic: license-file
38
38
  <!-- mcp-name: io.github.homeassistant-ai/ha-mcp -->
39
39
 
40
40
  <p align="center">
41
- <img src="https://img.shields.io/badge/tools-83-blue" alt="95+ Tools">
41
+ <img src="https://img.shields.io/badge/tools-84-blue" alt="95+ Tools">
42
42
  <a href="https://github.com/homeassistant-ai/ha-mcp/releases"><img src="https://img.shields.io/github/v/release/homeassistant-ai/ha-mcp" alt="Release"></a>
43
43
  <a href="https://github.com/homeassistant-ai/ha-mcp/actions/workflows/e2e-tests.yml"><img src="https://img.shields.io/github/actions/workflow/status/homeassistant-ai/ha-mcp/e2e-tests.yml?branch=master&label=E2E%20Tests" alt="E2E Tests"></a>
44
44
  <a href="LICENSE.md"><img src="https://img.shields.io/github/license/homeassistant-ai/ha-mcp.svg" alt="License"></a>
@@ -209,7 +209,7 @@ Spend less time configuring, more time enjoying your smart home.
209
209
  <details>
210
210
  <!-- TOOLS_TABLE_START -->
211
211
 
212
- <summary><b>Complete Tool List (83 tools)</b></summary>
212
+ <summary><b>Complete Tool List (84 tools)</b></summary>
213
213
 
214
214
  | Category | Tools |
215
215
  |----------|-------|
@@ -236,7 +236,7 @@ Spend less time configuring, more time enjoying your smart home.
236
236
  | **Scripts** | `ha_config_get_script`, `ha_config_remove_script`, `ha_config_set_script` |
237
237
  | **Search & Discovery** | `ha_get_overview`, `ha_get_state`, `ha_search` |
238
238
  | **Service & Device Control** | `ha_bulk_control`, `ha_call_event`, `ha_call_service`, `ha_get_operation_status`, `ha_list_services` |
239
- | **System** | `ha_config_set_yaml` *(beta)*, `ha_get_updates`, `ha_manage_backup`, `ha_manage_custom_tool` *(beta)*, `ha_reload_core`, `ha_restart` |
239
+ | **System** | `ha_config_set_yaml` *(beta)*, `ha_get_updates`, `ha_manage_backup`, `ha_manage_custom_tool` *(beta)*, `ha_manage_theme`, `ha_reload_core`, `ha_restart` |
240
240
  | **Todo Lists** | `ha_get_todo`, `ha_remove_todo_item`, `ha_set_todo_item` |
241
241
  | **Utilities** | `ha_eval_template`, `ha_install_mcp_tools` *(beta)*, `ha_report_issue` |
242
242
  | **Zones** | `ha_get_zone`, `ha_remove_zone`, `ha_set_zone` |
@@ -97,6 +97,7 @@ src/ha_mcp/tools/tools_search.py
97
97
  src/ha_mcp/tools/tools_service.py
98
98
  src/ha_mcp/tools/tools_services.py
99
99
  src/ha_mcp/tools/tools_system.py
100
+ src/ha_mcp/tools/tools_themes.py
100
101
  src/ha_mcp/tools/tools_todo.py
101
102
  src/ha_mcp/tools/tools_traces.py
102
103
  src/ha_mcp/tools/tools_updates.py