ha-mcp-dev 7.4.1.dev485__tar.gz → 7.4.1.dev487__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 (110) hide show
  1. {ha_mcp_dev-7.4.1.dev485/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.4.1.dev487}/PKG-INFO +1 -1
  2. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/pyproject.toml +1 -1
  3. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/tools/tools_integrations.py +110 -6
  4. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
  5. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/LICENSE +0 -0
  6. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/MANIFEST.in +0 -0
  7. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/README.md +0 -0
  8. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/setup.cfg +0 -0
  9. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/__init__.py +0 -0
  10. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/__main__.py +0 -0
  11. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/_pypi_marker +0 -0
  12. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/_version.py +0 -0
  13. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/auth/__init__.py +0 -0
  14. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/auth/consent_form.py +0 -0
  15. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/auth/provider.py +0 -0
  16. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/client/__init__.py +0 -0
  17. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/client/rest_client.py +0 -0
  18. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/client/supervisor_client.py +0 -0
  19. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/client/websocket_client.py +0 -0
  20. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/client/websocket_listener.py +0 -0
  21. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/config.py +0 -0
  22. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/errors.py +0 -0
  23. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/py.typed +0 -0
  24. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
  25. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
  26. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
  27. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
  28. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
  29. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
  30. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
  31. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
  32. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
  33. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
  34. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
  35. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
  36. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
  37. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
  38. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
  39. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
  40. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
  41. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
  42. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
  43. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
  44. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/server.py +0 -0
  45. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/settings_ui.py +0 -0
  46. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/smoke_test.py +0 -0
  47. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/tools/__init__.py +0 -0
  48. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/tools/backup.py +0 -0
  49. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/tools/best_practice_checker.py +0 -0
  50. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/tools/device_control.py +0 -0
  51. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/tools/enhanced.py +0 -0
  52. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/tools/helpers.py +0 -0
  53. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/tools/reference_validator.py +0 -0
  54. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/tools/registry.py +0 -0
  55. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/tools/smart_search.py +0 -0
  56. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/tools/tools_addons.py +0 -0
  57. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/tools/tools_areas.py +0 -0
  58. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/tools/tools_blueprints.py +0 -0
  59. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/tools/tools_bug_report.py +0 -0
  60. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/tools/tools_calendar.py +0 -0
  61. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/tools/tools_camera.py +0 -0
  62. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/tools/tools_categories.py +0 -0
  63. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/tools/tools_code.py +0 -0
  64. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/tools/tools_config_automations.py +0 -0
  65. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
  66. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/tools/tools_config_entry_flow.py +0 -0
  67. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
  68. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
  69. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
  70. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/tools/tools_energy.py +0 -0
  71. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/tools/tools_entities.py +0 -0
  72. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/tools/tools_filesystem.py +0 -0
  73. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/tools/tools_groups.py +0 -0
  74. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/tools/tools_hacs.py +0 -0
  75. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/tools/tools_history.py +0 -0
  76. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/tools/tools_labels.py +0 -0
  77. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/tools/tools_mcp_component.py +0 -0
  78. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/tools/tools_registry.py +0 -0
  79. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/tools/tools_resources.py +0 -0
  80. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/tools/tools_search.py +0 -0
  81. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/tools/tools_service.py +0 -0
  82. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/tools/tools_services.py +0 -0
  83. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/tools/tools_system.py +0 -0
  84. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/tools/tools_todo.py +0 -0
  85. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/tools/tools_traces.py +0 -0
  86. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/tools/tools_updates.py +0 -0
  87. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/tools/tools_utility.py +0 -0
  88. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
  89. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
  90. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/tools/tools_zones.py +0 -0
  91. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/tools/util_helpers.py +0 -0
  92. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/transforms/__init__.py +0 -0
  93. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/transforms/categorized_search.py +0 -0
  94. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/utils/__init__.py +0 -0
  95. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/utils/config_hash.py +0 -0
  96. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/utils/data_paths.py +0 -0
  97. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/utils/domain_handlers.py +0 -0
  98. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/utils/fuzzy_search.py +0 -0
  99. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
  100. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/utils/operation_manager.py +0 -0
  101. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/utils/python_sandbox.py +0 -0
  102. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp/utils/usage_logger.py +0 -0
  103. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
  104. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
  105. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
  106. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
  107. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
  108. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/tests/__init__.py +0 -0
  109. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/tests/test_constants.py +0 -0
  110. {ha_mcp_dev-7.4.1.dev485 → ha_mcp_dev-7.4.1.dev487}/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.dev485
3
+ Version: 7.4.1.dev487
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.dev485"
7
+ version = "7.4.1.dev487"
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"
@@ -295,6 +295,17 @@ class IntegrationTools:
295
295
  try:
296
296
  result = await self._client.get_config_entry(entry_id)
297
297
  entry_domain = result.get("domain") if isinstance(result, dict) else None
298
+
299
+ # Surface `options` on every per-entry response (HA's REST endpoint
300
+ # omits the field). For entries with supports_options=True we probe
301
+ # via OptionsFlow — see `_fetch_entry_options`. When include_schema
302
+ # is also requested, `_fetch_options_schema` below populates options
303
+ # from the same flow init so we don't pay for two round-trips.
304
+ if isinstance(result, dict):
305
+ result.setdefault("options", {})
306
+ if result.get("supports_options") and not include_schema:
307
+ result["options"] = await self._fetch_entry_options(entry_id)
308
+
298
309
  resp: dict[str, Any] = {
299
310
  "success": True,
300
311
  "entry_id": entry_id,
@@ -326,21 +337,95 @@ class IntegrationTools:
326
337
  ],
327
338
  )
328
339
 
340
+ @staticmethod
341
+ def _options_from_form_flow(flow: dict[str, Any]) -> dict[str, Any]:
342
+ """Extract ``{field_name: current_value}`` from a form-type OptionsFlow.
343
+
344
+ Reads each ``data_schema`` entry's ``default`` key, falling back to
345
+ ``value`` only when the ``default`` key is absent (constant-type
346
+ fields ship ``value`` instead of ``default``). Fields with a missing
347
+ or ``None`` value are skipped.
348
+ """
349
+ out: dict[str, Any] = {}
350
+ for field in flow.get("data_schema") or []:
351
+ name = field.get("name")
352
+ if name is None:
353
+ continue
354
+ value = field.get("default", field.get("value"))
355
+ if value is not None:
356
+ out[name] = value
357
+ return out
358
+
359
+ async def _fetch_entry_options(self, entry_id: str) -> dict[str, Any]:
360
+ """Read the current ``options`` for a config entry via its OptionsFlow.
361
+
362
+ Home Assistant does not expose ``ConfigEntry.options`` through any
363
+ read-only REST or WebSocket endpoint — ``/api/config/config_entries/entry``
364
+ deliberately omits the field. The closest approximation that the HA UI
365
+ itself uses is the ``default`` values populated into the OptionsFlow's
366
+ first-step ``data_schema``: integrations build that schema from the
367
+ existing options dict, so the defaults match the persisted state.
368
+
369
+ Starts the flow, harvests ``{name: default}`` from the first step,
370
+ and aborts the flow in ``finally`` so it doesn't sit half-open.
371
+
372
+ Returns ``{}`` on any failure (unsupported entry, non-form first step
373
+ such as a menu, init/abort errors) so callers can treat the return as
374
+ the canonical "options" field without further checks. Unexpected
375
+ exception types are logged at ``warning`` so probe breakage is
376
+ discoverable.
377
+ """
378
+ flow_id: str | None = None
379
+ try:
380
+ flow = await self._client.start_options_flow(entry_id)
381
+ flow_id = flow.get("flow_id")
382
+ flow_type = flow.get("type")
383
+ if flow_type != "form":
384
+ logger.debug(
385
+ f"OptionsFlow for {entry_id} returned type={flow_type!r}, "
386
+ f"not a form — cannot extract option defaults"
387
+ )
388
+ return {}
389
+ return self._options_from_form_flow(flow)
390
+ except Exception as exc:
391
+ logger.warning(
392
+ f"Failed to fetch options for {entry_id}: "
393
+ f"{type(exc).__name__}: {exc}"
394
+ )
395
+ return {}
396
+ finally:
397
+ if flow_id:
398
+ try:
399
+ await self._client.abort_options_flow(flow_id)
400
+ except Exception as abort_err:
401
+ logger.warning(
402
+ f"Failed to abort options flow {flow_id}: "
403
+ f"{type(abort_err).__name__}: {abort_err}"
404
+ )
405
+
329
406
  async def _fetch_options_schema(
330
407
  self, entry_id: str, resp: dict[str, Any]
331
408
  ) -> None:
332
- """Start an options flow to read the schema, then abort it."""
409
+ """Start an options flow to read the schema, then abort it.
410
+
411
+ Also populates ``resp["entry"]["options"]`` for form-type flows from
412
+ the same flow result so callers requesting both schema and options
413
+ don't pay for two round-trips.
414
+ """
333
415
  flow_id = None
334
416
  try:
335
417
  flow_result = await self._client.start_options_flow(entry_id)
336
418
  flow_id = flow_result.get("flow_id")
337
419
  flow_type = flow_result.get("type")
420
+ entry = resp.get("entry") if isinstance(resp.get("entry"), dict) else None
338
421
  if flow_type == "form":
339
422
  resp["options_schema"] = {
340
423
  "flow_type": "form",
341
424
  "step_id": flow_result.get("step_id"),
342
425
  "data_schema": flow_result.get("data_schema", []),
343
426
  }
427
+ if entry is not None:
428
+ entry["options"] = self._options_from_form_flow(flow_result)
344
429
  elif flow_type == "menu":
345
430
  resp["options_schema"] = {
346
431
  "flow_type": "menu",
@@ -348,16 +433,18 @@ class IntegrationTools:
348
433
  "menu_options": flow_result.get("menu_options", []),
349
434
  }
350
435
  except Exception as schema_err:
351
- logger.debug(
352
- f"Failed to fetch options schema for {entry_id}: {schema_err}"
436
+ logger.warning(
437
+ f"Failed to fetch options schema for {entry_id}: "
438
+ f"{type(schema_err).__name__}: {schema_err}"
353
439
  )
354
440
  finally:
355
441
  if flow_id:
356
442
  try:
357
443
  await self._client.abort_options_flow(flow_id)
358
444
  except Exception as abort_err:
359
- logger.debug(
360
- f"Failed to abort options flow {flow_id}: {abort_err}"
445
+ logger.warning(
446
+ f"Failed to abort options flow {flow_id}: "
447
+ f"{type(abort_err).__name__}: {abort_err}"
361
448
  )
362
449
 
363
450
  async def _list_entries(
@@ -394,11 +481,28 @@ class IntegrationTools:
394
481
  # Fetch current logger levels once; enrich each entry with its effective level.
395
482
  logger_levels = await get_logger_levels(self._client)
396
483
 
397
- # Format entries for response
484
+ # `_format_entry` is sync and cannot probe the OptionsFlow; options
485
+ # are filled in by a second async pass below for entries that
486
+ # advertise supports_options=True. See `_fetch_entry_options`.
398
487
  formatted_entries = [
399
488
  self._format_entry(entry, include_opts, logger_levels) for entry in entries
400
489
  ]
401
490
 
491
+ if include_opts:
492
+ options_targets = [
493
+ e for e in formatted_entries if e.get("supports_options")
494
+ ]
495
+ if options_targets:
496
+ fetched = await asyncio.gather(
497
+ *(
498
+ self._fetch_entry_options(e["entry_id"])
499
+ for e in options_targets
500
+ ),
501
+ return_exceptions=False,
502
+ )
503
+ for entry, opts in zip(options_targets, fetched, strict=True):
504
+ entry["options"] = opts
505
+
402
506
  # Apply search filter if query provided
403
507
  if query and query.strip():
404
508
  formatted_entries = self._filter_by_query(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ha-mcp-dev
3
- Version: 7.4.1.dev485
3
+ Version: 7.4.1.dev487
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