Skip to content

fix(ai/cursor): preserve large MCP tool-call args during streaming#2625

Merged
can1357 merged 3 commits into
mainfrom
farm/3fbf9ee4/cursor-mcp-streaming-merge-args
Jun 15, 2026
Merged

fix(ai/cursor): preserve large MCP tool-call args during streaming#2625
can1357 merged 3 commits into
mainfrom
farm/3fbf9ee4/cursor-mcp-streaming-merge-args

Conversation

@roboomp

@roboomp roboomp commented Jun 15, 2026

Copy link
Copy Markdown
Collaborator

Repro

The Cursor (cursor-agent) provider drops large MCP tool-call arguments — most visibly the built-in task tool's tasks array on multi-subagent dispatches, which fails downstream schema validation with tasks: Invalid input: expected array, received undefined.

Reproduced by driving processInteractionUpdate (in packages/ai/src/providers/cursor.ts) with the same sequence the cursor-agent stream emits for a task dispatch: a series of cumulative args_text_delta snapshots followed by a tool_call_completed frame whose McpArgs map omits the oversized tasks key. Against the pre-fix implementation the new packages/ai/test/cursor-streaming-args.test.ts fails three assertions exactly matching the report — garbled partialJson, doubled-up duplicate snapshots, and tasks dropped from the final arguments.

Cause

Two bugs in processInteractionUpdate:

  1. args_text_delta carries the cumulative args text so far per agent.proto's PartialToolCallUpdate.args_text_delta ("Aggregated args text so far"), but the handler concatenated each snapshot onto the buffer (partialJson = current + snapshot). For a 3-step stream the buffer grew as prefix1 + prefix1prefix2 + … + full, producing garbled JSON the streaming parser could only partially repair.
  2. tool_call_completed carries an McpArgs map that omits oversized parameters entirely and downgrades unparsable values to their raw string fallback (via decodeMcpArgValue's parseToolArgsJson fallback). The handler unconditionally overwrote state.currentToolCall.arguments with that map, so large keys that survived the stream (e.g. task.tasks) disappeared from the final tool call.

Fix

  • Strip the already-buffered prefix from each args_text_delta snapshot (snapshot.startsWith(current) ? snapshot.slice(current.length) : snapshot), so cumulative snapshots are treated as snapshots while genuine incremental fragments still append. Empty chunks short-circuit before emitting a no-op toolcall_delta.
  • Add mergeCursorMcpToolCallArgs(streamed, completion) and call it on toolCallCompleted instead of overwriting. The merge: keeps streamed keys the completion frame omits, keeps the streamed structured value when the completion frame downgrades to a string, and otherwise lets the completion frame win.
  • Export processInteractionUpdate, BlockState, ToolCallState, UsageState, and mergeCursorMcpToolCallArgs so the regression test can drive the streaming state machine directly.

Verification

cd packages/ai && bun test test/cursor-streaming-args.test.ts test/cursor-exec-handlers.test.ts — 20 pass, 0 fail. Full package suite: cd packages/ai && bun test — 1718 pass, 337 skip (env-gated E2E), 0 fail. bun check from packages/ai is clean (formatter + types).

Fixes #2615

roboomp added 3 commits June 15, 2026 06:02
Cursor's `cursor-agent` API streams MCP tool-call arguments via cumulative
`args_text_delta` snapshots ("aggregated args text so far" per agent.proto)
and a final `tool_call_completed` frame that carries an `McpArgs` map. Two
bugs in `processInteractionUpdate` corrupted large arguments — most visibly
the built-in `task` tool's `tasks` array on multi-subagent dispatches, which
failed downstream schema validation with
`tasks: Invalid input: expected array, received undefined`:

- Each cumulative snapshot was concatenated onto the buffer, so partialJson
  grew as `prefix1 + prefix1prefix2 + ... + full` — garbled JSON the
  streaming parser could only partially repair.
- The completion frame's `McpArgs` map unconditionally overwrote the
  streamed args, but that map omits oversized parameters entirely and
  downgrades unparsable values to their raw string fallback, so large keys
  present in the stream were dropped or downgraded.

Strip the already-buffered prefix from each `args_text_delta` snapshot
(falling back to append when the snapshot doesn't extend the buffer) and
merge the decoded `McpArgs` map into the streamed args instead of
overwriting — preserving streamed keys the completion frame omits and the
structured value when the completion frame downgrades it to a string.

The new `cursor-streaming-args.test.ts` covers the merge helper directly
and end-to-end via `processInteractionUpdate`; with the old implementation
restored the same suite fails on the three regression cases above.

Fixes #2615
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.

Cursor MCP streaming drops the task tool's tasks array when the completion frame omits oversized params

2 participants