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.
Files changed (109) hide show
  1. {ha_mcp_dev-7.4.1.dev464/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.4.1.dev465}/PKG-INFO +1 -1
  2. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/pyproject.toml +1 -1
  3. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_bug_report.py +372 -55
  4. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
  5. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/LICENSE +0 -0
  6. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/MANIFEST.in +0 -0
  7. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/README.md +0 -0
  8. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/setup.cfg +0 -0
  9. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/__init__.py +0 -0
  10. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/__main__.py +0 -0
  11. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/_pypi_marker +0 -0
  12. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/_version.py +0 -0
  13. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/auth/__init__.py +0 -0
  14. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/auth/consent_form.py +0 -0
  15. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/auth/provider.py +0 -0
  16. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/client/__init__.py +0 -0
  17. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/client/rest_client.py +0 -0
  18. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/client/websocket_client.py +0 -0
  19. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/client/websocket_listener.py +0 -0
  20. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/config.py +0 -0
  21. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/errors.py +0 -0
  22. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/py.typed +0 -0
  23. {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
  24. {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
  25. {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
  26. {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
  27. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
  28. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
  29. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
  30. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
  31. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
  32. {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
  33. {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
  34. {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
  35. {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
  36. {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
  37. {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
  38. {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
  39. {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
  40. {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
  41. {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
  42. {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
  43. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/server.py +0 -0
  44. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/settings_ui.py +0 -0
  45. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/smoke_test.py +0 -0
  46. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/__init__.py +0 -0
  47. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/backup.py +0 -0
  48. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/best_practice_checker.py +0 -0
  49. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/device_control.py +0 -0
  50. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/enhanced.py +0 -0
  51. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/helpers.py +0 -0
  52. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/reference_validator.py +0 -0
  53. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/registry.py +0 -0
  54. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/smart_search.py +0 -0
  55. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_addons.py +0 -0
  56. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_areas.py +0 -0
  57. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_blueprints.py +0 -0
  58. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_calendar.py +0 -0
  59. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_camera.py +0 -0
  60. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_categories.py +0 -0
  61. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_code.py +0 -0
  62. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_config_automations.py +0 -0
  63. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
  64. {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
  65. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
  66. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
  67. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
  68. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_energy.py +0 -0
  69. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_entities.py +0 -0
  70. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_filesystem.py +0 -0
  71. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_groups.py +0 -0
  72. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_hacs.py +0 -0
  73. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_history.py +0 -0
  74. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_integrations.py +0 -0
  75. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_labels.py +0 -0
  76. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_mcp_component.py +0 -0
  77. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_registry.py +0 -0
  78. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_resources.py +0 -0
  79. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_search.py +0 -0
  80. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_service.py +0 -0
  81. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_services.py +0 -0
  82. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_system.py +0 -0
  83. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_todo.py +0 -0
  84. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_traces.py +0 -0
  85. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_updates.py +0 -0
  86. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_utility.py +0 -0
  87. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
  88. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
  89. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/tools_zones.py +0 -0
  90. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/tools/util_helpers.py +0 -0
  91. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/transforms/__init__.py +0 -0
  92. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/transforms/categorized_search.py +0 -0
  93. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/utils/__init__.py +0 -0
  94. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/utils/config_hash.py +0 -0
  95. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/utils/data_paths.py +0 -0
  96. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/utils/domain_handlers.py +0 -0
  97. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/utils/fuzzy_search.py +0 -0
  98. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
  99. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/utils/operation_manager.py +0 -0
  100. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/utils/python_sandbox.py +0 -0
  101. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp/utils/usage_logger.py +0 -0
  102. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
  103. {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
  104. {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
  105. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
  106. {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
  107. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/tests/__init__.py +0 -0
  108. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/tests/test_constants.py +0 -0
  109. {ha_mcp_dev-7.4.1.dev464 → ha_mcp_dev-7.4.1.dev465}/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.4.1.dev464
3
+ Version: 7.4.1.dev465
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.4.1.dev464"
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 = "https://github.com/homeassistant-ai/ha-mcp/issues/new?template=runtime_bug.yml"
35
- AGENT_BEHAVIOR_URL = "https://github.com/homeassistant-ai/ha-mcp/issues/new?template=agent_behavior.yml"
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 platform
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"Platform: {platform_info['os']} {platform_info['os_release']} ({platform_info['architecture']})",
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
- f"=== Startup Logs ({len(startup_logs)} entries) ===",
356
- startup_log_summary,
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
- f"=== Recent Tool Calls ({len(recent_logs)} entries) ===",
363
- log_summary,
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
- "=== Add-on Container Logs ===",
370
- addon_logs,
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, log_summary, startup_log_summary, recent_logs, startup_logs,
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, log_summary, recent_logs
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
- " - If gh CLI is available: use `gh issue list --search \"keyword\"`\n"
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. **Present the anonymized report to the user**:\n"
439
- " a. Show the suggested_title (user can edit if needed)\n"
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:\n"
442
- f" - Runtime bugs: {RUNTIME_BUG_URL}\n"
443
- f" - Agent behavior: {AGENT_BEHAVIOR_URL}\n"
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((log for log in reversed(recent_logs) if log.get("error_message")), None)
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 = "\n".join(error_messages) if error_messages else "<!-- No errors detected in recent logs -->"
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
- {RUNTIME_BUG_URL}
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('ha_mcp_version', 'Unknown')}
688
- - **Installation Method:** {diagnostic_info.get('installation_method', 'Unknown')}
689
- - **Platform:** {platform_info.get('os', 'Unknown')} {platform_info.get('os_release', '')} ({platform_info.get('architecture', 'Unknown')})
690
- - **Python Version:** {platform_info.get('python_version', 'Unknown')}
691
- - **Home Assistant Version:** {diagnostic_info.get('home_assistant_version', 'Unknown')}
692
- - **Connection Status:** {diagnostic_info.get('connection_status', 'Unknown')}
693
- - **Entity Count:** {diagnostic_info.get('entity_count', 0)}
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
- platform_info = diagnostic_info.get("platform", {})
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
- {AGENT_BEHAVIOR_URL}
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 (Optional)
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
- - **ha-mcp Version:** {diagnostic_info.get('ha_mcp_version', 'Unknown')}
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
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ha-mcp-dev
3
- Version: 7.4.1.dev464
3
+ Version: 7.4.1.dev465
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