ha-mcp-dev 7.5.0.dev576__tar.gz → 7.5.0.dev577__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.
- {ha_mcp_dev-7.5.0.dev576/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.5.0.dev577}/PKG-INFO +1 -1
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/pyproject.toml +1 -2
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/tools/best_practice_checker.py +34 -29
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/tools/tools_bug_report.py +81 -59
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/tools/tools_config_automations.py +243 -249
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/tools/tools_config_entry_flow.py +315 -249
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/tools/tools_service.py +67 -53
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/tools/tools_updates.py +118 -69
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/tools/tools_yaml_config.py +33 -22
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/tools/util_helpers.py +437 -358
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/LICENSE +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/MANIFEST.in +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/README.md +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/setup.cfg +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/__init__.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/__main__.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/_pypi_marker +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/_version.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/auth/__init__.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/auth/consent_form.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/auth/provider.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/client/__init__.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/client/rest_client.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/client/supervisor_client.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/client/websocket_client.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/client/websocket_listener.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/config.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/errors.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/py.typed +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/yaml-only-integrations.md +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/server.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/settings_ui.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/smoke_test.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/tools/__init__.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/tools/backup.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/tools/device_control.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/tools/enhanced.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/tools/helpers.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/tools/reference_validator.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/tools/registry.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/tools/smart_search.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/tools/tools_addons.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/tools/tools_areas.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/tools/tools_blueprints.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/tools/tools_calendar.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/tools/tools_camera.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/tools/tools_categories.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/tools/tools_code.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/tools/tools_energy.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/tools/tools_entities.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/tools/tools_filesystem.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/tools/tools_groups.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/tools/tools_hacs.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/tools/tools_history.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/tools/tools_integrations.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/tools/tools_labels.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/tools/tools_mcp_component.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/tools/tools_registry.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/tools/tools_resources.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/tools/tools_search.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/tools/tools_services.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/tools/tools_system.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/tools/tools_todo.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/tools/tools_traces.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/tools/tools_utility.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/tools/tools_zones.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/transforms/__init__.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/transforms/categorized_search.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/utils/__init__.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/utils/config_hash.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/utils/data_paths.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/utils/domain_handlers.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/utils/fuzzy_search.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/utils/operation_manager.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/utils/python_sandbox.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/utils/usage_logger.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/tests/__init__.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/tests/test_constants.py +0 -0
- {ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/tests/test_env_manager.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ha-mcp-dev"
|
|
7
|
-
version = "7.5.0.
|
|
7
|
+
version = "7.5.0.dev577"
|
|
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"
|
|
@@ -148,7 +148,6 @@ ignore = [
|
|
|
148
148
|
"src/ha_mcp/tools/tools_search.py" = ["C901"]
|
|
149
149
|
"src/ha_mcp/tools/tools_utility.py" = ["C901"]
|
|
150
150
|
"src/ha_mcp/tools/smart_search.py" = ["C901"]
|
|
151
|
-
"src/ha_mcp/tools/util_helpers.py" = ["C901"]
|
|
152
151
|
|
|
153
152
|
[tool.pytest.ini_options]
|
|
154
153
|
testpaths = ["tests"]
|
{ha_mcp_dev-7.5.0.dev576 → ha_mcp_dev-7.5.0.dev577}/src/ha_mcp/tools/best_practice_checker.py
RENAMED
|
@@ -279,10 +279,7 @@ def _check_template_string(
|
|
|
279
279
|
# reframes #695 from "enumerate bad shapes" to "surface every template
|
|
280
280
|
# in a logic position". Specific detectors above keep their tailored
|
|
281
281
|
# messages.
|
|
282
|
-
if (
|
|
283
|
-
len(warnings) == initial_count
|
|
284
|
-
and _RE_ANY_TEMPLATE.search(template)
|
|
285
|
-
):
|
|
282
|
+
if len(warnings) == initial_count and _RE_ANY_TEMPLATE.search(template):
|
|
286
283
|
warnings.append(
|
|
287
284
|
f"Template detected in {position} — if this maps to a native option "
|
|
288
285
|
"(`numeric_state`, `state`, `time`, `sun`, `zone`, `device`), use that "
|
|
@@ -304,9 +301,7 @@ def _check_choose_actions(
|
|
|
304
301
|
_check_condition_templates(
|
|
305
302
|
option.get("conditions", []), warnings, skill_prefix
|
|
306
303
|
)
|
|
307
|
-
_check_action_tree(
|
|
308
|
-
option.get("sequence", []), warnings, skill_prefix
|
|
309
|
-
)
|
|
304
|
+
_check_action_tree(option.get("sequence", []), warnings, skill_prefix)
|
|
310
305
|
|
|
311
306
|
|
|
312
307
|
def _check_repeat_actions(
|
|
@@ -317,6 +312,31 @@ def _check_repeat_actions(
|
|
|
317
312
|
_check_action_tree(repeat.get("sequence", []), warnings, skill_prefix)
|
|
318
313
|
|
|
319
314
|
|
|
315
|
+
def _check_control_flow_actions(
|
|
316
|
+
action: dict[str, Any], warnings: list[str], skill_prefix: str | None
|
|
317
|
+
) -> None:
|
|
318
|
+
"""Check choose/if/then/else/repeat/parallel sub-trees in a single action."""
|
|
319
|
+
if "choose" in action:
|
|
320
|
+
_check_choose_actions(action["choose"], warnings, skill_prefix)
|
|
321
|
+
|
|
322
|
+
if "if" in action:
|
|
323
|
+
_check_condition_templates(action["if"], warnings, skill_prefix)
|
|
324
|
+
|
|
325
|
+
for key in ("then", "else", "default"):
|
|
326
|
+
nested = action.get(key)
|
|
327
|
+
if isinstance(nested, list):
|
|
328
|
+
_check_action_tree(nested, warnings, skill_prefix)
|
|
329
|
+
|
|
330
|
+
if "repeat" in action and isinstance(action["repeat"], dict):
|
|
331
|
+
_check_repeat_actions(action["repeat"], warnings, skill_prefix)
|
|
332
|
+
|
|
333
|
+
# `parallel:` runs sub-actions concurrently — same shape as `sequence`,
|
|
334
|
+
# different semantics. Recurse so templates inside parallel branches
|
|
335
|
+
# are inspected the same as templates inside choose/repeat sequences.
|
|
336
|
+
if "parallel" in action and isinstance(action["parallel"], list):
|
|
337
|
+
_check_action_tree(action["parallel"], warnings, skill_prefix)
|
|
338
|
+
|
|
339
|
+
|
|
320
340
|
def _check_action_tree(
|
|
321
341
|
actions: Any, warnings: list[str], skill_prefix: str | None
|
|
322
342
|
) -> None:
|
|
@@ -359,26 +379,7 @@ def _check_action_tree(
|
|
|
359
379
|
if isinstance(target, dict):
|
|
360
380
|
_check_target_dict(target, warnings, skill_prefix)
|
|
361
381
|
|
|
362
|
-
|
|
363
|
-
if "choose" in action:
|
|
364
|
-
_check_choose_actions(action["choose"], warnings, skill_prefix)
|
|
365
|
-
|
|
366
|
-
if "if" in action:
|
|
367
|
-
_check_condition_templates(action["if"], warnings, skill_prefix)
|
|
368
|
-
|
|
369
|
-
for key in ("then", "else", "default"):
|
|
370
|
-
nested = action.get(key)
|
|
371
|
-
if isinstance(nested, list):
|
|
372
|
-
_check_action_tree(nested, warnings, skill_prefix)
|
|
373
|
-
|
|
374
|
-
if "repeat" in action and isinstance(action["repeat"], dict):
|
|
375
|
-
_check_repeat_actions(action["repeat"], warnings, skill_prefix)
|
|
376
|
-
|
|
377
|
-
# `parallel:` runs sub-actions concurrently — same shape as `sequence`,
|
|
378
|
-
# different semantics. Recurse so templates inside parallel branches
|
|
379
|
-
# are inspected the same as templates inside choose/repeat sequences.
|
|
380
|
-
if "parallel" in action and isinstance(action["parallel"], list):
|
|
381
|
-
_check_action_tree(action["parallel"], warnings, skill_prefix)
|
|
382
|
+
_check_control_flow_actions(action, warnings, skill_prefix)
|
|
382
383
|
|
|
383
384
|
|
|
384
385
|
def _check_service_template(
|
|
@@ -439,7 +440,9 @@ def _check_target_dict(
|
|
|
439
440
|
f"hardcode the literal value instead. The self-reference is always "
|
|
440
441
|
f"resolvable at write time, so the template adds runtime cost without "
|
|
441
442
|
f"any flexibility."
|
|
442
|
-
+ _ref(
|
|
443
|
+
+ _ref(
|
|
444
|
+
skill_prefix, "template-guidelines.md#when-to-avoid-templates"
|
|
445
|
+
)
|
|
443
446
|
)
|
|
444
447
|
else:
|
|
445
448
|
warnings.append(
|
|
@@ -447,7 +450,9 @@ def _check_target_dict(
|
|
|
447
450
|
f"or use a `choose` action with native conditions to dispatch to different "
|
|
448
451
|
f"hardcoded targets. Templates in target fields fail silently if they "
|
|
449
452
|
f"resolve to a non-existent entity."
|
|
450
|
-
+ _ref(
|
|
453
|
+
+ _ref(
|
|
454
|
+
skill_prefix, "template-guidelines.md#when-to-avoid-templates"
|
|
455
|
+
)
|
|
451
456
|
)
|
|
452
457
|
|
|
453
458
|
|
|
@@ -16,6 +16,7 @@ from urllib.parse import quote_plus
|
|
|
16
16
|
|
|
17
17
|
import httpx
|
|
18
18
|
from fastmcp import Context
|
|
19
|
+
from fastmcp.tools import tool
|
|
19
20
|
from pydantic import Field
|
|
20
21
|
|
|
21
22
|
from ha_mcp import __version__
|
|
@@ -28,7 +29,7 @@ from ..utils.usage_logger import (
|
|
|
28
29
|
get_recent_logs,
|
|
29
30
|
get_startup_logs,
|
|
30
31
|
)
|
|
31
|
-
from .helpers import log_tool_usage
|
|
32
|
+
from .helpers import log_tool_usage, register_tool_methods
|
|
32
33
|
from .util_helpers import ANSI_ESCAPE_RE
|
|
33
34
|
|
|
34
35
|
logger = logging.getLogger(__name__)
|
|
@@ -388,10 +389,66 @@ async def _fetch_addon_logs() -> str:
|
|
|
388
389
|
return ""
|
|
389
390
|
|
|
390
391
|
|
|
391
|
-
def
|
|
392
|
-
|
|
392
|
+
def _build_formatted_report(
|
|
393
|
+
diagnostic_info: dict[str, Any],
|
|
394
|
+
mcp_transport: str,
|
|
395
|
+
client_info: dict[str, str],
|
|
396
|
+
platform_info: dict[str, str],
|
|
397
|
+
config_toggles: dict[str, Any],
|
|
398
|
+
startup_logs: list[dict[str, Any]],
|
|
399
|
+
startup_log_summary: str,
|
|
400
|
+
recent_logs: list[dict[str, Any]],
|
|
401
|
+
log_summary: str,
|
|
402
|
+
addon_logs: str,
|
|
403
|
+
) -> str:
|
|
404
|
+
report_lines = [
|
|
405
|
+
"=== ha-mcp Bug Report Info ===",
|
|
406
|
+
"",
|
|
407
|
+
f"ha-mcp Version: {diagnostic_info['ha_mcp_version']}",
|
|
408
|
+
f"Installation Method: {diagnostic_info['installation_method']}",
|
|
409
|
+
f"MCP Transport: {mcp_transport}",
|
|
410
|
+
f"MCP Client: {_format_client_info_for_template(client_info)}",
|
|
411
|
+
f"Operating System: {platform_info['os']} {platform_info['os_release']} ({platform_info['architecture']})",
|
|
412
|
+
f"Python Version: {platform_info['python_version']}",
|
|
413
|
+
f"Home Assistant Version: {diagnostic_info['home_assistant_version']}",
|
|
414
|
+
f"Connection Status: {diagnostic_info['connection_status']}",
|
|
415
|
+
f"Entity Count: {diagnostic_info['entity_count']}",
|
|
416
|
+
]
|
|
417
|
+
if "location_name" in diagnostic_info:
|
|
418
|
+
report_lines.append(f"Location Name: {diagnostic_info['location_name']}")
|
|
419
|
+
if "time_zone" in diagnostic_info:
|
|
420
|
+
report_lines.append(f"Time Zone: {diagnostic_info['time_zone']}")
|
|
421
|
+
if config_toggles:
|
|
422
|
+
report_lines.extend(["", "=== ha-mcp Config Toggles ==="])
|
|
423
|
+
for key, value in config_toggles.items():
|
|
424
|
+
report_lines.append(f" {key}: {value}")
|
|
425
|
+
if startup_logs:
|
|
426
|
+
report_lines.extend(
|
|
427
|
+
[
|
|
428
|
+
"",
|
|
429
|
+
f"=== Startup Logs ({len(startup_logs)} entries) ===",
|
|
430
|
+
startup_log_summary,
|
|
431
|
+
]
|
|
432
|
+
)
|
|
433
|
+
if recent_logs:
|
|
434
|
+
report_lines.extend(
|
|
435
|
+
[
|
|
436
|
+
"",
|
|
437
|
+
f"=== Recent Tool Calls ({len(recent_logs)} entries) ===",
|
|
438
|
+
log_summary,
|
|
439
|
+
]
|
|
440
|
+
)
|
|
441
|
+
if addon_logs:
|
|
442
|
+
report_lines.extend(["", "=== Add-on Container Logs ===", addon_logs])
|
|
443
|
+
return "\n".join(report_lines)
|
|
444
|
+
|
|
393
445
|
|
|
394
|
-
|
|
446
|
+
class BugReportTools:
|
|
447
|
+
def __init__(self, client: Any) -> None:
|
|
448
|
+
self._client = client
|
|
449
|
+
|
|
450
|
+
@tool(
|
|
451
|
+
name="ha_report_issue",
|
|
395
452
|
tags={"Utilities"},
|
|
396
453
|
annotations={
|
|
397
454
|
"idempotentHint": True,
|
|
@@ -401,6 +458,7 @@ def register_bug_report_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
|
|
|
401
458
|
)
|
|
402
459
|
@log_tool_usage
|
|
403
460
|
async def ha_report_issue(
|
|
461
|
+
self,
|
|
404
462
|
tool_call_count: Annotated[
|
|
405
463
|
int,
|
|
406
464
|
Field(
|
|
@@ -476,7 +534,7 @@ def register_bug_report_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
|
|
|
476
534
|
|
|
477
535
|
# Try to get Home Assistant config and connection status
|
|
478
536
|
try:
|
|
479
|
-
config = await
|
|
537
|
+
config = await self._client.get_config()
|
|
480
538
|
diagnostic_info["connection_status"] = "Connected"
|
|
481
539
|
diagnostic_info["home_assistant_version"] = config.get("version", "Unknown")
|
|
482
540
|
diagnostic_info["location_name"] = config.get("location_name", "Unknown")
|
|
@@ -487,7 +545,7 @@ def register_bug_report_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
|
|
|
487
545
|
|
|
488
546
|
# Try to get entity count
|
|
489
547
|
try:
|
|
490
|
-
states = await
|
|
548
|
+
states = await self._client.get_states()
|
|
491
549
|
if states:
|
|
492
550
|
diagnostic_info["entity_count"] = len(states)
|
|
493
551
|
except Exception as e:
|
|
@@ -511,59 +569,18 @@ def register_bug_report_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
|
|
|
511
569
|
startup_log_summary = _format_startup_logs(startup_logs)
|
|
512
570
|
|
|
513
571
|
# Build the formatted report
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
]
|
|
527
|
-
|
|
528
|
-
# Add optional fields if available
|
|
529
|
-
if "location_name" in diagnostic_info:
|
|
530
|
-
report_lines.append(f"Location Name: {diagnostic_info['location_name']}")
|
|
531
|
-
if "time_zone" in diagnostic_info:
|
|
532
|
-
report_lines.append(f"Time Zone: {diagnostic_info['time_zone']}")
|
|
533
|
-
|
|
534
|
-
if config_toggles:
|
|
535
|
-
report_lines.extend(["", "=== ha-mcp Config Toggles ==="])
|
|
536
|
-
for key, value in config_toggles.items():
|
|
537
|
-
report_lines.append(f" {key}: {value}")
|
|
538
|
-
|
|
539
|
-
if startup_logs:
|
|
540
|
-
report_lines.extend(
|
|
541
|
-
[
|
|
542
|
-
"",
|
|
543
|
-
f"=== Startup Logs ({len(startup_logs)} entries) ===",
|
|
544
|
-
startup_log_summary,
|
|
545
|
-
]
|
|
546
|
-
)
|
|
547
|
-
|
|
548
|
-
if recent_logs:
|
|
549
|
-
report_lines.extend(
|
|
550
|
-
[
|
|
551
|
-
"",
|
|
552
|
-
f"=== Recent Tool Calls ({len(recent_logs)} entries) ===",
|
|
553
|
-
log_summary,
|
|
554
|
-
]
|
|
555
|
-
)
|
|
556
|
-
|
|
557
|
-
if addon_logs:
|
|
558
|
-
report_lines.extend(
|
|
559
|
-
[
|
|
560
|
-
"",
|
|
561
|
-
"=== Add-on Container Logs ===",
|
|
562
|
-
addon_logs,
|
|
563
|
-
]
|
|
564
|
-
)
|
|
565
|
-
|
|
566
|
-
formatted_report = "\n".join(report_lines)
|
|
572
|
+
formatted_report = _build_formatted_report(
|
|
573
|
+
diagnostic_info,
|
|
574
|
+
mcp_transport,
|
|
575
|
+
client_info,
|
|
576
|
+
platform_info,
|
|
577
|
+
config_toggles,
|
|
578
|
+
startup_logs,
|
|
579
|
+
startup_log_summary,
|
|
580
|
+
recent_logs,
|
|
581
|
+
log_summary,
|
|
582
|
+
addon_logs,
|
|
583
|
+
)
|
|
567
584
|
|
|
568
585
|
# Generate suggested title up-front so it can be folded into the
|
|
569
586
|
# submission URLs as a `&title=` query param. This auto-fills the
|
|
@@ -673,6 +690,11 @@ def register_bug_report_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
|
|
|
673
690
|
}
|
|
674
691
|
|
|
675
692
|
|
|
693
|
+
def register_bug_report_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
|
|
694
|
+
"""Register bug report tools with the MCP server."""
|
|
695
|
+
register_tool_methods(mcp, BugReportTools(client))
|
|
696
|
+
|
|
697
|
+
|
|
676
698
|
def _format_config_toggles_for_template(toggles: dict[str, Any]) -> str:
|
|
677
699
|
"""Render config toggle snapshot as a markdown bullet list.
|
|
678
700
|
|