Skip to content

fix: MCP loader/agent — empty-list semantics, collision warning, kwargs forwarding, concurrent execution - #12

Open
wilsonhj wants to merge 1 commit into
americanexpress:mainfrom
wilsonhj:upstream/fix-mcp
Open

fix: MCP loader/agent — empty-list semantics, collision warning, kwargs forwarding, concurrent execution#12
wilsonhj wants to merge 1 commit into
americanexpress:mainfrom
wilsonhj:upstream/fix-mcp

Conversation

@wilsonhj

@wilsonhj wilsonhj commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Fixes

Four defects in the MCP tool layer, plus a performance improvement:

  • MCPToolLoader.load_tools([]) treated an explicit empty list as "no filter" (load everything) instead of "no tools".
  • Same-name tool collisions across servers silently dropped tools; now logs a warning naming the collision.
  • MCPToolAgent.ainvoke violated its own -> dict return type when no tool was called.
  • ainvoke dropped **kwargs instead of forwarding them to llm.ainvoke.
  • Independent tool calls now run concurrently via asyncio.gather (order-preserving; per-tool error isolation unchanged — one failing/slow tool cannot affect siblings).

Verification

  • tests/unit_tests/tools/test_mcp.py: 11 passed, including a rendezvous test (asyncio.Event-based, no wall-clock sleeps) that only passes under true concurrency — it fails on revert to the sequential loop.
  • Cross-checked: the MCP modules depend on nothing outside this diff (loader uses config.data.get(...) dict access; model() call signature unchanged).

Sequencing

Includes the shared dependency pin (needed to build standalone); that hunk rebases away once #10 merges. Otherwise independent of the other PRs in the series.

Co-authored-by: Claude noreply@anthropic.com

…ning, kwargs forwarding, concurrent tool execution

Also pin langchain family and langchain-mcp-adapters to bounded version
ranges so the MCP dependencies install reproducibly.

Co-authored-by: Claude <noreply@anthropic.com>
@wilsonhj

wilsonhj commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

Runtime verification — PASS ✅ (real stdio MCP server end-to-end)

Verified against a live MCP server over stdio transport (FastMCP, mcp 1.28.1: add, async slow_echo with 0.5s sleep, fail_tool that raises), real config.yml mcp.servers section, real MCPToolLoader + langchain-mcp-adapters 0.1.14. Only the LLM layer was a fake chat model emitting preset tool_calls; loader, transport, and tool execution were all live. No test-suite rerun.

  • load_tools() over stdio → discovered ['add','fail_tool','slow_echo']
  • load_tools([])0 tools (the fix — old code treated [] as falsy = load everything); control load_tools(['probe']) → 3 ✅
  • Concurrency, wall-clocked: sequential baseline 1.594s vs agent ainvoke 0.807s1.97× speedup (near-perfect 2×), order preserved ✅
  • Failure isolation: [add, fail_tool] batch → add result present, fail_tool mapped to an error entry, no exception propagated ✅
  • No-tool-call path: plain response still returns a dict ({'content': ..., 'tool_results': []}) — the return-type fix ✅
  • kwargs forwarding: ainvoke(..., stop=['END'], foo='bar') arrived at the model exactly — previously dropped ✅

One sub-claim not driven live: the cross-server same-name collision warning (requires two servers sharing a tool name; the logic is a simple dict-overwrite-with-warning). Everything else runtime-confirmed.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant