Repository navigation
refactor(cua): fold the MCP adapter onto a core-owned strict stdio client - #141432
Conversation
…ient Core now owns the strict stdio MCP client the CUA computer plugin used privately: process ownership, bounded fail-closed framing, exact-version handshake, request admission, fatal cancellation/timeouts without SDK cancellation notifications, request-local JSON-RPC errors, and confirmed-or-uncertain shutdown. The plugin keeps CUA policy only. Two SDK impedance hacks are removed rather than moved: JSON-RPC ids are no longer shifted (the real driver answers id 0) and JSON-RPC error frames map through the SDK's McpError, so that local error text now carries the code. Server-originated notifications and requests are routed by the SDK instead of being fatal. The unshipped plugin-sdk mcpStdioRuntime seam now exposes only createMcpStdioClient. Net −52 production lines; no SDK budget or baseline changes.
|
Codex review: blocked before merge. Reviewed September 7, 2026, 2:46 PM ET / 18:46 UTC. ClawSweeper reviewWhat this changesMoves the CUA computer plugin’s MCP connection management and frame validation into a shared core client while retaining plugin-specific launch settings and tool-result handling. Merge readiness⛔ Blocked before merge - 1 item remains This remains useful work that current main does not implement. No actionable introduced defect was found; the latest discussion resolves the prior compatibility concern, and the recorded real-driver proof supports the refactor. Priority: P2 Review scores
Verification
How this fits togetherThe CUA computer plugin sends computer-control requests through an MCP proxy to a driver daemon. The shared core client manages request correlation, validation, deadlines, and proxy cleanup before returning results to the plugin. flowchart LR
A[Computer tool requests] --> B[CUA plugin adapter]
B --> C[Shared strict MCP client]
C --> D[Owned proxy process]
D --> E[Driver daemon]
E --> D
D --> F[Validate and correlate replies]
F --> B
C --> G[Retire connection and clean up proxy]
Before merge
Agent review detailsSecurityNone. PR surfaceSource -54, Tests +239, Docs +5. Total +190 across 9 files. View PR surface stats
Review metrics
Merge-risk optionsMaintainer options:
Technical reviewBest possible solution: Keep one core-owned strict MCP lifecycle and the narrow lazy factory, with CUA-specific policy confined to the plugin. Do we have a high-confidence way to reproduce the issue? Not applicable: this is a refactor rather than a reported defect; existing boundary tests and recorded real-driver observations cover the preserved behavior. Is this the best way to solve the issue? Yes: consolidating framing and lifecycle handling under the existing core owner removes duplication while retaining CUA policy, and the API direction is explicitly accepted in the discussion. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning medium; reviewed against ce8fb5328d34. LabelsLabel changes:
Label justifications:
EvidenceWhat I checked:
Likely related people:
Rating scale
Overall follows the weaker of proof and patch quality. Workflow
History |
Initialize the strict client's callbacks as typed fields on its private transport subclass. This preserves the same failure and exit handling while removing the two new suppressions rejected by the production lint inventory. Keep the suppression allowlist and tests unchanged. Validation: suppression inventory and both MCP client suites passed (23 tests); focused core lint passed. Total production delta is now -54 lines.
|
Addressing the prior proof items and the remaining compatibility risk in the ClawSweeper review of f7be3456fd31:
The PR body records this evidence. The completed review of |
|
Merged via squash.
|
…ient (openclaw#141432) * refactor(cua): fold the MCP adapter onto a core-owned strict stdio client Core now owns the strict stdio MCP client the CUA computer plugin used privately: process ownership, bounded fail-closed framing, exact-version handshake, request admission, fatal cancellation/timeouts without SDK cancellation notifications, request-local JSON-RPC errors, and confirmed-or-uncertain shutdown. The plugin keeps CUA policy only. Two SDK impedance hacks are removed rather than moved: JSON-RPC ids are no longer shifted (the real driver answers id 0) and JSON-RPC error frames map through the SDK's McpError, so that local error text now carries the code. Server-originated notifications and requests are routed by the SDK instead of being fatal. The unshipped plugin-sdk mcpStdioRuntime seam now exposes only createMcpStdioClient. Net −52 production lines; no SDK budget or baseline changes. * fix(cua): bind MCP callbacks without lint suppressions Initialize the strict client's callbacks as typed fields on its private transport subclass. This preserves the same failure and exit handling while removing the two new suppressions rejected by the production lint inventory. Keep the suppression allowlist and tests unchanged. Validation: suppression inventory and both MCP client suites passed (23 tests); focused core lint passed. Total production delta is now -54 lines.
What Problem This Solves
#141027 moved the CUA computer plugin's MCP subprocess onto core's stdio transport and the MCP SDK's request correlation, but the plugin still re-implemented the strict client around them: a private transport subclass, a fake client for the shared startup deadline, the handshake, request admission, abort/timeout/write-failure fatal mapping, JSON-RPC error mapping, stderr tail, and shutdown ordering, plus a private decoder that carried two SDK impedance hacks (wire id = SDK id + 1, and JSON-RPC error frames re-wrapped inside a result envelope). The consolidation landed at +216 net production lines instead of the expected deletion.
Why This Change Was Made
Core now owns one strict stdio MCP client (
src/agents/mcp-stdio-client.ts+src/agents/mcp-stdio-frame-decoder.ts): process ownership through the existing transport and lifecycle helpers, bounded newline framing with fail-closed frame validation, exact-version handshake under the shared startup deadline, pending-request admission, client-wide fatal cancellation and timeouts without SDK cancellation notifications, non-timeout JSON-RPC errors kept local, and confirmed-or-uncertain shutdown. The caller supplies only its error taxonomy. The plugin adapter keeps CUA policy: environment scrub and launch arguments,COMPUTER_DRIVER_*error prefixes, tool-result normalization into CUA'sToolResult, action/enum mapping, session state, and availability.Two SDK impedance hacks are gone rather than moved. The real driver answers JSON-RPC id 0 normally (probed on the bundled cua-driver 0.22.0 and proven again on pinned 0.22.2 in Testbox), so ids are no longer shifted. JSON-RPC error frames now map through the SDK's
McpError, so the local error text carries the code (CUA MCP request failed: MCP error <code>: <message>); the driver reports tool failures asisErrorresults, so this path is not exercised in normal operation. Server-originated notifications and requests are now accepted and routed by the SDK instead of being treated as fatal invalid responses.The plugin-sdk
mcpStdioRuntimeseam (unshipped, landed in #141027 the same day) now exposes onlycreateMcpStdioClient; the transport and lifecycle helpers are core-internal again.User Impact
CUA action and wire behavior is preserved: same launch arguments and exact environment, same 256 MiB frame bound, 64-request admission, 10 s startup and 120 s request deadlines, same fatal cancellation, and the same daemon-ownership boundary (proxy disposal never stops a separately started daemon). Two error texts change as described above. No configuration, dependency, protocol-version, or persistent-store change.
Production delta: −54 net lines (+380 / −434) (plugin −312:
mcp-driver-client.ts682 → 462 lines,mcp-response-decoder.tsdeleted, core +268:mcp-stdio-client.ts57 → 240 lines, newmcp-stdio-frame-decoder.ts85 lines; SDK loader −10).Evidence
node scripts/run-vitest.mjs src/agents/mcp-stdio-client.test.ts extensions/cua-computer/src/mcp-driver-client.test.ts19 passed; focused core lint passed.node scripts/run-vitest.mjs extensions/cua-computer182 passed (15 files). Three approved fixture edits inmcp-driver-client.test.tsreflect the removed hacks: initialize id 0, the incompatible-version reply id 0, and a numeric JSON-RPC error code plus SDK-formatted error text. Every other assertion is unchanged.src/agents/mcp-stdio-frame-decoder.test.ts+src/agents/mcp-stdio-client.test.ts+ existing transport/stderr/lifecycle suitesnode scripts/run-vitest.mjs src/agents/mcp-*.test.ts: 250 passed, 1 Windows-only skip.pnpm plugin-sdk:surface:check: public 152 entrypoints / 4445 exports / 2627 callables (no budget edits).pnpm buildpassed (declaration graph 3,475,869 / 5,315,536 bytes, 152 public subpaths verified).node scripts/check-changed.mjspassed every phase (ratchets, format, typecheck core/tests/extensions, dead-export scans, core lint) except the extension-lint phase, which stops at the documented nested-checkout declaration guard in this worktree; targetedrun-oxlint.mjson the changed extension files passed, and hosted CI owns the full lint lane.f7be3456fd31ad62bab8379d4e7a019d200958ce: https://github.com/openclaw/openclaw/actions/runs/34151833032.dist/esm/shared/protocol.jswraps existing transport callbacks before startup, clears pending response handlers on close, and sends cancellation before rejecting a timeout;dist/esm/types.jsembeds the sameResultSchemainJSONRPCResultResponseSchema. The strict decoder validates before SDK correlation, and fatal timeout/cancellation behavior is covered by the client and CUA suites.blacksmith-testbox, leasetbx_01m1yh3pra91a9rt8dcaec28kz, now stopped): plugin suite 182 passed; pinned real cua-driver 0.22.2 live proof passed, both wrapper exits 0. The wrapper verified source76df29ba5e83d9250962eadae7ac831bd46bf245and tree2e1e262740a7b73450085065a253f91f7d240301for both runs. The owned daemon under Xvfb survived proxy disposal; repeatedget_session_stateand reconnection succeeded; process cleanup was confirmed and scratch removed. Shared lease backing run: https://github.com/openclaw/openclaw/actions/runs/34150655274. Crabbox's external portal sync timed out after both successful commands; their timing JSON and local command logs record success independently of the backing workflow status.