ha-mcp-dev 7.12.3.dev1934__tar.gz → 7.12.3.dev1937__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 (178) hide show
  1. {ha_mcp_dev-7.12.3.dev1934/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.12.3.dev1937}/PKG-INFO +1 -1
  2. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/pyproject.toml +1 -1
  3. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/settings_ui/_tools_meta.py +10 -2
  4. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_filesystem.py +29 -1
  5. ha_mcp_dev-7.12.3.dev1937/src/ha_mcp/tools/tools_yaml_read.py +336 -0
  6. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
  7. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp_dev.egg-info/SOURCES.txt +1 -0
  8. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/LICENSE +0 -0
  9. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/MANIFEST.in +0 -0
  10. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/README.md +0 -0
  11. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/setup.cfg +0 -0
  12. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/__init__.py +0 -0
  13. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/__main__.py +0 -0
  14. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/_pypi_marker +0 -0
  15. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/_version.py +0 -0
  16. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/auth/__init__.py +0 -0
  17. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/auth/consent_form.py +0 -0
  18. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/auth/provider.py +0 -0
  19. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/backup_manager.py +0 -0
  20. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/browser_landing.py +0 -0
  21. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/client/__init__.py +0 -0
  22. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/client/rest_client.py +0 -0
  23. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/client/supervisor_client.py +0 -0
  24. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/client/websocket_client.py +0 -0
  25. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/client/websocket_listener.py +0 -0
  26. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/config.py +0 -0
  27. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/dashboard_screenshot/__init__.py +0 -0
  28. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/dashboard_screenshot/capture.py +0 -0
  29. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/dashboard_screenshot/content.py +0 -0
  30. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/dashboard_screenshot/paths.py +0 -0
  31. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/dashboard_screenshot/provision.py +0 -0
  32. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/errors.py +0 -0
  33. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/llm_exposure.py +0 -0
  34. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/policy/__init__.py +0 -0
  35. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/policy/approval_queue.py +0 -0
  36. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/policy/evaluator.py +0 -0
  37. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/policy/handlers.py +0 -0
  38. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/policy/middleware.py +0 -0
  39. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/policy/model.py +0 -0
  40. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/policy/persistence.py +0 -0
  41. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/policy/value_sources.py +0 -0
  42. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/py.typed +0 -0
  43. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/read_only.py +0 -0
  44. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
  45. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
  46. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
  47. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
  48. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
  49. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
  50. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
  51. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
  52. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
  53. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
  54. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
  55. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
  56. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/appdaemon.md +0 -0
  57. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
  58. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/blueprint-guide.md +0 -0
  59. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
  60. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
  61. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
  62. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
  63. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
  64. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
  65. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
  66. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/scenes.md +0 -0
  67. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
  68. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/yaml-only-integrations.md +0 -0
  69. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/server.py +0 -0
  70. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/settings_ui/__init__.py +0 -0
  71. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/settings_ui/_handlers_advanced.py +0 -0
  72. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/settings_ui/_handlers_backups.py +0 -0
  73. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/settings_ui/_handlers_fs.py +0 -0
  74. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/settings_ui/_handlers_server.py +0 -0
  75. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/settings_ui/_handlers_theme.py +0 -0
  76. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/settings_ui/_handlers_tools.py +0 -0
  77. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/settings_ui/_persistence.py +0 -0
  78. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/settings_ui/_supervisor.py +0 -0
  79. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/settings_ui/_theme.py +0 -0
  80. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/settings_ui/settings.css +0 -0
  81. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/settings_ui/settings.html +0 -0
  82. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/settings_ui/settings.js +0 -0
  83. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/smoke_test.py +0 -0
  84. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
  85. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/strict_bps.py +0 -0
  86. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/__init__.py +0 -0
  87. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/auto_backup.py +0 -0
  88. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/backup.py +0 -0
  89. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/best_practice_checker.py +0 -0
  90. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/component_api.py +0 -0
  91. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/config_entry_flow.py +0 -0
  92. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/device_control.py +0 -0
  93. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/enhanced.py +0 -0
  94. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/helpers.py +0 -0
  95. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/radio/__init__.py +0 -0
  96. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/radio/base.py +0 -0
  97. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/radio/matter.py +0 -0
  98. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/radio/thread.py +0 -0
  99. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/radio/zigbee.py +0 -0
  100. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/radio/zwave.py +0 -0
  101. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/reference_validator.py +0 -0
  102. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/registry.py +0 -0
  103. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/smart_search/__init__.py +0 -0
  104. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/smart_search/_base.py +0 -0
  105. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/smart_search/_config.py +0 -0
  106. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/smart_search/_deep.py +0 -0
  107. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/smart_search/_entities.py +0 -0
  108. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/smart_search/_fetch.py +0 -0
  109. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/smart_search/_overview.py +0 -0
  110. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/smart_search/_scenes.py +0 -0
  111. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/smart_search/_scoring.py +0 -0
  112. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tool_search_hint_middleware.py +0 -0
  113. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_addons.py +0 -0
  114. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_areas.py +0 -0
  115. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_blueprints.py +0 -0
  116. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_bug_report.py +0 -0
  117. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_calendar.py +0 -0
  118. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_camera.py +0 -0
  119. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_categories.py +0 -0
  120. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_code.py +0 -0
  121. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_config_automations.py +0 -0
  122. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
  123. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
  124. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
  125. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
  126. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_dashboard_screenshot.py +0 -0
  127. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_dev.py +0 -0
  128. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_energy.py +0 -0
  129. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_entities.py +0 -0
  130. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_groups.py +0 -0
  131. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_hacs.py +0 -0
  132. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_history.py +0 -0
  133. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_integrations.py +0 -0
  134. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_labels.py +0 -0
  135. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_mcp_component.py +0 -0
  136. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_radio.py +0 -0
  137. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_registry.py +0 -0
  138. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_resources.py +0 -0
  139. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_search.py +0 -0
  140. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_service.py +0 -0
  141. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_services.py +0 -0
  142. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_system.py +0 -0
  143. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_themes.py +0 -0
  144. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_todo.py +0 -0
  145. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_traces.py +0 -0
  146. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_updates.py +0 -0
  147. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_utility.py +0 -0
  148. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
  149. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
  150. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/tools_zones.py +0 -0
  151. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/util_helpers.py +0 -0
  152. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/tools/validation_middleware.py +0 -0
  153. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/transforms/__init__.py +0 -0
  154. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/transforms/categorized_search.py +0 -0
  155. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
  156. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/transport_security.py +0 -0
  157. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/update_check.py +0 -0
  158. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/utils/__init__.py +0 -0
  159. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/utils/config_hash.py +0 -0
  160. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/utils/data_paths.py +0 -0
  161. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/utils/domain_handlers.py +0 -0
  162. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/utils/fuzzy_search.py +0 -0
  163. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
  164. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/utils/operation_manager.py +0 -0
  165. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/utils/python_sandbox.py +0 -0
  166. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/utils/skill_loader.py +0 -0
  167. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/utils/usage_logger.py +0 -0
  168. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/visibility/__init__.py +0 -0
  169. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/visibility/model.py +0 -0
  170. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/visibility/persistence.py +0 -0
  171. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp/visibility/resolver.py +0 -0
  172. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
  173. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
  174. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
  175. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
  176. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/tests/__init__.py +0 -0
  177. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/tests/test_constants.py +0 -0
  178. {ha_mcp_dev-7.12.3.dev1934 → ha_mcp_dev-7.12.3.dev1937}/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.12.3.dev1934
3
+ Version: 7.12.3.dev1937
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.12.3.dev1934"
7
+ version = "7.12.3.dev1937"
8
8
  description = "Home Assistant MCP Server - Complete control of Home Assistant through MCP"
9
9
  readme = "README.md"
10
10
  # Upper bound widened to <3.15 so the package installs on Home Assistant's
@@ -89,8 +89,9 @@ TRANSFORM_GENERATED_TOOLS: dict[str, ToolStub] = {}
89
89
  # won't appear in local_provider._list_tools(), so we inject stub entries
90
90
  # into the settings UI so users discover the tool exists and how to enable
91
91
  # it. Keep this dict in sync with the ``"beta"`` tag added to each tool's
92
- # source file (tools_yaml_config.py, tools_filesystem.py, tools_mcp_component.py,
93
- # tools_code.py) — a future rename or removal needs to land in both places.
92
+ # source file (tools_yaml_config.py, tools_yaml_read.py, tools_filesystem.py,
93
+ # tools_mcp_component.py, tools_code.py) — a future rename or removal needs to
94
+ # land in both places.
94
95
  FEATURE_GATED_TOOLS: dict[str, ToolStub] = {
95
96
  "ha_config_set_yaml": {
96
97
  "title": "Set YAML Config",
@@ -99,6 +100,13 @@ FEATURE_GATED_TOOLS: dict[str, ToolStub] = {
99
100
  "disabled_by": "enable_yaml_config_editing",
100
101
  "destructiveHint": True,
101
102
  },
103
+ "ha_config_get_yaml": {
104
+ "title": "Read YAML Config Fragment",
105
+ "primary_tag": "System",
106
+ "description": "Read the YAML fragment under a key in a config file, or find which file defines it.",
107
+ "disabled_by": "enable_filesystem_tools",
108
+ "readOnlyHint": True,
109
+ },
102
110
  "ha_manage_custom_tool": {
103
111
  "title": "Custom Tool",
104
112
  "primary_tag": "System",
@@ -74,7 +74,14 @@ CALLER_TOKEN_BOOTSTRAP_SERVICE = "get_caller_token"
74
74
  # ENABLE_YAML_EDIT_CONFIRM, default on); a <0.11.0 component's strict
75
75
  # (PREVENT_EXTRA) schema rejects the unknown arg with a raw voluptuous
76
76
  # error — the gate surfaces an actionable "update" prompt instead.
77
- MIN_COMPONENT_VERSION = "0.11.0"
77
+ # 1.1.0: the YAML fragment read (#1788) needs two component behaviours that a
78
+ # <1.1.0 component reports no differently from a missing key. ``list_files``
79
+ # only now honours the configured packages folder, so ha_config_get_yaml's
80
+ # glob/discovery would otherwise come back "Path not allowed" instead of
81
+ # enumerating packages; and ``include_parsed`` on ``read_file`` is a new arg
82
+ # that an older strict (PREVENT_EXTRA) schema rejects with a raw voluptuous
83
+ # error. The gate turns both into the actionable "update" prompt.
84
+ MIN_COMPONENT_VERSION = "1.1.0"
78
85
 
79
86
 
80
87
  def _version_tuple(version: str) -> tuple[int, ...]:
@@ -511,6 +518,19 @@ class FilesystemTools:
511
518
  ),
512
519
  ),
513
520
  ] = None,
521
+ yaml_path: Annotated[
522
+ str | None,
523
+ Field(
524
+ default=None,
525
+ description=(
526
+ "Dotted YAML key path (e.g. 'alert2', 'mqtt.sensor'). When "
527
+ "set, the response also carries 'subtree': the round-trip "
528
+ "text of just that key's value. To look a key up across "
529
+ "packages/*.yaml, or to get it as structured data, use "
530
+ "ha_config_get_yaml instead."
531
+ ),
532
+ ),
533
+ ] = None,
514
534
  ) -> dict[str, Any]:
515
535
  """Read a file from the Home Assistant config directory.
516
536
 
@@ -541,6 +561,9 @@ class FilesystemTools:
541
561
  - size: File size in bytes
542
562
  - modified: Last modification timestamp
543
563
  - path: The file path that was read
564
+ - subtree: Round-trip text of the `yaml_path` key, when that arg is set
565
+ (null when the key is absent). Comments and HA tags (`!secret`,
566
+ `!include`) survive as written — a `!secret` is never resolved.
544
567
 
545
568
  **Example:**
546
569
  ```python
@@ -549,6 +572,9 @@ class FilesystemTools:
549
572
 
550
573
  # Read last 100 lines of log
551
574
  result = ha_read_file(path="home-assistant.log", tail_lines=100)
575
+
576
+ # Read just the alert2 block out of a package file
577
+ result = ha_read_file(path="packages/alert2.yaml", yaml_path="alert2")
552
578
  ```
553
579
  """
554
580
  try:
@@ -559,6 +585,8 @@ class FilesystemTools:
559
585
  service_data: dict[str, Any] = {"path": path}
560
586
  if tail_lines is not None:
561
587
  service_data["tail_lines"] = tail_lines
588
+ if yaml_path is not None:
589
+ service_data["yaml_path"] = yaml_path
562
590
 
563
591
  # Call the custom component service
564
592
  result = await call_mcp_tools_service(
@@ -0,0 +1,336 @@
1
+ """Read-only YAML fragment lookup for Home Assistant config files.
2
+
3
+ The read companion to ``ha_config_set_yaml``: same ``file`` + ``yaml_path``
4
+ addressing, so a fragment found here can be handed straight back to the editor.
5
+
6
+ **Dependency:** Requires the ha_mcp_tools custom component (the round-trip parse
7
+ needs ``ruamel``, which the component carries and the MCP server does not).
8
+
9
+ Gating: behind ``enable_filesystem_tools``, NOT ENABLE_YAML_CONFIG_EDITING. The
10
+ editing flag gates *edits*, and reading a fragment is not one — but this returns
11
+ config-file contents through the same ``read_file``/``list_files`` component
12
+ services as ``ha_read_file``/``ha_list_files``, so it belongs behind the same
13
+ flag those are behind. Registering it unconditionally would hand an install that
14
+ deliberately turned filesystem tools off a config-file read surface through the
15
+ back door.
16
+ """
17
+
18
+ import asyncio
19
+ import logging
20
+ from typing import Annotated, Any
21
+
22
+ from fastmcp.exceptions import ToolError
23
+ from fastmcp.tools import tool
24
+ from pydantic import Field
25
+
26
+ from ..errors import ErrorCode, create_error_response
27
+ from .helpers import (
28
+ exception_to_structured_error,
29
+ log_tool_usage,
30
+ raise_tool_error,
31
+ register_tool_methods,
32
+ )
33
+ from .tools_filesystem import (
34
+ _assert_mcp_tools_available,
35
+ call_mcp_tools_service,
36
+ is_filesystem_tools_enabled,
37
+ )
38
+ from .util_helpers import unwrap_service_response
39
+
40
+ logger = logging.getLogger(__name__)
41
+
42
+ # fnmatch metacharacters. Matching itself happens component-side (list_files
43
+ # fnmatches on the file name); this only decides single-file vs. glob.
44
+ _GLOB_METACHARS = ("*", "?", "[")
45
+
46
+
47
+ def _has_glob(file: str) -> bool:
48
+ """True if ``file`` carries an fnmatch wildcard and must be expanded."""
49
+ return any(char in file for char in _GLOB_METACHARS)
50
+
51
+
52
+ def _call_failed(service: str, context: dict[str, Any]) -> None:
53
+ """Raise the shared 'unexpected response' error for a component service."""
54
+ raise_tool_error(
55
+ create_error_response(
56
+ ErrorCode.SERVICE_CALL_FAILED,
57
+ f"Unexpected response format from {service} service",
58
+ context=context,
59
+ )
60
+ )
61
+
62
+
63
+ def _unwrap_or_raise(result: Any, service: str, context: dict[str, Any]) -> Any:
64
+ """Unwrap a component service response, raising on a failure payload."""
65
+ if not isinstance(result, dict):
66
+ _call_failed(service, context)
67
+ unwrapped = unwrap_service_response(result)
68
+ if not unwrapped.get("success", True):
69
+ raise_tool_error(unwrapped)
70
+ return unwrapped
71
+
72
+
73
+ def _failure_text(payload: dict[str, Any]) -> str:
74
+ """Readable reason out of a failure payload, for a per-file warning.
75
+
76
+ The component answers with ``error`` as a plain string; the shared
77
+ ``create_error_response`` shape nests a ``message`` under it instead.
78
+ """
79
+ error = payload.get("error")
80
+ if isinstance(error, dict):
81
+ return str(error.get("message") or error)
82
+ return str(error or "read failed")
83
+
84
+
85
+ def _evaluate_read(
86
+ response: Any,
87
+ target: str,
88
+ yaml_path: str,
89
+ *,
90
+ is_glob: bool,
91
+ include_content: bool,
92
+ include_parsed: bool,
93
+ ) -> tuple[dict[str, Any] | None, str | None]:
94
+ """Turn one ``read_file`` response into a match, a warning, or neither.
95
+
96
+ Three outcomes, in the order they are decided:
97
+
98
+ * **warning** — the file could not be searched at all. Under a glob that
99
+ must not sink the whole search and discard the matches already found:
100
+ the glob is not restricted to ``*.yaml``, so ``packages/*`` can turn up
101
+ a README the component refuses to read, and a permission-denied or
102
+ non-UTF-8 ``.yaml`` lands here too. A syntactically broken file is the
103
+ same class — reporting it as "key absent" would claim a file was
104
+ inspected when it never was. A single explicit target has no siblings
105
+ to salvage, so it raises instead.
106
+ * **neither** — the file parsed and simply has no such key. The real
107
+ non-match, and the whole point of a glob search.
108
+ * **match** — the key is there.
109
+ """
110
+ # Only reachable under a glob: with a single target return_exceptions is
111
+ # False, so gather itself raised.
112
+ if isinstance(response, BaseException):
113
+ return None, f"{target} was not searched: {response}."
114
+
115
+ if not isinstance(response, dict):
116
+ if is_glob:
117
+ return None, f"{target} was not searched: unexpected read_file response."
118
+ _call_failed("read_file", {"file": target, "yaml_path": yaml_path})
119
+ unwrapped = unwrap_service_response(response)
120
+
121
+ if not unwrapped.get("success", True):
122
+ if not is_glob:
123
+ raise_tool_error(unwrapped)
124
+ return None, f"{target} was not searched: {_failure_text(unwrapped)}."
125
+
126
+ parse_error = unwrapped.get("parse_error")
127
+ if parse_error:
128
+ return None, f"{target} was not searched: {parse_error}."
129
+
130
+ if unwrapped.get("subtree") is None:
131
+ return None, None
132
+
133
+ match: dict[str, Any] = {"file": target, "yaml_path": yaml_path}
134
+ if include_content:
135
+ match["content"] = unwrapped["subtree"]
136
+ if include_parsed:
137
+ match["parsed"] = unwrapped.get("parsed")
138
+ return match, None
139
+
140
+
141
+ async def _resolve_target_files(client: Any, file: str) -> list[str]:
142
+ """Expand ``file`` into the concrete config-relative paths to read.
143
+
144
+ A plain path is returned as-is (the read itself reports a missing file). A
145
+ glob is expanded through the component's ``list_files``, which matches the
146
+ file name only — so ``packages/*.yaml`` covers one directory level, not a
147
+ nested tree.
148
+ """
149
+ if not _has_glob(file):
150
+ return [file]
151
+
152
+ directory, _, pattern = file.rpartition("/")
153
+ unwrapped = _unwrap_or_raise(
154
+ await call_mcp_tools_service(
155
+ client,
156
+ "list_files",
157
+ {"path": directory or ".", "pattern": pattern},
158
+ ),
159
+ "list_files",
160
+ {"file": file},
161
+ )
162
+ entries = unwrapped.get("files")
163
+ if not isinstance(entries, list):
164
+ _call_failed("list_files", {"file": file})
165
+ # Sorted so a multi-file result is deterministic across calls.
166
+ return sorted(
167
+ str(entry["path"])
168
+ for entry in entries
169
+ if isinstance(entry, dict) and entry.get("path") and not entry.get("is_dir")
170
+ )
171
+
172
+
173
+ class YamlReadTools:
174
+ def __init__(self, client: Any) -> None:
175
+ self._client = client
176
+
177
+ @tool(
178
+ name="ha_config_get_yaml",
179
+ tags={"System", "beta"},
180
+ annotations={
181
+ "readOnlyHint": True,
182
+ "openWorldHint": False,
183
+ "title": "Read YAML Config Fragment",
184
+ },
185
+ )
186
+ @log_tool_usage
187
+ async def ha_config_get_yaml(
188
+ self,
189
+ yaml_path: Annotated[
190
+ str,
191
+ Field(
192
+ description=(
193
+ "Dotted YAML key path to look up, e.g. 'alert2', 'mqtt', "
194
+ "'template'. Any key is readable — unlike ha_config_set_yaml, "
195
+ "which only writes an allowlisted set."
196
+ ),
197
+ ),
198
+ ],
199
+ file: Annotated[
200
+ str,
201
+ Field(
202
+ default="configuration.yaml",
203
+ description=(
204
+ "Config-relative file to read. Accepts an fnmatch glob to "
205
+ "search several files at once — 'packages/*.yaml' matches one "
206
+ "directory level, not a nested tree. Use the glob to find "
207
+ "which file defines a key."
208
+ ),
209
+ ),
210
+ ] = "configuration.yaml",
211
+ include_content: Annotated[
212
+ bool,
213
+ Field(
214
+ default=True,
215
+ description=(
216
+ "Return the round-trip YAML text of each match. Set False to "
217
+ "discover only which files define the key."
218
+ ),
219
+ ),
220
+ ] = True,
221
+ include_parsed: Annotated[
222
+ bool,
223
+ Field(
224
+ default=False,
225
+ description=(
226
+ "Additionally return each match as structured data. HA tags "
227
+ "stay in source form ('!secret api_key'), never resolved."
228
+ ),
229
+ ),
230
+ ] = False,
231
+ ) -> dict[str, Any]:
232
+ """Get the current YAML fragment under a key, from one config file or across a glob.
233
+
234
+ Use before ha_config_set_yaml to inspect what a key currently holds, and
235
+ to find which file defines it — the returned ``file`` + ``yaml_path`` are
236
+ exactly the arguments that address the same fragment for an edit.
237
+
238
+ Not for storage-mode items: automations, scripts and scenes created
239
+ through the UI live outside YAML — read those with ha_config_get_automation,
240
+ ha_config_get_script and ha_config_get_scene. This tool sees only what is
241
+ written in the config files themselves.
242
+
243
+ Reads the file, so it reflects what is on disk rather than what Home
244
+ Assistant currently has loaded; a fragment edited but not yet reloaded
245
+ differs from the running config. Comments and HA tags survive as written
246
+ and a ``!secret`` is never resolved to its value, so ``content`` can be
247
+ handed back to ha_config_set_yaml unchanged. Files outside the read
248
+ allowlist stay unreadable, and secrets.yaml reads back with its values
249
+ masked.
250
+ """
251
+ try:
252
+ await _assert_mcp_tools_available(self._client)
253
+
254
+ is_glob = _has_glob(file)
255
+ targets = await _resolve_target_files(self._client, file)
256
+
257
+ extra: dict[str, Any] = {"include_parsed": True} if include_parsed else {}
258
+ # The reads are independent, so fan them out rather than paying N
259
+ # sequential round-trips to HA — a packages glob is routinely 10+
260
+ # files. gather preserves order, so matches stay sorted by file.
261
+ # Under a glob a single read blowing up must not sink its siblings,
262
+ # so exceptions come back as values to be warned about below.
263
+ responses = await asyncio.gather(
264
+ *(
265
+ call_mcp_tools_service(
266
+ self._client,
267
+ "read_file",
268
+ {"path": target, "yaml_path": yaml_path, **extra},
269
+ )
270
+ for target in targets
271
+ ),
272
+ return_exceptions=is_glob,
273
+ )
274
+
275
+ matches: list[dict[str, Any]] = []
276
+ warnings: list[str] = []
277
+ for target, response in zip(targets, responses, strict=True):
278
+ match, warning = _evaluate_read(
279
+ response,
280
+ target,
281
+ yaml_path,
282
+ is_glob=is_glob,
283
+ include_content=include_content,
284
+ include_parsed=include_parsed,
285
+ )
286
+ if warning:
287
+ warnings.append(warning)
288
+ elif match:
289
+ matches.append(match)
290
+
291
+ result: dict[str, Any] = {
292
+ "success": True,
293
+ "yaml_path": yaml_path,
294
+ "matches": matches,
295
+ "count": len(matches),
296
+ # Separates "the glob matched no files" from "no file defines
297
+ # the key" — same empty matches[], different fix.
298
+ "files_searched": len(targets),
299
+ }
300
+ if warnings:
301
+ result["warnings"] = warnings
302
+ return result
303
+
304
+ except ToolError:
305
+ raise
306
+ except Exception as e:
307
+ exception_to_structured_error(
308
+ e,
309
+ context={
310
+ "tool": "ha_config_get_yaml",
311
+ "file": file,
312
+ "yaml_path": yaml_path,
313
+ },
314
+ )
315
+ # Unreachable (the call is typed NoReturn) but explicit, because
316
+ # CodeQL cannot see the NoReturn and would otherwise read this
317
+ # handler as falling through to an implicit None (py/mixed-returns).
318
+ # The sibling tools additionally carry a `return None` AFTER the
319
+ # try/except; that shape only works because their try ends in a
320
+ # raise_tool_error() call CodeQL treats as returning. This try ends
321
+ # in a real return, so the same trailer is provably dead there
322
+ # (py/unreachable-statement).
323
+ return None
324
+
325
+
326
+ def register_yaml_read_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
327
+ """Register the read-only YAML lookup tool.
328
+
329
+ Behind ``enable_filesystem_tools`` (see module docstring): this returns
330
+ config-file contents, so it registers with the same flag as the other
331
+ file-read tools rather than with the YAML *editing* flag.
332
+ """
333
+ if not is_filesystem_tools_enabled():
334
+ logger.debug("YAML read tool disabled (set HAMCP_ENABLE_FILESYSTEM_TOOLS=true)")
335
+ return
336
+ register_tool_methods(mcp, YamlReadTools(client))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ha-mcp-dev
3
- Version: 7.12.3.dev1934
3
+ Version: 7.12.3.dev1937
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
@@ -129,6 +129,7 @@ src/ha_mcp/tools/tools_updates.py
129
129
  src/ha_mcp/tools/tools_utility.py
130
130
  src/ha_mcp/tools/tools_voice_assistant.py
131
131
  src/ha_mcp/tools/tools_yaml_config.py
132
+ src/ha_mcp/tools/tools_yaml_read.py
132
133
  src/ha_mcp/tools/tools_zones.py
133
134
  src/ha_mcp/tools/util_helpers.py
134
135
  src/ha_mcp/tools/validation_middleware.py