ha-mcp-dev 7.4.1.dev464__tar.gz → 7.4.1.dev465__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.4.1.dev464/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.4.1.dev465}/PKG-INFO +1 -1
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/pyproject.toml +1 -1
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_bug_report.py +372 -55
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/LICENSE +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/MANIFEST.in +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/README.md +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/setup.cfg +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/__init__.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/__main__.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/_pypi_marker +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/_version.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/auth/__init__.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/auth/consent_form.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/auth/provider.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/client/__init__.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/client/rest_client.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/client/websocket_client.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/client/websocket_listener.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/config.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/errors.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/py.typed +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/server.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/settings_ui.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/smoke_test.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/__init__.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/backup.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/best_practice_checker.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/device_control.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/enhanced.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/helpers.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/reference_validator.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/registry.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/smart_search.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_addons.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_areas.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_blueprints.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_calendar.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_camera.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_categories.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_code.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_config_automations.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_config_entry_flow.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_energy.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_entities.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_filesystem.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_groups.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_hacs.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_history.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_integrations.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_labels.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_mcp_component.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_registry.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_resources.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_search.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_service.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_services.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_system.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_todo.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_traces.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_updates.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_utility.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_zones.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/util_helpers.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/transforms/__init__.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/transforms/categorized_search.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/utils/__init__.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/utils/config_hash.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/utils/data_paths.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/utils/domain_handlers.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/utils/fuzzy_search.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/utils/operation_manager.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/utils/python_sandbox.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/utils/usage_logger.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/tests/__init__.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/tests/test_constants.py +0 -0
- {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/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.4.1.
|
|
7
|
+
version = "7.4.1.dev465"
|
|
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"
|
|
@@ -15,11 +15,12 @@ from typing import Annotated, Any
|
|
|
15
15
|
from urllib.parse import quote_plus
|
|
16
16
|
|
|
17
17
|
import httpx
|
|
18
|
+
from fastmcp import Context
|
|
18
19
|
from pydantic import Field
|
|
19
20
|
|
|
20
21
|
from ha_mcp import __version__
|
|
21
22
|
|
|
22
|
-
from ..config import get_global_settings
|
|
23
|
+
from ..config import Settings, get_global_settings
|
|
23
24
|
from ..utils.usage_logger import (
|
|
24
25
|
AVG_LOG_ENTRIES_PER_TOOL,
|
|
25
26
|
get_recent_logs,
|
|
@@ -31,8 +32,12 @@ from .util_helpers import ANSI_ESCAPE_RE
|
|
|
31
32
|
logger = logging.getLogger(__name__)
|
|
32
33
|
|
|
33
34
|
# GitHub issue template URLs
|
|
34
|
-
RUNTIME_BUG_URL =
|
|
35
|
-
|
|
35
|
+
RUNTIME_BUG_URL = (
|
|
36
|
+
"https://github.com/homeassistant-ai/ha-mcp/issues/new?template=runtime_bug.yml"
|
|
37
|
+
)
|
|
38
|
+
AGENT_BEHAVIOR_URL = (
|
|
39
|
+
"https://github.com/homeassistant-ai/ha-mcp/issues/new?template=agent_behavior.yml"
|
|
40
|
+
)
|
|
36
41
|
|
|
37
42
|
# Max characters to include from addon container logs.
|
|
38
43
|
# 3000 chars ≈ 750 LLM tokens — keeps the tool response well below context budgets
|
|
@@ -104,6 +109,172 @@ def _detect_platform() -> dict[str, str]:
|
|
|
104
109
|
}
|
|
105
110
|
|
|
106
111
|
|
|
112
|
+
# Tool-surface-shaping toggles surfaced in bug reports. The set is small on
|
|
113
|
+
# purpose: only flags that materially change which tools the agent sees, since
|
|
114
|
+
# the same bug report behaves very differently depending on these. New
|
|
115
|
+
# tool-shaping toggles should be added here so triage doesn't have to ask.
|
|
116
|
+
_CONFIG_TOGGLE_FIELDS: tuple[str, ...] = (
|
|
117
|
+
"enable_websocket",
|
|
118
|
+
"enable_dashboard_partial_tools",
|
|
119
|
+
"enable_tool_search",
|
|
120
|
+
"tool_search_max_results",
|
|
121
|
+
"enable_yaml_config_editing",
|
|
122
|
+
"enable_code_mode",
|
|
123
|
+
"enabled_tool_modules",
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _get_config_toggles(settings: Settings | None = None) -> dict[str, Any]:
|
|
128
|
+
"""Read tool-surface-shaping config toggles from Settings.
|
|
129
|
+
|
|
130
|
+
Defaults to the global settings singleton; tests can pass a fake Settings
|
|
131
|
+
instance instead. Returns an empty dict on any failure (Settings
|
|
132
|
+
construction, attribute coercion, list-field split) so a misconfigured
|
|
133
|
+
environment can't break the bug report path itself.
|
|
134
|
+
"""
|
|
135
|
+
try:
|
|
136
|
+
s = settings if settings is not None else get_global_settings()
|
|
137
|
+
|
|
138
|
+
toggles: dict[str, Any] = {}
|
|
139
|
+
for field in _CONFIG_TOGGLE_FIELDS:
|
|
140
|
+
value = getattr(s, field, None)
|
|
141
|
+
if value is None:
|
|
142
|
+
continue
|
|
143
|
+
toggles[field] = value
|
|
144
|
+
|
|
145
|
+
# Summarize list-shaped seeds as counts rather than dumping the full
|
|
146
|
+
# strings — they can be very long, and listing the exact tools the
|
|
147
|
+
# user disabled isn't useful for triage.
|
|
148
|
+
for list_field in ("disabled_tools", "pinned_tools"):
|
|
149
|
+
raw = getattr(s, list_field, "") or ""
|
|
150
|
+
count = len([item for item in raw.split(",") if item.strip()])
|
|
151
|
+
toggles[f"{list_field}_count"] = count
|
|
152
|
+
|
|
153
|
+
return toggles
|
|
154
|
+
except Exception as e:
|
|
155
|
+
logger.warning(
|
|
156
|
+
"Failed to read settings for bug report toggles: %s (%s)",
|
|
157
|
+
e,
|
|
158
|
+
type(e).__name__,
|
|
159
|
+
)
|
|
160
|
+
return {}
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _extract_client_info(ctx: Context | None) -> dict[str, str]:
|
|
164
|
+
"""Pull the connecting MCP client's self-identification off the request context.
|
|
165
|
+
|
|
166
|
+
The MCP ``initialize`` handshake carries a ``clientInfo`` Implementation
|
|
167
|
+
object (``name``/``version``/optional ``title``). FastMCP exposes the
|
|
168
|
+
underlying server session as ``ctx.session``; the MCP SDK's
|
|
169
|
+
``ServerSession`` keeps the parsed initialize params on ``client_params``.
|
|
170
|
+
The attribute name on the parsed Pydantic model is ``clientInfo`` in
|
|
171
|
+
``mcp`` 1.24.x (the version this project pins) — we also fall back to
|
|
172
|
+
``client_info`` to stay forward-compatible with SDK versions that switch
|
|
173
|
+
to snake_case.
|
|
174
|
+
|
|
175
|
+
Returns ``{"name": ..., "version": ..., "title": ...}``. ``name`` and
|
|
176
|
+
``version`` fall back to ``"unknown"`` when the client didn't send them;
|
|
177
|
+
``title`` falls back to the empty string so callers can distinguish "not
|
|
178
|
+
sent" from a real title without false-positive aside rendering.
|
|
179
|
+
|
|
180
|
+
Returns an empty dict if no context is available (tool invoked outside an
|
|
181
|
+
MCP request, e.g. unit tests) so the bug-report path stays robust. The
|
|
182
|
+
log level is intentionally INFO, not DEBUG: this catch is the only signal
|
|
183
|
+
we'd get if FastMCP/MCP SDK shape drifts in a future release, and silent
|
|
184
|
+
drift would hide a regression for months.
|
|
185
|
+
"""
|
|
186
|
+
if ctx is None:
|
|
187
|
+
return {}
|
|
188
|
+
try:
|
|
189
|
+
session = getattr(ctx, "session", None)
|
|
190
|
+
params = (
|
|
191
|
+
getattr(session, "client_params", None) if session is not None else None
|
|
192
|
+
)
|
|
193
|
+
if params is None:
|
|
194
|
+
return {}
|
|
195
|
+
# Try the camelCase attribute (mcp 1.24.x) first, then snake_case so
|
|
196
|
+
# we keep working if the SDK switches the alias direction.
|
|
197
|
+
client = getattr(params, "clientInfo", None) or getattr(
|
|
198
|
+
params, "client_info", None
|
|
199
|
+
)
|
|
200
|
+
if client is None:
|
|
201
|
+
return {}
|
|
202
|
+
return {
|
|
203
|
+
"name": getattr(client, "name", None) or "unknown",
|
|
204
|
+
"version": getattr(client, "version", None) or "unknown",
|
|
205
|
+
"title": getattr(client, "title", None) or "",
|
|
206
|
+
}
|
|
207
|
+
except Exception as e:
|
|
208
|
+
logger.info(
|
|
209
|
+
"Failed to read MCP client info from context: %s (%s)",
|
|
210
|
+
e,
|
|
211
|
+
type(e).__name__,
|
|
212
|
+
)
|
|
213
|
+
return {}
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def _format_client_info_for_template(info: dict[str, str]) -> str:
|
|
217
|
+
"""Render the MCP client identification as a single human-readable line.
|
|
218
|
+
|
|
219
|
+
Falls back to ``unknown (client did not advertise itself)`` when no
|
|
220
|
+
client info was available — this happens for direct MCP clients that
|
|
221
|
+
skip the optional ``clientInfo`` field, or when the bug report tool
|
|
222
|
+
runs outside a live request. Phrasing is deliberately observable
|
|
223
|
+
rather than naming the underlying API field (which may be renamed).
|
|
224
|
+
"""
|
|
225
|
+
if not info:
|
|
226
|
+
return "unknown (client did not advertise itself)"
|
|
227
|
+
name = info.get("name") or "unknown"
|
|
228
|
+
version = info.get("version") or "unknown"
|
|
229
|
+
title = info.get("title") or ""
|
|
230
|
+
base = f"{name} {version}"
|
|
231
|
+
if title and title != name:
|
|
232
|
+
return f"{base} _(advertised title: {title})_"
|
|
233
|
+
return base
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def _detect_mcp_transport() -> str:
|
|
237
|
+
"""Best-effort MCP transport detection.
|
|
238
|
+
|
|
239
|
+
Returns ``stdio`` / ``http`` / ``sse`` / ``unknown``. We can't observe the
|
|
240
|
+
transport perfectly from a tool call, so we look at the entrypoint name
|
|
241
|
+
and well-known env hints. The result is informational — the bug template
|
|
242
|
+
surfaces it as an auto-detect that the agent or user can override.
|
|
243
|
+
"""
|
|
244
|
+
# Entry-point script name (e.g. ``ha-mcp-web`` for HTTP, ``ha-mcp-sse``
|
|
245
|
+
# for SSE; pyproject.toml's [project.scripts] is the source of truth).
|
|
246
|
+
argv0 = (sys.argv[0] if sys.argv else "").lower()
|
|
247
|
+
basename = os.path.basename(argv0)
|
|
248
|
+
if basename.endswith("-web"):
|
|
249
|
+
return "http"
|
|
250
|
+
if basename.endswith("-sse"):
|
|
251
|
+
return "sse"
|
|
252
|
+
|
|
253
|
+
# Env hints set by HTTP wrappers / supervisors. ``streamable-http`` is the
|
|
254
|
+
# documented FastMCP variant; collapse it to ``http`` since the
|
|
255
|
+
# distinction doesn't change triage decisions.
|
|
256
|
+
transport_env = os.environ.get("FASTMCP_TRANSPORT", "").strip().lower()
|
|
257
|
+
if transport_env in {"http", "stdio", "sse"}:
|
|
258
|
+
return transport_env
|
|
259
|
+
if transport_env == "streamable-http":
|
|
260
|
+
return "http"
|
|
261
|
+
if os.environ.get("MCP_HTTP_PORT") or os.environ.get("FASTMCP_PORT"):
|
|
262
|
+
return "http"
|
|
263
|
+
|
|
264
|
+
# If stdin is piped (not a TTY), ha-mcp was launched by an MCP host on
|
|
265
|
+
# stdio. If it IS a TTY, this is a manual / interactive run with no
|
|
266
|
+
# other transport hints — fall through to ``unknown``.
|
|
267
|
+
try:
|
|
268
|
+
if not sys.stdin.isatty():
|
|
269
|
+
return "stdio"
|
|
270
|
+
except (AttributeError, OSError, ValueError):
|
|
271
|
+
# ``sys.stdin`` can be None or detached (pythonw, daemonized
|
|
272
|
+
# contexts, certain test harnesses). Treat as no signal.
|
|
273
|
+
pass
|
|
274
|
+
|
|
275
|
+
return "unknown"
|
|
276
|
+
|
|
277
|
+
|
|
107
278
|
def _sanitize_log_text(text: str) -> str:
|
|
108
279
|
"""Best-effort secret scrubber for log text.
|
|
109
280
|
|
|
@@ -190,9 +361,7 @@ async def _fetch_addon_logs() -> str:
|
|
|
190
361
|
headers={"Authorization": f"Bearer {token}"},
|
|
191
362
|
)
|
|
192
363
|
if resp.status_code != 200:
|
|
193
|
-
logger.info(
|
|
194
|
-
"Addon log fetch returned HTTP %s", resp.status_code
|
|
195
|
-
)
|
|
364
|
+
logger.info("Addon log fetch returned HTTP %s", resp.status_code)
|
|
196
365
|
return ""
|
|
197
366
|
|
|
198
367
|
# Strip ANSI escape codes first, then sanitize, then truncate.
|
|
@@ -221,8 +390,8 @@ def register_bug_report_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
|
|
|
221
390
|
annotations={
|
|
222
391
|
"idempotentHint": True,
|
|
223
392
|
"readOnlyHint": True,
|
|
224
|
-
"title": "Report Issue or Feedback"
|
|
225
|
-
}
|
|
393
|
+
"title": "Report Issue or Feedback",
|
|
394
|
+
},
|
|
226
395
|
)
|
|
227
396
|
@log_tool_usage
|
|
228
397
|
async def ha_report_issue(
|
|
@@ -240,6 +409,7 @@ def register_bug_report_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
|
|
|
240
409
|
),
|
|
241
410
|
),
|
|
242
411
|
] = 10,
|
|
412
|
+
ctx: Context | None = None,
|
|
243
413
|
) -> dict[str, Any]:
|
|
244
414
|
"""
|
|
245
415
|
Collect diagnostic information for filing issue reports or feedback.
|
|
@@ -279,14 +449,20 @@ def register_bug_report_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
|
|
|
279
449
|
empty string otherwise)
|
|
280
450
|
- `suggested_title`, `duplicate_check_urls`, `anonymization_guide`
|
|
281
451
|
"""
|
|
282
|
-
# Detect installation method and
|
|
452
|
+
# Detect installation method, platform, and runtime config.
|
|
283
453
|
install_method = _detect_installation_method()
|
|
284
454
|
platform_info = _detect_platform()
|
|
455
|
+
config_toggles = _get_config_toggles()
|
|
456
|
+
mcp_transport = _detect_mcp_transport()
|
|
457
|
+
client_info = _extract_client_info(ctx)
|
|
285
458
|
|
|
286
459
|
diagnostic_info: dict[str, Any] = {
|
|
287
460
|
"ha_mcp_version": __version__,
|
|
288
461
|
"installation_method": install_method,
|
|
289
462
|
"platform": platform_info,
|
|
463
|
+
"mcp_transport": mcp_transport,
|
|
464
|
+
"mcp_client_info": client_info,
|
|
465
|
+
"config_toggles": config_toggles,
|
|
290
466
|
"connection_status": "Unknown",
|
|
291
467
|
"home_assistant_version": "Unknown",
|
|
292
468
|
"entity_count": 0,
|
|
@@ -296,9 +472,7 @@ def register_bug_report_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
|
|
|
296
472
|
try:
|
|
297
473
|
config = await client.get_config()
|
|
298
474
|
diagnostic_info["connection_status"] = "Connected"
|
|
299
|
-
diagnostic_info["home_assistant_version"] = config.get(
|
|
300
|
-
"version", "Unknown"
|
|
301
|
-
)
|
|
475
|
+
diagnostic_info["home_assistant_version"] = config.get("version", "Unknown")
|
|
302
476
|
diagnostic_info["location_name"] = config.get("location_name", "Unknown")
|
|
303
477
|
diagnostic_info["time_zone"] = config.get("time_zone", "Unknown")
|
|
304
478
|
except Exception as e:
|
|
@@ -336,7 +510,9 @@ def register_bug_report_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
|
|
|
336
510
|
"",
|
|
337
511
|
f"ha-mcp Version: {diagnostic_info['ha_mcp_version']}",
|
|
338
512
|
f"Installation Method: {diagnostic_info['installation_method']}",
|
|
339
|
-
f"
|
|
513
|
+
f"MCP Transport: {mcp_transport}",
|
|
514
|
+
f"MCP Client: {_format_client_info_for_template(client_info)}",
|
|
515
|
+
f"Operating System: {platform_info['os']} {platform_info['os_release']} ({platform_info['architecture']})",
|
|
340
516
|
f"Python Version: {platform_info['python_version']}",
|
|
341
517
|
f"Home Assistant Version: {diagnostic_info['home_assistant_version']}",
|
|
342
518
|
f"Connection Status: {diagnostic_info['connection_status']}",
|
|
@@ -349,45 +525,70 @@ def register_bug_report_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
|
|
|
349
525
|
if "time_zone" in diagnostic_info:
|
|
350
526
|
report_lines.append(f"Time Zone: {diagnostic_info['time_zone']}")
|
|
351
527
|
|
|
528
|
+
if config_toggles:
|
|
529
|
+
report_lines.extend(["", "=== ha-mcp Config Toggles ==="])
|
|
530
|
+
for key, value in config_toggles.items():
|
|
531
|
+
report_lines.append(f" {key}: {value}")
|
|
532
|
+
|
|
352
533
|
if startup_logs:
|
|
353
|
-
report_lines.extend(
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
534
|
+
report_lines.extend(
|
|
535
|
+
[
|
|
536
|
+
"",
|
|
537
|
+
f"=== Startup Logs ({len(startup_logs)} entries) ===",
|
|
538
|
+
startup_log_summary,
|
|
539
|
+
]
|
|
540
|
+
)
|
|
358
541
|
|
|
359
542
|
if recent_logs:
|
|
360
|
-
report_lines.extend(
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
543
|
+
report_lines.extend(
|
|
544
|
+
[
|
|
545
|
+
"",
|
|
546
|
+
f"=== Recent Tool Calls ({len(recent_logs)} entries) ===",
|
|
547
|
+
log_summary,
|
|
548
|
+
]
|
|
549
|
+
)
|
|
365
550
|
|
|
366
551
|
if addon_logs:
|
|
367
|
-
report_lines.extend(
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
552
|
+
report_lines.extend(
|
|
553
|
+
[
|
|
554
|
+
"",
|
|
555
|
+
"=== Add-on Container Logs ===",
|
|
556
|
+
addon_logs,
|
|
557
|
+
]
|
|
558
|
+
)
|
|
372
559
|
|
|
373
560
|
formatted_report = "\n".join(report_lines)
|
|
374
561
|
|
|
562
|
+
# Generate suggested title up-front so it can be folded into the
|
|
563
|
+
# submission URLs as a `&title=` query param. This auto-fills the
|
|
564
|
+
# GitHub issue title field — without it, users routinely submit reports
|
|
565
|
+
# titled just "[BUG]".
|
|
566
|
+
suggested_title = _generate_bug_title(diagnostic_info, recent_logs)
|
|
567
|
+
title_query = quote_plus(suggested_title)
|
|
568
|
+
runtime_bug_submit_url = f"{RUNTIME_BUG_URL}&title={title_query}"
|
|
569
|
+
agent_behavior_submit_url = f"{AGENT_BEHAVIOR_URL}&title={title_query}"
|
|
570
|
+
|
|
375
571
|
# Generate BOTH templates
|
|
376
572
|
runtime_bug_template = _generate_runtime_bug_template(
|
|
377
|
-
diagnostic_info,
|
|
573
|
+
diagnostic_info,
|
|
574
|
+
log_summary,
|
|
575
|
+
startup_log_summary,
|
|
576
|
+
recent_logs,
|
|
577
|
+
startup_logs,
|
|
378
578
|
addon_logs=addon_logs,
|
|
579
|
+
submit_url=runtime_bug_submit_url,
|
|
379
580
|
)
|
|
380
581
|
|
|
381
582
|
agent_behavior_template = _generate_agent_behavior_template(
|
|
382
|
-
diagnostic_info,
|
|
583
|
+
diagnostic_info,
|
|
584
|
+
log_summary,
|
|
585
|
+
recent_logs,
|
|
586
|
+
submit_url=agent_behavior_submit_url,
|
|
383
587
|
)
|
|
384
588
|
|
|
385
589
|
# Anonymization instructions
|
|
386
590
|
anonymization_guide = _generate_anonymization_guide()
|
|
387
591
|
|
|
388
|
-
# Generate suggested title
|
|
389
|
-
suggested_title = _generate_bug_title(diagnostic_info, recent_logs)
|
|
390
|
-
|
|
391
592
|
# Generate search keywords and URLs for duplicate check
|
|
392
593
|
search_keywords = _generate_search_keywords(diagnostic_info, recent_logs)
|
|
393
594
|
duplicate_check_urls = [
|
|
@@ -408,12 +609,14 @@ def register_bug_report_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
|
|
|
408
609
|
"agent_behavior_template": agent_behavior_template,
|
|
409
610
|
"anonymization_guide": anonymization_guide,
|
|
410
611
|
"suggested_title": suggested_title,
|
|
612
|
+
"runtime_bug_submit_url": runtime_bug_submit_url,
|
|
613
|
+
"agent_behavior_submit_url": agent_behavior_submit_url,
|
|
411
614
|
"duplicate_check_urls": duplicate_check_urls,
|
|
412
615
|
"instructions": (
|
|
413
616
|
"WORKFLOW FOR PRESENTING BUG REPORTS:\n\n"
|
|
414
617
|
"1. **Check for duplicates FIRST** (before presenting the template):\n"
|
|
415
618
|
" - Use the duplicate_check_urls to search for similar issues\n"
|
|
416
|
-
|
|
619
|
+
' - If gh CLI is available: use `gh issue list --search "keyword"`\n'
|
|
417
620
|
" - Otherwise: inform user to check the duplicate_check_urls\n"
|
|
418
621
|
" - If duplicates found, ask user if they want to comment on existing issue instead\n\n"
|
|
419
622
|
"2. **Determine which template to present**:\n"
|
|
@@ -435,12 +638,27 @@ def register_bug_report_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
|
|
|
435
638
|
" d. Verify no tokens, passwords, or IPs are visible\n"
|
|
436
639
|
" e. Keep entity domains, error messages, and technical details\n"
|
|
437
640
|
" See anonymization_guide for full details.\n\n"
|
|
438
|
-
"4. **
|
|
439
|
-
"
|
|
641
|
+
"4. **Fill in the self-reported fields BEFORE presenting**:\n"
|
|
642
|
+
" - `**AI Model:**` — write your identity on this line (provider/family + the\n"
|
|
643
|
+
" most specific version you know, in whatever form you'd describe yourself).\n"
|
|
644
|
+
" Do not invent a version number. If you don't know it, say so or omit the\n"
|
|
645
|
+
" version. There are no options to pick from — just answer honestly.\n"
|
|
646
|
+
" - `**Triggering Prompt & Tool Call:** <fill in>` — the EXACT user message\n"
|
|
647
|
+
" and the tool call(s) that produced the bug, copy-pasted verbatim. Truncate\n"
|
|
648
|
+
" long inputs only after anonymization. This is the single most useful field\n"
|
|
649
|
+
" for triage — do not skip it.\n"
|
|
650
|
+
" `MCP Transport` and `MCP Client` are auto-detected by the server (the latter\n"
|
|
651
|
+
" from the MCP `initialize` handshake); leave both as-is unless they're clearly\n"
|
|
652
|
+
" wrong.\n\n"
|
|
653
|
+
"5. **Present the anonymized report to the user**:\n"
|
|
654
|
+
" a. Show the suggested_title (user can edit if needed) and tell them GitHub's\n"
|
|
655
|
+
" title field is now pre-filled via the submission URL — they don't need to\n"
|
|
656
|
+
" retype it.\n"
|
|
440
657
|
" b. Present the chosen ANONYMIZED template IN A MARKDOWN CODE BLOCK (```markdown...```) for easy copy/paste\n"
|
|
441
|
-
" c. PROMINENTLY display the submission URL at the top
|
|
442
|
-
|
|
443
|
-
|
|
658
|
+
" c. PROMINENTLY display the submission URL at the top — these include the\n"
|
|
659
|
+
" pre-filled title:\n"
|
|
660
|
+
" - Runtime bugs: see runtime_bug_submit_url\n"
|
|
661
|
+
" - Agent behavior: see agent_behavior_submit_url\n"
|
|
444
662
|
" d. Ask them to fill in the description sections\n"
|
|
445
663
|
" e. For HA add-on installs, the runtime bug template includes a collapsible '📦 Add-on Container Logs' section auto-filled from addon_logs — keep it as-is\n"
|
|
446
664
|
" f. Remind them to review for any remaining personal information before submitting\n\n"
|
|
@@ -449,6 +667,20 @@ def register_bug_report_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
|
|
|
449
667
|
}
|
|
450
668
|
|
|
451
669
|
|
|
670
|
+
def _format_config_toggles_for_template(toggles: dict[str, Any]) -> str:
|
|
671
|
+
"""Render config toggle snapshot as a markdown bullet list.
|
|
672
|
+
|
|
673
|
+
Returns a placeholder line when no toggles were collected (e.g. Settings
|
|
674
|
+
construction failed) so the template stays consistent.
|
|
675
|
+
"""
|
|
676
|
+
if not toggles:
|
|
677
|
+
return "_(config toggles unavailable)_"
|
|
678
|
+
lines = []
|
|
679
|
+
for key, value in toggles.items():
|
|
680
|
+
lines.append(f"- **{key}:** `{value}`")
|
|
681
|
+
return "\n".join(lines)
|
|
682
|
+
|
|
683
|
+
|
|
452
684
|
def _format_logs_for_report(logs: list[dict[str, Any]]) -> str:
|
|
453
685
|
"""Format log entries for inclusion in a bug report."""
|
|
454
686
|
if not logs:
|
|
@@ -564,7 +796,9 @@ def _generate_search_keywords(
|
|
|
564
796
|
keywords = set()
|
|
565
797
|
|
|
566
798
|
# Find the most recent error from logs
|
|
567
|
-
last_error_log = next(
|
|
799
|
+
last_error_log = next(
|
|
800
|
+
(log for log in reversed(recent_logs) if log.get("error_message")), None
|
|
801
|
+
)
|
|
568
802
|
|
|
569
803
|
if last_error_log:
|
|
570
804
|
tool_name = last_error_log.get("tool_name")
|
|
@@ -602,6 +836,7 @@ def _generate_runtime_bug_template(
|
|
|
602
836
|
startup_logs: list[dict[str, Any]],
|
|
603
837
|
*,
|
|
604
838
|
addon_logs: str = "",
|
|
839
|
+
submit_url: str = RUNTIME_BUG_URL,
|
|
605
840
|
) -> str:
|
|
606
841
|
"""
|
|
607
842
|
Generate a runtime bug report template matching runtime_bug.md format.
|
|
@@ -610,10 +845,19 @@ def _generate_runtime_bug_template(
|
|
|
610
845
|
copy-paste without format conflicts.
|
|
611
846
|
"""
|
|
612
847
|
platform_info = diagnostic_info.get("platform", {})
|
|
848
|
+
config_toggles = diagnostic_info.get("config_toggles") or {}
|
|
849
|
+
mcp_transport = diagnostic_info.get("mcp_transport", "unknown")
|
|
850
|
+
client_info = diagnostic_info.get("mcp_client_info") or {}
|
|
613
851
|
|
|
614
852
|
# Extract error messages from recent logs
|
|
615
853
|
error_messages = _extract_error_messages(recent_logs)
|
|
616
|
-
error_section =
|
|
854
|
+
error_section = (
|
|
855
|
+
"\n".join(error_messages)
|
|
856
|
+
if error_messages
|
|
857
|
+
else "<!-- No errors detected in recent logs -->"
|
|
858
|
+
)
|
|
859
|
+
|
|
860
|
+
config_toggles_section = _format_config_toggles_for_template(config_toggles)
|
|
617
861
|
|
|
618
862
|
# Show startup logs section only if they exist
|
|
619
863
|
startup_section = ""
|
|
@@ -657,7 +901,9 @@ def _generate_runtime_bug_template(
|
|
|
657
901
|
> All environment info and logs below were collected automatically.
|
|
658
902
|
|
|
659
903
|
**Submit this report at:**
|
|
660
|
-
{
|
|
904
|
+
{submit_url}
|
|
905
|
+
|
|
906
|
+
(The submission link above pre-fills the issue title — you don't need to retype it.)
|
|
661
907
|
|
|
662
908
|
---
|
|
663
909
|
|
|
@@ -680,17 +926,49 @@ def _generate_runtime_bug_template(
|
|
|
680
926
|
<!-- What actually happened? -->
|
|
681
927
|
|
|
682
928
|
|
|
929
|
+
---
|
|
930
|
+
|
|
931
|
+
## 💬 Triggering Prompt & Tool Call
|
|
932
|
+
|
|
933
|
+
<!-- The calling AI agent fills this in. Paste, verbatim, the user message that
|
|
934
|
+
triggered this bug AND the tool call(s) that produced it. Truncate only
|
|
935
|
+
after anonymizing tokens / personal names. This is the highest-leverage
|
|
936
|
+
field for triage. -->
|
|
937
|
+
|
|
938
|
+
**User prompt:** <fill in>
|
|
939
|
+
|
|
940
|
+
**Tool call(s):**
|
|
941
|
+
```
|
|
942
|
+
<fill in — name + arguments + (truncated) response, e.g.:
|
|
943
|
+
ha_call_service(domain="light", service="turn_on", entity_id="light.example")
|
|
944
|
+
→ ToolError: Service not found
|
|
945
|
+
>
|
|
946
|
+
```
|
|
947
|
+
|
|
683
948
|
---
|
|
684
949
|
|
|
685
950
|
## 🔧 Environment
|
|
686
951
|
|
|
687
|
-
- **ha-mcp Version:** {diagnostic_info.get(
|
|
688
|
-
- **Installation Method:** {diagnostic_info.get(
|
|
689
|
-
- **
|
|
690
|
-
- **
|
|
691
|
-
- **
|
|
692
|
-
- **
|
|
693
|
-
- **
|
|
952
|
+
- **ha-mcp Version:** {diagnostic_info.get("ha_mcp_version", "Unknown")}
|
|
953
|
+
- **Installation Method:** {diagnostic_info.get("installation_method", "Unknown")}
|
|
954
|
+
- **MCP Transport:** {mcp_transport} _(auto-detected — correct if wrong)_
|
|
955
|
+
- **MCP Client:** {_format_client_info_for_template(client_info)} _(auto-detected from the MCP `initialize` handshake)_
|
|
956
|
+
- **AI Model:**
|
|
957
|
+
- **Operating System:** {platform_info.get("os", "Unknown")} {platform_info.get("os_release", "")} ({platform_info.get("architecture", "Unknown")})
|
|
958
|
+
- **Python Version:** {platform_info.get("python_version", "Unknown")}
|
|
959
|
+
- **Home Assistant Version:** {diagnostic_info.get("home_assistant_version", "Unknown")}
|
|
960
|
+
- **Connection Status:** {diagnostic_info.get("connection_status", "Unknown")}
|
|
961
|
+
- **Entity Count:** {diagnostic_info.get("entity_count", 0)}
|
|
962
|
+
|
|
963
|
+
---
|
|
964
|
+
|
|
965
|
+
## ⚙️ ha-mcp Configuration
|
|
966
|
+
|
|
967
|
+
These flags shape which tools the agent sees, so the same report can mean
|
|
968
|
+
different things depending on toggle state. Auto-collected from the running
|
|
969
|
+
server:
|
|
970
|
+
|
|
971
|
+
{config_toggles_section}
|
|
694
972
|
|
|
695
973
|
---
|
|
696
974
|
|
|
@@ -734,13 +1012,23 @@ def _generate_agent_behavior_template(
|
|
|
734
1012
|
diagnostic_info: dict[str, Any],
|
|
735
1013
|
log_summary: str,
|
|
736
1014
|
recent_logs: list[dict[str, Any]],
|
|
1015
|
+
*,
|
|
1016
|
+
submit_url: str = AGENT_BEHAVIOR_URL,
|
|
737
1017
|
) -> str:
|
|
738
1018
|
"""
|
|
739
1019
|
Generate an agent behavior feedback template matching agent_behavior_feedback.md format.
|
|
740
1020
|
|
|
741
1021
|
This template focuses on AI agent tool usage patterns and inefficiencies.
|
|
742
1022
|
"""
|
|
743
|
-
|
|
1023
|
+
config_toggles = diagnostic_info.get("config_toggles") or {}
|
|
1024
|
+
mcp_transport = diagnostic_info.get("mcp_transport", "unknown")
|
|
1025
|
+
client_info = diagnostic_info.get("mcp_client_info") or {}
|
|
1026
|
+
config_toggles_section = _format_config_toggles_for_template(config_toggles)
|
|
1027
|
+
|
|
1028
|
+
# _extract_error_messages and recent_logs are unused in the agent template;
|
|
1029
|
+
# tool sequence already lives in log_summary. Kept in the signature so
|
|
1030
|
+
# callers don't have to remember which template needs which arg.
|
|
1031
|
+
del recent_logs
|
|
744
1032
|
|
|
745
1033
|
return f"""## 🤖 Auto-Generated by `ha_report_issue` Tool
|
|
746
1034
|
|
|
@@ -748,7 +1036,9 @@ def _generate_agent_behavior_template(
|
|
|
748
1036
|
> Tool call history was collected automatically to help analyze agent behavior.
|
|
749
1037
|
|
|
750
1038
|
**Submit this feedback at:**
|
|
751
|
-
{
|
|
1039
|
+
{submit_url}
|
|
1040
|
+
|
|
1041
|
+
(The submission link above pre-fills the issue title — you don't need to retype it.)
|
|
752
1042
|
|
|
753
1043
|
---
|
|
754
1044
|
|
|
@@ -774,6 +1064,21 @@ def _generate_agent_behavior_template(
|
|
|
774
1064
|
<!-- Example: "I asked the agent to create an automation that..." -->
|
|
775
1065
|
|
|
776
1066
|
|
|
1067
|
+
---
|
|
1068
|
+
|
|
1069
|
+
## 💬 Triggering Prompt & Tool Call
|
|
1070
|
+
|
|
1071
|
+
<!-- The AI agent fills this in. Paste, verbatim, the user message that
|
|
1072
|
+
prompted the questionable behavior AND the tool call(s) the agent made
|
|
1073
|
+
in response. Truncate only after anonymizing tokens / personal names. -->
|
|
1074
|
+
|
|
1075
|
+
**User prompt:** <fill in>
|
|
1076
|
+
|
|
1077
|
+
**Tool call(s) the agent chose:**
|
|
1078
|
+
```
|
|
1079
|
+
<fill in — name + arguments + (truncated) response>
|
|
1080
|
+
```
|
|
1081
|
+
|
|
777
1082
|
---
|
|
778
1083
|
|
|
779
1084
|
## 🔧 Tool Calls Made (Auto-Filled)
|
|
@@ -806,11 +1111,23 @@ def _generate_agent_behavior_template(
|
|
|
806
1111
|
|
|
807
1112
|
---
|
|
808
1113
|
|
|
809
|
-
## 📊 Environment
|
|
1114
|
+
## 📊 Environment
|
|
1115
|
+
|
|
1116
|
+
- **ha-mcp Version:** {diagnostic_info.get("ha_mcp_version", "Unknown")}
|
|
1117
|
+
- **Installation Method:** {diagnostic_info.get("installation_method", "Unknown")}
|
|
1118
|
+
- **MCP Transport:** {mcp_transport} _(auto-detected — correct if wrong)_
|
|
1119
|
+
- **MCP Client:** {_format_client_info_for_template(client_info)} _(auto-detected from the MCP `initialize` handshake)_
|
|
1120
|
+
- **AI Model:**
|
|
1121
|
+
- **Home Assistant Version:** {diagnostic_info.get("home_assistant_version", "Unknown")}
|
|
1122
|
+
|
|
1123
|
+
---
|
|
1124
|
+
|
|
1125
|
+
## ⚙️ ha-mcp Configuration
|
|
1126
|
+
|
|
1127
|
+
These flags shape which tools the agent sees, so the same behavior may be
|
|
1128
|
+
expected vs. surprising depending on toggle state:
|
|
810
1129
|
|
|
811
|
-
|
|
812
|
-
- **AI Client:** (Claude Desktop / Claude Code / Other)
|
|
813
|
-
- **Home Assistant Version:** {diagnostic_info.get('home_assistant_version', 'Unknown')}
|
|
1130
|
+
{config_toggles_section}
|
|
814
1131
|
|
|
815
1132
|
---
|
|
816
1133
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/resources/skills-vendor/AGENTS.md
RENAMED
|
File without changes
|
{ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/resources/skills-vendor/CLAUDE.md
RENAMED
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/resources/skills-vendor/LICENSE
RENAMED
|
File without changes
|
{ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/resources/skills-vendor/README.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/best_practice_checker.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_config_automations.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_config_dashboards.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_config_entry_flow.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_config_helpers.py
RENAMED
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_config_scripts.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_voice_assistant.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/transforms/categorized_search.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/utils/kill_signal_diagnostics.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp_dev.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp_dev.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|