Commit c29ab1f
fix(component): give the loopback MCP session an actual generous timeout (#2276)
* fix(component): give the loopback MCP session an actual generous timeout
llm_api._mcp_session handed the SDK Home Assistant's shared httpx client
(helpers.httpx_client.get_async_client) so the SDK would never build its
own client mid-event-loop (blocking CA-bundle load). But the SDK applies
no timeout of its own when a caller-provided client is passed, and HA's
shared client is built with no explicit timeout=, so it silently carried
httpx's own hardcoded 5-second default — capping every tool call at 5s
of read-idle no matter how generous _CALL_TOOL_TIMEOUT_SECONDS (300s) /
_LIST_TOOLS_TIMEOUT_SECONDS (10s) looked.
_mcp_session now builds its own short-lived client per session instead,
with an explicit generous timeout and verify=False (the loopback URL is
always plain http://127.0.0.1 and never negotiates TLS, so skipping
verification is not a security relaxation — it just avoids the same
blocking SSL-context setup the shared-client approach was working around).
Found investigating a ~60s Assist-pipeline hang; ruled out as root cause
for that specific report, but a real, independent bug regardless.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(component): disable env proxy trust for the loopback MCP client
httpx.AsyncClient defaults to trust_env=True, so under HTTP_PROXY without
127.0.0.1 in NO_PROXY, the loopback tool-call request would leave the
listener entirely and go out through the configured proxy instead -
also leaking url's embedded secret_path (the private endpoint
credential) to that proxy, and breaking every Assist tool call in that
environment. Pass trust_env=False; this client should never consult a
proxy for a 127.0.0.1 request.
Review finding (chatgpt-codex-connector) on #2276.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(component): keep the pre-rename SDK fallback off env proxies too
The trust_env=False fix only covered the canonical streamable_http_client
path. The deprecated streamablehttp_client fallback (older fastmcp/mcp
pinned via a pip-spec override) takes no http_client param, but it DOES
accept an httpx_client_factory override for the client it builds
internally - so it was still trusting HTTP_PROXY/NO_PROXY by default.
Pass _loopback_httpx_client_factory there too, which builds the same
verify=False/trust_env=False client as the canonical path.
Review finding (chatgpt-codex-connector) on #2276.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(component): mirror the SDK's None-timeout default in the fallback factory
_loopback_httpx_client_factory forwarded timeout=None straight to
httpx.AsyncClient, which disables every timeout rather than applying a
sane default. create_mcp_http_client (the factory this substitutes for)
treats None as "no timeout was supplied" and substitutes its own
Timeout(30, read=300) - mirror that so a future zero-argument call to
this factory doesn't get an unbounded loopback client.
Also pin verify=False on both httpx.AsyncClient constructions via a
constructor-kwargs spy (verify has no public accessor on a built client,
so introspecting the result after construction can't cover it the way
timeout/trust_env can).
Review finding (Patch76) on #2276.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(component): stop the fallback factory's own fix from breaking it
The previous commit mirrored create_mcp_http_client's None-timeout
handling by importing MCP_DEFAULT_TIMEOUT/MCP_DEFAULT_SSE_READ_TIMEOUT
from mcp.shared._httpx_utils - but those constants don't exist before
mcp 1.24, and _loopback_httpx_client_factory only ever runs on SDKs old
enough to lack streamable_http_client (the canonical name tried first),
which is exactly that pre-1.24 range. The unconditional import raised
ImportError on every real install of the fallback it was meant to
harden - verified against the actual SDK source at v1.23.3 and v1.24.0.
Inline the substitute value instead: httpx.Timeout(30.0), matching what
create_mcp_http_client's own None default actually is on the SDKs this
fallback serves (the read=300 shape is itself a 1.24+ addition, so
guessing it here would have been wrong anyway).
Added a regression test that fakes mcp.shared._httpx_utils down to the
pre-1.24 shape (create_mcp_http_client only) - confirmed it reproduces
the exact ImportError against the previous commit before verifying the
fix clears it.
Review finding (Patch76) on #2276.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* docs(tests): add docstrings to functions touched by this PR's diff
CodeRabbit's docstring-coverage pre-merge check flagged 38.89% on the
18 functions touched across this PR's two files (threshold 80%). All
of the shortfall was in test_llm_api.py, in touched test methods and
their nested fake-transport/fake-session helpers.
Verified precisely by diffing the two files against the PR's merge
base and checking every touched function for a docstring (19 touched,
matching CodeRabbit's count within rounding) - all 13 missing ones now
have one, without touching the existing rationale comments they sit
alongside.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* docs(tests): narrow the "exact same calls" claim in the llm-api e2e docstring
test_llm_api_client_path_full_catalog drives the webhook relay URL and
lets the SDK build its own default httpx client, so it does not exercise
the dedicated loopback client _mcp_session now hands the SDK. Say so
instead of claiming the calls are exactly the same (optional review
finding on this PR).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VrVRQZN3S9CsYhiqPnbKq1
---------
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: kingpanther13 <kingpanther13@users.noreply.github.com>1 parent f2fc5f9 commit c29ab1f
4 files changed
Lines changed: 257 additions & 59 deletions
File tree
- custom_components/ha_mcp_tools
- tests/src
- e2e/workflows/embedded
- unit
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
50 | 50 | | |
51 | 51 | | |
52 | 52 | | |
53 | | - | |
| 53 | + | |
54 | 54 | | |
55 | 55 | | |
56 | 56 | | |
| |||
59 | 59 | | |
60 | 60 | | |
61 | 61 | | |
62 | | - | |
63 | 62 | | |
64 | 63 | | |
65 | 64 | | |
| |||
72 | 71 | | |
73 | 72 | | |
74 | 73 | | |
| 74 | + | |
75 | 75 | | |
76 | 76 | | |
77 | 77 | | |
| |||
243 | 243 | | |
244 | 244 | | |
245 | 245 | | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
246 | 291 | | |
247 | 292 | | |
248 | 293 | | |
249 | | - | |
250 | 294 | | |
251 | 295 | | |
252 | 296 | | |
253 | 297 | | |
254 | 298 | | |
255 | 299 | | |
256 | | - | |
257 | | - | |
258 | | - | |
259 | | - | |
260 | | - | |
261 | | - | |
262 | | - | |
263 | | - | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
264 | 333 | | |
265 | 334 | | |
266 | 335 | | |
267 | | - | |
268 | | - | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
| 340 | + | |
| 341 | + | |
| 342 | + | |
| 343 | + | |
| 344 | + | |
| 345 | + | |
| 346 | + | |
| 347 | + | |
| 348 | + | |
| 349 | + | |
| 350 | + | |
| 351 | + | |
| 352 | + | |
| 353 | + | |
| 354 | + | |
| 355 | + | |
| 356 | + | |
| 357 | + | |
| 358 | + | |
| 359 | + | |
| 360 | + | |
| 361 | + | |
269 | 362 | | |
270 | | - | |
271 | | - | |
272 | | - | |
273 | | - | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
274 | 366 | | |
275 | | - | |
276 | | - | |
277 | | - | |
278 | | - | |
279 | | - | |
280 | | - | |
281 | | - | |
282 | | - | |
283 | | - | |
284 | | - | |
285 | | - | |
286 | | - | |
287 | | - | |
288 | | - | |
289 | | - | |
290 | 367 | | |
291 | 368 | | |
292 | 369 | | |
| |||
370 | 447 | | |
371 | 448 | | |
372 | 449 | | |
373 | | - | |
| 450 | + | |
374 | 451 | | |
375 | 452 | | |
376 | 453 | | |
| |||
516 | 593 | | |
517 | 594 | | |
518 | 595 | | |
519 | | - | |
| 596 | + | |
520 | 597 | | |
521 | 598 | | |
522 | 599 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
476 | 476 | | |
477 | 477 | | |
478 | 478 | | |
479 | | - | |
| 479 | + | |
480 | 480 | | |
481 | 481 | | |
482 | 482 | | |
483 | 483 | | |
| 484 | + | |
| 485 | + | |
| 486 | + | |
| 487 | + | |
484 | 488 | | |
485 | 489 | | |
486 | 490 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
631 | 631 | | |
632 | 632 | | |
633 | 633 | | |
634 | | - | |
635 | | - | |
636 | | - | |
637 | | - | |
638 | | - | |
639 | | - | |
640 | | - | |
641 | 634 | | |
642 | 635 | | |
643 | 636 | | |
| |||
0 commit comments