Skip to content

fix mcp never failing till timeout bug#661

Open
davidhadas wants to merge 1 commit into
kagenti:mainfrom
davidhadas:wrap_mcp_init
Open

fix mcp never failing till timeout bug#661
davidhadas wants to merge 1 commit into
kagenti:mainfrom
davidhadas:wrap_mcp_init

Conversation

@davidhadas

Copy link
Copy Markdown
Contributor

Force errors to propagate and do not wait for timeout to continue processing the task.

Fixes #660

Signed-off-by: David Hadas <david.hadas@gmail.com>

@pdettori pdettori left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Solid, well-documented fix for #660. Correctly diagnoses that mcpadapt swallows the connect exception in its daemon thread (so ready never sets → full MCP_TIMEOUT hang that also freezes the event loop), and the poll-the-thread fast-fail is the right seam. The output-bounding (tool_limits) and cooperative cancellation via a per-task threading.Event + CrewAI step_callback are thoughtful, and the guarded fallback to blocking behavior when library internals shift is a good defensive posture. CI is fully green.

All findings below are non-blocking (suggestions/nits) — approving.

Assisted-By: Claude Code


if task_id:
task_updater = TaskUpdater(event_queue, task_id, task.context_id)
await task_updater.cancel()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: await task_updater.cancel() here is unguarded, unlike the equivalent call in execute()'s cancellation handler (~line 230) which is wrapped best-effort in try/except.

On cancel, task_updater.cancel() can be invoked twice — once here, then again in execute()'s except (asyncio.CancelledError, TaskCancelled) path once the crew observes the flag and unwinds — and/or against an already torn-down event queue. The execute() path defends against that; cancel() does not, so an exception here would propagate back to the A2A framework. Consider wrapping this best-effort for symmetry.

return None


def _cap_result(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: No unit tests accompany the two new pure-logic modules — _cap_result/wrap_tool_output here and _connect_with_fast_fail in mcp_connect.py. Both have clean, deterministic contracts that are cheap to pin down: for _cap_result, the dict-with-items slice, the bare-list slice, the non-JSON passthrough, and the char-cap marker; for the connect helper, the three ready/dead/timeout states. test_startup.exp only exercises process startup, so these branches are currently uncovered. A small pytest over _cap_result would be high-value / low-cost.

if list_key is not None:
items = parsed[list_key]
total = _read_total(parsed, default=len(items))
if len(items) > max_items:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: The programmatic truncation note only fires when the server returns more than max_items. When the injected per_page=max_items successfully caps the fetch at exactly max_items while the true total_count is larger, len(items) > max_items is False, so no note is recorded — and "results were capped" awareness then rests entirely on the prompt guidance + the model honoring total_count. This looks intended (the prompt is the primary channel), just flagging to confirm the design intent.

Version note
------------
The fast-fail path pokes at ``mcpadapt`` internals (``MCPAdapt.ready`` /
``MCPAdapt.thread`` / ``MCPAdapt.tools()``), verified against ``crewai-tools``

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Good call documenting the reliance on private mcpadapt internals and guarding it with getattr + a blocking fallback. One heads-up: pyproject.toml pins crewai-tools[mcp]>=1.6.1 (unbounded upper; only uv.lock fixes the deployed version), and the fallback path silently reverts to the exact slow-timeout behavior this PR fixes — logging only a warning. So a future crewai-tools bump landing via a lock refresh could quietly disable the fast-fail. Might be worth a bounded upper pin or a startup canary/log that surfaces when the fast-fail path is inactive.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: New/ToDo

Development

Successfully merging this pull request may close these issues.

🐛 git issue always wait for timeout to drop an mcp call

3 participants