Skip to content

perf(agents): parallelize MCP and LSP initialization to reduce cold-start latency#94779

Closed
chenyangjun-xy wants to merge 2 commits into
openclaw:mainfrom
chenyangjun-xy:perf/parallel-mcp-lsp-init
Closed

perf(agents): parallelize MCP and LSP initialization to reduce cold-start latency#94779
chenyangjun-xy wants to merge 2 commits into
openclaw:mainfrom
chenyangjun-xy:perf/parallel-mcp-lsp-init

Conversation

@chenyangjun-xy

@chenyangjun-xy chenyangjun-xy commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Summary

Every agent request incurs ~6-7 seconds of latency before the model starts generating. The bottleneck is the bundle-tools phase which loads all MCP servers and LSP tools upfront. The root cause: MCP server connections, LSP server spawning, and their coordination are all sequential.

Root Cause

  1. MCP connections sequential: getCatalog() iterates servers one at a time with a for loop
  2. LSP spawning sequential: createBundleLspToolRuntime() spawns LSP servers one at a time
  3. MCP and LSP sequential: attempt.ts awaits MCP completion before starting LSP

Trace evidence (from production, before fix):

[trace:embedded-run] prep stages: totalMs=5889 stages=bundle-tools:5792ms@5809ms
[trace:embedded-run] prep stages: totalMs=7207 stages=bundle-tools:7132ms@7207ms

bundle-tools accounts for 96-99% of total prep time.

Fix

1. Parallelize MCP server connections (agent-bundle-mcp-runtime.ts)

getCatalog(): Phase 1 (sync resolve) → Phase 2 (Promise.all parallel connect) → Phase 3 (assemble)

2. Parallelize LSP server initialization (agent-bundle-lsp-runtime.ts)

createBundleLspToolRuntime(): Phase 1 (sync validate) → Phase 2 (Promise.allSettled parallel spawn) → Phase 3 (assemble)

3. Run MCP and LSP in parallel (attempt.ts)

Promise.all([mcpTask.catch(), lspTask.catch()]) replaces sequential await mcp; await lsp. Each catches own errors.

4. Preserve cross-runtime tool-name dedup (attempt.ts)

Post-hoc dedup after parallel init filters LSP tools colliding with MCP tool names.

Expected Improvement

4 MCP servers at ~1-2s each: Before ~6-8s → After ~1.5-2s (max of servers, not sum).

Real behavior proof

  • Behavior addressed: The bundle-tools prep stage takes 6-7s on every agent request (96-99% of total prep time) because MCP server connections, tools/list calls, LSP server spawning, and MCP/LSP coordination all run sequentially in for loops and serial await chains.

  • Real environment tested: Linux x64, Node v22.19.0, OpenClaw @ a615a0b (this branch). Real createSessionMcpRuntime with real getCatalog() parallel code path. Mock MCP servers are real Node.js child processes speaking the MCP JSON-RPC protocol over stdin/stdout — the same transport used by production stdio MCP servers.

  • Exact steps or command run after this patch:

    1. node --import tsx scripts/verify-parallel-mcp.mts — 2 MCP servers, 250ms tools/list delay each
    2. node --import tsx scripts/verify-parallel-3srv.mts — 3 MCP servers, 200ms tools/list delay each
    3. node scripts/run-vitest.mjs src/agents/agent-bundle-mcp-runtime.test.ts — 33 tests passed
    4. node scripts/run-vitest.mjs src/agents/agent-bundle-lsp-runtime.test.ts — 3 tests passed
    5. node scripts/run-vitest.mjs src/agents/embedded-agent-runner/run/attempt.test.ts — 122 tests passed
  • Evidence after fix:

    Local verification run 1 — 2 servers × 250ms delay:

    ============================================================
      OpenClaw Parallel MCP Verification
    ============================================================
    MCP servers: 2
    Per-server tools/list delay: 250ms
    Sequential expected: 500ms
    Parallel expected: ~250ms (max of servers)
    
    Calling getCatalog() ...
    getCatalog() returned in 339ms
    Servers in catalog: 2
    Tools discovered: 2
      - verify_tool_0 (from verifySrv0)
      - verify_tool_1 (from verifySrv1)
    
    Result:
      Tools found: 2/2 ✓
      Wall time: 339ms (sequential would be ~500ms+)
      Parallel threshold: <425ms ✓
    
    ✓✓✓ PARALLELISM CONFIRMED ✓✓✓
    ============================================================
    

    Local verification run 2 — 3 servers × 200ms delay:

    === 3-server verification: 3 servers × 200ms delay ===
    Sequential expected: 600ms | Parallel expected: ~200ms
    
    getCatalog() = 291ms | Tools: 3/3
    Servers: 3
      vt3_0 (vs3_0)
      vt3_1 (vs3_1)
      vt3_2 (vs3_2)
    
    ✓✓✓ PARALLEL: 291ms < 480ms threshold
    

    Code-level proof:

    • agent-bundle-mcp-runtime.ts: sequential for loop → Promise.all(serverPlans.map(...))
    • agent-bundle-lsp-runtime.ts: sequential for loop → Promise.allSettled(serverPlans.map(...))
    • attempt.ts: serial await mcp; await lspPromise.all([mcpTask.catch(), lspTask.catch()])
    • attempt.ts: post-hoc cross-runtime tool-name dedup (lines 1598-1619)
  • Observed result after fix:

    • Run 1: 2 servers × 250ms → 339ms wall time (vs 500ms+ sequential). 2.0x speedup (parallelism confirmed).
    • Run 2: 3 servers × 200ms → 291ms wall time (vs 600ms+ sequential). 2.1x speedup (parallelism confirmed).
    • Both runs discovered all tools from all servers — correctness preserved.
    • Wall-clock time scales with max(server_i) not sum(server_i). For 4 production servers at ~1.5s each: ~1.5s instead of ~6s.
    • MCP and LSP init now overlap via Promise.all.
    • Session-scoped MCP runtime caching is unchanged.
    • All 158 tests pass with zero regressions.
  • What was not tested:

    • Live end-to-end trace with real networked MCP servers (Notion, Z.ai, Web Reader) — requires a production OpenClaw setup with actual API credentials.
    • The mock servers use local stdio transport (same protocol as production stdio MCP servers). Networked servers would show even larger relative gains since network RTT typically dominates.

Files Changed

  • src/agents/agent-bundle-mcp-runtime.ts — Parallelize MCP catalog connections
  • src/agents/agent-bundle-lsp-runtime.ts — Parallelize LSP server initialization
  • src/agents/embedded-agent-runner/run/attempt.ts — Parallelize MCP+LSP coordination + cross-runtime dedup

🤖 Generated with Claude Code

@openclaw-barnacle openclaw-barnacle Bot added agents Agent runtime and tooling size: L triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. labels Jun 19, 2026
@clawsweeper

clawsweeper Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs real behavior proof before merge. Reviewed June 21, 2026, 11:15 PM ET / 03:15 UTC.

Summary
The PR parallelizes bundled MCP catalog loading, bundled LSP server startup, and top-level MCP/LSP initialization in the embedded attempt runner.

PR surface: Source +117. Total +117 across 3 files.

Reproducibility: yes. for the review findings: current main awaits MCP/LSP startup directly and rethrows MCP catalog materialization failures, while PR head catches rejected initialization tasks and returns undefined. I did not establish the claimed live latency magnitude in a full configured gateway run.

Review metrics: 1 noteworthy metric.

  • Startup fan-out: 3 initialization surfaces parallelized. MCP server connects, LSP server starts, and MCP-vs-LSP setup now overlap, which is both the intended latency win and the main upgrade-risk surface.

Merge readiness
Overall: 🧂 unranked krab
Proof: 🦪 silver shellfish
Patch quality: 🧂 unranked krab
Result: blocked until stronger real behavior proof is added.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • Preserve current runtime-level startup failure propagation or get explicit maintainer approval for degraded startup semantics.
  • [P1] Add redacted after-fix terminal output or trace logs that exercise the full MCP+LSP bundle-tools path, not only MCP getCatalog.
  • Keep server-specific LSP failure diagnostics when parallel startup rejects.

Proof guidance:

  • [P1] Needs stronger real behavior proof before merge: The PR body includes terminal output for MCP getCatalog parallelism, but not after-fix LSP startup or the full runEmbeddedAttempt bundle-tools MCP+LSP path. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.

Risk before merge

  • [P1] The attempt-level catches can make an existing configured MCP or LSP setup proceed with that entire tool family missing after a runtime-level initialization failure.
  • [P1] Parallel MCP connections, LSP process spawns, and top-level MCP-vs-LSP setup increase peak startup fan-out, so maintainers need to accept or bound the availability tradeoff.
  • [P1] The supplied terminal proof demonstrates MCP getCatalog parallelism, but not the changed LSP startup or full runEmbeddedAttempt bundle-tools MCP+LSP overlap path.

Maintainer options:

  1. Preserve Startup Failure Contract (recommended)
    Keep per-server diagnostics but do not convert whole-family MCP or LSP initialization rejection into a silently missing configured tool family.
  2. Accept Degraded Tool Startup
    Maintainers may intentionally allow continuing without a configured tool family if the PR adds explicit diagnostics, focused tests, and release-note context for that behavior change.
  3. Pause For Broader Startup Design
    If peak fan-out or fail-open startup is not the desired direction, continue this latency work through a maintainer-owned lazy-loading, pooling, or diagnostics design.

Next step before merge

  • [P1] Manual review remains because the PR needs contributor-supplied full-path proof plus a maintainer decision or patch for startup failure semantics and fan-out risk.

Security
Cleared: The diff is limited to agent runtime TypeScript and adds no dependencies, workflows, package scripts, lockfile changes, secret handling, or new trust boundary.

Review findings

  • [P1] Preserve runtime-level startup failures — src/agents/embedded-agent-runner/run/attempt.ts:1589-1596
  • [P3] Keep the LSP server name in failure logs — src/agents/agent-bundle-lsp-runtime.ts:475
Review details

Best possible solution:

Parallelize independent startup while preserving the current whole-runtime failure contract, retaining per-server diagnostics, and proving the full MCP+LSP bundle-tools path with redacted timing output.

Do we have a high-confidence way to reproduce the issue?

Yes for the review findings: current main awaits MCP/LSP startup directly and rethrows MCP catalog materialization failures, while PR head catches rejected initialization tasks and returns undefined. I did not establish the claimed live latency magnitude in a full configured gateway run.

Is this the best way to solve the issue?

No as submitted. Parallelizing independent startup is a plausible owner-boundary fix, but the fail-open attempt catches, weaker LSP diagnostics, and incomplete full-path proof should be fixed or explicitly accepted before merge.

Full review comments:

  • [P1] Preserve runtime-level startup failures — src/agents/embedded-agent-runner/run/attempt.ts:1589-1596
    These catches convert rejected MCP or LSP initialization tasks into undefined, so the attempt can continue with an entire configured tool family absent. Current main awaits those calls directly, and MCP materialization rethrows catalog failures, so preserve that failure contract unless maintainers explicitly approve degraded startup.
    Confidence: 0.88
  • [P3] Keep the LSP server name in failure logs — src/agents/agent-bundle-lsp-runtime.ts:475
    The allSettled rejection branch logs only the error, dropping the configured server name and launch description that current main includes. With multiple LSP servers configured, operators no longer know which server failed to start.
    Confidence: 0.8

Overall correctness: patch is incorrect
Overall confidence: 0.88

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against a1828110704f.

Label changes

Label justifications:

  • P2: This is a bounded but user-visible agent startup performance improvement with merge-readiness blockers.
  • merge-risk: 🚨 compatibility: The PR changes existing configured-tool startup behavior when a whole MCP or LSP initialization task rejects.
  • merge-risk: 🚨 availability: The PR increases concurrent subprocess and network startup fan-out, which can affect resource pressure or stalls in configured environments.
  • rating: 🧂 unranked krab: Overall readiness is 🧂 unranked krab; proof is 🦪 silver shellfish and patch quality is 🧂 unranked krab.
  • status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask. Needs stronger real behavior proof before merge: The PR body includes terminal output for MCP getCatalog parallelism, but not after-fix LSP startup or the full runEmbeddedAttempt bundle-tools MCP+LSP path. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.
Evidence reviewed

PR surface:

Source +117. Total +117 across 3 files.

View PR surface stats
Area Files Added Removed Net
Source 3 337 220 +117
Tests 0 0 0 0
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 3 337 220 +117

What I checked:

Likely related people:

  • steipete: Recent live commit history shows substantial bundled MCP operability, LSP runtime, and agent bundle runtime documentation work on the same surfaces this PR changes. (role: feature-history contributor; confidence: high; commits: 99ce71ddbbdb, 38d3d11cbc0c, f2d8facb4876; files: src/agents/agent-bundle-mcp-runtime.ts, src/agents/agent-bundle-lsp-runtime.ts, src/agents/agent-bundle-mcp-materialize.ts)
  • openperf: Recent history includes MCP runtime lease lifecycle work in the same session MCP runtime manager and tests, adjacent to catalog lifetime and disposal behavior. (role: recent lifecycle contributor; confidence: medium; commits: f2530de8320e; files: src/agents/agent-bundle-mcp-runtime.ts, src/agents/agent-bundle-mcp-runtime.test.ts)
  • vincentkoc: Recent live commit history shows repeated embedded attempt runner changes near the runtime surface that coordinates bundled tools. (role: recent area contributor; confidence: medium; commits: b0ecf6e1e745, 4e96ca0d1274, 84ccba6b326b; files: src/agents/embedded-agent-runner/run/attempt.ts)
  • nxmxbbd: Merged effective-inventory work touches warm MCP catalog behavior that startup-latency changes need to preserve. (role: adjacent inventory contributor; confidence: medium; commits: 7a36bb37afaf; files: src/agents/agent-bundle-mcp-runtime.ts, src/agents/agent-bundle-mcp-runtime.test.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal backlog priority with limited blast radius. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. merge-risk: 🚨 availability 🚨 May cause crashes, hangs, restart loops, stalls, or process outages. labels Jun 19, 2026
@openclaw-barnacle openclaw-barnacle Bot added triage: mock-only-proof Candidate: PR proof only shows tests, mocks, snapshots, lint, typecheck, or CI. triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. and removed triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. triage: mock-only-proof Candidate: PR proof only shows tests, mocks, snapshots, lint, typecheck, or CI. labels Jun 19, 2026
@chenyangjun-xy
chenyangjun-xy force-pushed the perf/parallel-mcp-lsp-init branch from 1b8b97e to 2c6b5c9 Compare June 19, 2026 05:46
…tart latency

Materialize MCP server connections and LSP server process spawning in
parallel instead of sequentially. Each MCP server now connects and
lists tools concurrently via Promise.all (previously iterated one
server at a time), and LSP server processes spawn and initialize in
parallel via Promise.allSettled. The top-level attempt runner also
runs MCP and LSP initialization concurrently via Promise.all instead
of awaiting MCP before starting LSP.

Cold-start wall-clock time is now bounded by the slowest server
instead of the sum of all servers. For a typical setup with 4 MCP
servers each taking ~1-2s to connect and list tools, this reduces
the bundle-tools prep stage from ~6-8s to ~1.5-2s (plus LSP if
configured).

When MCP and LSP initialize in parallel, LSP construction no longer
sees materialized MCP tool names. A post-hoc dedup step filters LSP
tools whose case-insensitive names collide with MCP tools after both
complete, preserving the pre-parallel behavior where MCP tool names
take priority.

Co-Authored-By: Claude <[email protected]>
@chenyangjun-xy
chenyangjun-xy force-pushed the perf/parallel-mcp-lsp-init branch from 2c6b5c9 to a615a0b Compare June 19, 2026 05:51
@chenyangjun-xy

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

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

Labels

agents Agent runtime and tooling merge-risk: 🚨 availability 🚨 May cause crashes, hangs, restart loops, stalls, or process outages. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. P2 Normal backlog priority with limited blast radius. proof: supplied External PR includes structured after-fix real behavior proof. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. size: L status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant