You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
fix(server): agents read why a T3 tool rejected their arguments - #35729
Cause: Effect's MCP 2025-06-18 adapter (offered first by McpHttpServer.layerMcpTransport and used as the fallback for unknown versions) answers a tool's invalid arguments with a JSON-RPC -32602 error. Only 2025-11-25 and newer turn invalid input into an isError tool result. Cursor reduces the protocol error to "Tool execution error", so models never learned what to fix. Fix: toolkitRegistration now wraps McpServer.registerToolkit with withReadableInvalidArguments, a wrapper around McpServer.addTool in the same style as withAdvertisedSchemas and withToolSpans. For tool calls, registerToolkit raises InvalidParams only when a tool's arguments fail validation, and the wrapper returns it as CallToolResult{isError:true, content:[text: validation message]}. That works on every negotiated protocol version, matches the 2025-11-25 spec, and matches what the hand-registered image tools (preview snapshot, device screenshot) already did. It sits inside the span and analytics wrappers, so those see the isError result and record it as a failure. The repro test from factory/repro-35713 passes unchanged. Five existing tests that asserted the old protocol failure (unknown-argument and below-minimum cases in McpHttpServer.test.ts, and the unknown-organize-target case in toolkits/core.test.ts) now assert the isError result. No production code calls the in-process server directly. No protocol versions, defaults or stdio bridge behaviour changed. No new regex or parser.
Commit e0d6aad (origin/main). Started T3CODE_DEV_SEED=0 vp run dev in a fresh worktree, enabled a cursor provider instance in the worktree's own .t3 settings.json, added a scratch project with t3 project add, then ran t3 thread start --provider cursor --model composer-2.5 with a prompt telling the model to call orchestrator_capabilities and then delegate_task once with target.options set to the JSON string "{"reasoningEffort":"high"}" (gemini-4-carbon itself was refused by the VM's Cursor account for usage). Observed: the Cursor SDK's tool-call-completed event for delegate_task carried result {"status":"error","error":{"message":"Tool execution error"}}, matching the reporter's payload, and T3's projected turn item for the call records only "Tool execution error". composer-2.5's reply did quote the full detail ("Tool execution error. MCP error -32602: Invalid parameters for tool 'delegate_task': Expected array | object | null at ["target"]["options"]"), so whether the model sees the detail depends on Cursor's backend and model; what Gemini receives was not verified. Server side: McpHttpServer.layerMcpTransport offers [v2025_06_18, v2026_07_28], with 2025-06-18 first and used as the fallback for unknown versions, and under 2025-06-18 Effect's McpServer answers invalid arguments with a JSON-RPC -32602 error instead of an isError tool result. Added a failing HTTP-transport test in apps/server/src/mcp/McpHttpServer.test.ts that negotiates 2025-06-18 and calls a tool with a string where an object is required; on main it receives {code:-32602, message:"Invalid parameters for tool 'probe': Expected object | null ..."} instead of an isError result.
Evidence
Being captured: before and after in the real app. This section is filled in before the pull request can merge.
Tests
vp test run src/mcp/McpHttpServer.test.ts (before the test updates: repro test passed, 4 old-behaviour tests failed as expected); vp test run src/mcp/ -> 26 files, 365 tests passed; vp run check:changed -> format, lint, knip, graphql, typecheck (3 packages), tests (3 files) all pass
Not in this change
Offering MCP 2025-11-25 (or making it the fallback) in layerMcpTransport, and the 2025-06-18 pin in AcpMcpStdioBridge.ts: not needed for this fix and would change protocol negotiation for every client
The upstream Effect behaviour where the 2025-06-18 adapter maps InvalidToolInput to a JSON-RPC error rather than an isError result
Cursor SDK reducing JSON-RPC errors to a generic 'Tool execution error' in what T3 records for the call
Who did the work
Written by Claude Opus 5.5, in Claude Code
Reviewed by GPT-6.1 Sol, in Codex, and by Claude Opus 5.5, each on its own
Proof captured by GPT-6.1 Sol and checked by Claude Opus 5.5
…n error"
Failing repro for #35713: over HTTP MCP 2025-06-18, T3 answers invalid
tool arguments with a JSON-RPC -32602 error instead of an isError tool
result, and Cursor reduces protocol errors to a generic message.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Under MCP 2025-06-18, which Cursor negotiates, invalid tool arguments went
back as a JSON-RPC -32602 error. Cursor reduces that to "Tool execution
error", so models never saw what to fix (for example target.options sent
as a string to delegate_task). Toolkit tools now return the validation
message as an isError tool result on every protocol version, as MCP
2025-11-25 specifies and the hand-registered image tools already do.
Fixes#35713
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Macroscope's review found this PR approvable — Invalid tool arguments now return readable validation details as an error-marked tool result, allowing agents to correct their calls. The change is limited to error presentation and leaves successful tool execution and access controls unchanged.
Notes:
All code in this push has already been reviewed. Approvability was decided on eligibility alone.
You can add or adjust custom eligibility rules. Learn more.
No actionable comments were generated in the recent review.
Walkthrough
The server converts invalid tool argument failures into MCP error tool results with readable messages. Tests check returned errors and verify that rejected calls are not recorded or dispatched.
Changes: Readable MCP validation errors
Layer / File(s)
Summary
Convert invalid parameters to tool results apps/server/src/mcp/McpHttpServer.ts
A wrapper converts InvalidParams failures from registered tool handlers into error tool results containing the error message. Toolkit registration applies the wrapper.
Invalid tool arguments now come back as readable tool errors instead of protocol errors, so agents can see why a call was rejected and correct it. No merge-blocking risk was identified.
Pre-merge checks: 4
Check name
Status
Explanation
Title check
The title names the main change: agents can now read why a T3 tool rejected their arguments. The changeset wraps toolkit registration so invalid arguments return an isError tool result with the valida…
Description check
The description follows the template's main sections. It gives a clear cause, a change summary, and a reproduction on main. It links the issue (#35713) for scope and lists tests run. Two template item…
Linked Issues check
Directly linked issue 35713 asks that the model see why delegate_task rejected target.options. At head 018a653, McpHttpServer.ts adds withReadableInvalidArguments. It wraps McpServer.addTool, catches …
Out of Scope Changes check
The changed code in McpHttpServer.ts serves issue 35713. withReadableInvalidArguments is the fix. Its placement inside withToolSpans and withToolAnalytics lets those wrappers record the isError result…
Full details: Title check
Explanation
The title names the main change: agents can now read why a T3 tool rejected their arguments. The changeset wraps toolkit registration so invalid arguments return an isError tool result with the validation message. The title uses the conventional commit form and is specific, so it passes.
Full details: Description check
Explanation
The description follows the template's main sections. It gives a clear cause, a change summary, and a reproduction on main. It links the issue (#35713) for scope and lists tests run. Two template items are weak. The Evidence section says before/after capture is still pending, and the Scope section does not state a bug-fix rationale beyond the linked issue. The Verification section lists focused and repo checks with observed results, which meets the template's bar.
Resolution
Complete the Evidence section with the before and after capture in the real app, or state what could not be captured. The PR objectives also note that the base commit was not confirmed as reproducing the problem by automated capture, so the reproduction claim should be stated with that caveat.
Full details: Linked Issues check
Explanation
Directly linked issue 35713 asks that the model see why delegate_task rejected target.options. At head 018a653, McpHttpServer.ts adds withReadableInvalidArguments. It wraps McpServer.addTool, catches InvalidParams from each tool handler, and returns an isError CallToolResult whose text is the validation message. toolkitRegistration applies it around McpServer.registerToolkit, so delegate_task and other toolkit tools return the message as a readable tool result. This matches the first expected outcome in issue 35713. The wrapper does not depend on protocol version, so it covers the 2025-06-18 path from the issue. Test edits are described in the raw summary but were not read. The incremental diff from previous head f997adc was too large to inspect, so changes since the previous review are confirmed only for McpHttpServer.ts. Whether Cursor shows the message to the model is outside this PR.
Full details: Out of Scope Changes check
Explanation
The changed code in McpHttpServer.ts serves issue 35713. withReadableInvalidArguments is the fix. Its placement inside withToolSpans and withToolAnalytics lets those wrappers record the isError result as a failure. The raw summary lists three changed files: McpHttpServer.ts, McpHttpServer.test.ts, and toolkits/core.test.ts. The test edits update existing tests that asserted the old JSON-RPC failure, so they support the behavior change. The PR does not change protocol versions, the layerMcpTransport fallback, or AcpMcpStdioBridge, and it adds no parser. The test hunks were not read, and changes since the previous review were not fully inspected.
Finishing Touches
Generate unit tests (beta)
Commit to this branch
Create a new PR
Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
This review was completed with usage-based billing: files reviewed beyond your plan's included limits are billed at $0.25/file. View usage-based billing.
The automated pipeline stopped on this pull request and will not merge it:
the capture step could not show the problem on the base commit, so there is nothing to prove fixed.
On head, installed dependencies, started the dev server, enabled Cursor, added a synthetic scratch project, and started gemini-4-carbon. The prompt requested orchestrator_capabilities, an intentionally invalid delegate_task call with string target.options, quoting the received error, then retrying with an object preserving reasoningEffort high. Cursor refused the model before any tool call. Base was not run; neither verdict is established. Stopped the server and removed the scratch directory and worktree.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
size:M30-99 changed lines (additions + deletions).takeover:blockedTakeover blocked; reason in the PR bodyvouch:trustedAuthor is trusted by repo permissions or the VOUCHED list.
1 participant
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Cause: Effect's MCP 2025-06-18 adapter (offered first by McpHttpServer.layerMcpTransport and used as the fallback for unknown versions) answers a tool's invalid arguments with a JSON-RPC -32602 error. Only 2025-11-25 and newer turn invalid input into an isError tool result. Cursor reduces the protocol error to "Tool execution error", so models never learned what to fix. Fix: toolkitRegistration now wraps McpServer.registerToolkit with withReadableInvalidArguments, a wrapper around McpServer.addTool in the same style as withAdvertisedSchemas and withToolSpans. For tool calls, registerToolkit raises InvalidParams only when a tool's arguments fail validation, and the wrapper returns it as CallToolResult{isError:true, content:[text: validation message]}. That works on every negotiated protocol version, matches the 2025-11-25 spec, and matches what the hand-registered image tools (preview snapshot, device screenshot) already did. It sits inside the span and analytics wrappers, so those see the isError result and record it as a failure. The repro test from factory/repro-35713 passes unchanged. Five existing tests that asserted the old protocol failure (unknown-argument and below-minimum cases in McpHttpServer.test.ts, and the unknown-organize-target case in toolkits/core.test.ts) now assert the isError result. No production code calls the in-process server directly. No protocol versions, defaults or stdio bridge behaviour changed. No new regex or parser.
Fixes #35713
Reproduction on main
Commit e0d6aad (origin/main). Started
T3CODE_DEV_SEED=0 vp run devin a fresh worktree, enabled acursorprovider instance in the worktree's own .t3 settings.json, added a scratch project witht3 project add, then rant3 thread start --provider cursor --model composer-2.5with a prompt telling the model to call orchestrator_capabilities and then delegate_task once with target.options set to the JSON string "{"reasoningEffort":"high"}" (gemini-4-carbon itself was refused by the VM's Cursor account for usage). Observed: the Cursor SDK's tool-call-completed event for delegate_task carried result {"status":"error","error":{"message":"Tool execution error"}}, matching the reporter's payload, and T3's projected turn item for the call records only "Tool execution error". composer-2.5's reply did quote the full detail ("Tool execution error. MCP error -32602: Invalid parameters for tool 'delegate_task': Expected array | object | null at ["target"]["options"]"), so whether the model sees the detail depends on Cursor's backend and model; what Gemini receives was not verified. Server side: McpHttpServer.layerMcpTransport offers [v2025_06_18, v2026_07_28], with 2025-06-18 first and used as the fallback for unknown versions, and under 2025-06-18 Effect's McpServer answers invalid arguments with a JSON-RPC -32602 error instead of an isError tool result. Added a failing HTTP-transport test in apps/server/src/mcp/McpHttpServer.test.ts that negotiates 2025-06-18 and calls a tool with a string where an object is required; on main it receives {code:-32602, message:"Invalid parameters for tool 'probe': Expected object | null ..."} instead of an isError result.Evidence
Being captured: before and after in the real app. This section is filled in before the pull request can merge.
Tests
vp test run src/mcp/McpHttpServer.test.ts (before the test updates: repro test passed, 4 old-behaviour tests failed as expected); vp test run src/mcp/ -> 26 files, 365 tests passed; vp run check:changed -> format, lint, knip, graphql, typecheck (3 packages), tests (3 files) all pass
Not in this change
Who did the work
Run by the t3code factory in T3 Code.