Skip to content

Commit fbed601

Browse files
authored
Merge branch 'master' into fix/loopback-mcp-client-timeout
2 parents 36383c9 + 9cb28fb commit fbed601

16 files changed

Lines changed: 2433 additions & 95 deletions

homeassistant-addon-dev/config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: "Home Assistant MCP Server (Dev)"
22
description: "Development channel - AI assistant integration via MCP (unstable)"
3-
version: "8.3.0.dev2415"
3+
version: "8.3.0.dev2419"
44
slug: "ha_mcp_dev"
55
url: "https://github.com/homeassistant-ai/ha-mcp"
66
stage: experimental

site/src/data/tools.json

Lines changed: 4 additions & 4 deletions
Large diffs are not rendered by default.

src/ha_mcp/client/websocket_client.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,13 @@
4646
# overflowed the previous 20MB cap (#1721).
4747
MAX_WS_MESSAGE_BYTES = 64 * 1024 * 1024
4848

49+
# How long :meth:`HomeAssistantWebSocketClient.send_command` waits for a reply
50+
# when the caller names no ``_wait_timeout``. Named rather than inlined because
51+
# callers that schedule retries have to budget around it: a caller whose retry
52+
# delay assumes a fast failure will start its next attempt one whole timeout
53+
# later than it planned when the command hangs instead.
54+
DEFAULT_COMMAND_WAIT_TIMEOUT = 30.0
55+
4956

5057
def _extract_ws_error(error: Any) -> tuple[str, str | None]:
5158
"""Split an HA WebSocket ``error`` payload into ``(message, code)``.
@@ -621,10 +628,11 @@ async def send_command(self, command_type: str, **kwargs: Any) -> dict[str, Any]
621628
Args:
622629
command_type: Type of command to send
623630
_wait_timeout: Seconds to wait for the response (consumed from
624-
``kwargs``, not forwarded to Home Assistant). Defaults to 30s,
625-
which suits fast commands; long-running ones (e.g. a
626-
``supervisor/api`` add-on install) must raise this so the
627-
client doesn't give up before Home Assistant replies.
631+
``kwargs``, not forwarded to Home Assistant). Defaults to
632+
``DEFAULT_COMMAND_WAIT_TIMEOUT``, which suits fast commands;
633+
long-running ones (e.g. a ``supervisor/api`` add-on install)
634+
must raise this so the client doesn't give up before Home
635+
Assistant replies.
628636
**kwargs: Command parameters (merged into the outgoing message)
629637
630638
Returns:
@@ -644,7 +652,7 @@ async def send_command(self, command_type: str, **kwargs: Any) -> dict[str, Any]
644652
# break that call shape under mypy. The leading underscore keeps it out
645653
# of the HA message namespace — HA WebSocket fields never start with
646654
# one — so it can never shadow a real command field when popped.
647-
wait_timeout: float = kwargs.pop("_wait_timeout", 30.0)
655+
wait_timeout: float = kwargs.pop("_wait_timeout", DEFAULT_COMMAND_WAIT_TIMEOUT)
648656

649657
message_id = self.get_next_message_id()
650658
message = {"id": message_id, "type": command_type, **kwargs}

src/ha_mcp/tools/config_entry_flow.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,8 +168,10 @@ async def set_config_subentry(
168168
169169
The reconfigure branch fails when the flow leaves any supplied config key
170170
unconsumed, where it previously returned success plus a warning — see
171-
:func:`_handle_config_subentry_flow_steps` for why. The create branch is
172-
unchanged.
171+
:func:`_handle_config_subentry_flow_steps` for why. It also walks with
172+
``keep_current_values`` (issue #2254), so a partial patch keeps the
173+
subentry fields it does not name instead of resetting them. The create
174+
branch is unchanged on both counts.
173175
"""
174176
_reject_redaction_sentinels(config_dict)
175177
flow_result = await client.start_config_subentry_flow(
@@ -205,6 +207,7 @@ async def set_config_subentry(
205207
flow_result,
206208
config_dict,
207209
is_reconfigure=subentry_id is not None,
210+
keep_current_values=subentry_id is not None,
208211
)
209212
except asyncio.CancelledError:
210213
await _abort_subentry_flow_best_effort(client, flow_id)
@@ -289,6 +292,17 @@ async def update_config_entry_options(
289292
``ha_set_integration`` path passes ``None`` to accept any domain). Starts
290293
an options flow, walks the flow steps, and returns the result. Aborts the
291294
flow on error. ``noun`` only affects response wording.
295+
296+
This edits an existing entry, so the walk runs with
297+
``keep_current_values``: every field an options step declares that
298+
``config_dict`` does not name is submitted with the value the step itself
299+
carries, exactly as the HA UI's "Configure" dialog posts back the boxes
300+
nobody touched. Before issue #2254 those keys were dropped and voluptuous
301+
substituted each field's static default, so a one-key patch silently reset
302+
the rest of the entry's options. A key the caller sets to ``None`` is the
303+
opposite request and is honoured as a clear, which for a field carrying a
304+
schema default means submitting the ``None`` for Home Assistant to
305+
validate rather than omitting it into that default.
292306
"""
293307
_reject_redaction_sentinels(config_dict)
294308
config_entry = await client.get_config_entry(entry_id)
@@ -332,6 +346,7 @@ async def update_config_entry_options(
332346
config_dict,
333347
submit_fn=client.submit_options_flow_step,
334348
helper_type=expected_domain,
349+
keep_current_values=True,
335350
)
336351
except Exception:
337352
try:

0 commit comments

Comments
 (0)