Repository navigation
fix(mcp): avoid replaying tools after ambiguous call failures - #4124
Conversation
|
Review of Two added test fixtures need to be updated for the MCP SDK on current main (mcp-go v1.1.1):
I reproduced all three failures on the current-main merged tree. With only those fixture adjustments in a temporary copy, every subcase of |
|
Thanks for reproducing this against the updated SDK. I reproduced the same three failures after merging PR head
All 11 subcases of the requested tests pass on the updated merged tree: go test -ldflags='-w -s' ./internal/agent/tools ./internal/mcp \
-run 'TestMCPCallRetryDoesNotRepeatAmbiguousExecution|TestCallToolOAuthRefreshFailureDoesNotClaimUnknownExecution' \
-count=1 -vThe full MCP/tools/agent package tests, focused race tests, vet and formatting also pass on that tree. Full-root diff lint passes with golangci-lint v2.12.2 (0 issues); the initial local 1 GiB run was stopped after exceeding 10 minutes, and the rerun passed with a 2 GiB memory budget. The same two tests additionally pass on the original branch with mcp-go v0.52.0, covering the legacy handshake. Verification used Go 1.26.0 in Ubuntu/WSL and local HTTP/approval fixtures; debug symbols were stripped for local memory limits. This follow-up changes only the two test files. |
Description
A remote MCP tool can commit a write and then lose its response, return a gateway error, or report an RPC failure. The agent currently disconnects and calls the tool again after any non-stdio call error. In a local HTTP reproduction, one approved
create_orderinvocation executes the server handler twice and is reported as successful after the second call.This patch limits that automatic replay to pre-execution rejection signals:
ErrNotConnected, the SDK's typed expired-session/HTTP 404 error, and typed authorization/reauthorization failures. The agent's reconnect recovery remains bounded to one retry and does not start after caller cancellation. Other attempted-call failures retain their cause and carry an unknown-outcome error with advice to verify remote state before retrying. The agent renders this as a tool-call failure, rather than a failed connection. OAuth preflight, token refresh and connection-based authorization recovery retain their behavior.Tests exercise an actual SDK-backed HTTP server, discovery/description, approval,
call_mcp_tool, and the model-context renderer. They inject 502, EOF and RPC errors after executing the handler, plus cancellation, expired sessions, ordinary tool errors and successful calls. Separately approved later calls remain possible. The MCP guide explains the verification/retry behavior.Performance and compatibility
isErrorresults and OAuth's existing one-time resource-401 refresh are preserved. Typed expired-session and authorization recovery remain available.Type of Change
Related Issue
N/A — reproduced on current main while checking the MCP execution path. This retains the session recovery introduced by #3765. It is independent of #4074 (structured result preservation) and #4095 (protocol negotiation).
Testing
Branch point and reproduction baseline:
097bd475f; tested head:4f597f14c. Ubuntu/WSL, Go 1.26.0, golangci-lint 2.12.2, CGO and SQLite development headers. Local protocol/approval tests use HTTP fixture servers and a fake approval policy, with no live model, external credentials or production writes. Resource-limited test/build commands useGOMAXPROCS=2,GOFLAGS=-p=1and strip debug symbols (-ldflags='-w -s'); this does not replace the normal GitHub App workflow.mcp_call_retry_test.gocopied into an isolated base worktree:go test -ldflags='-w -s' ./internal/agent/tools -run '^TestMCPCallRetryDoesNotRepeatAmbiguousExecution$' -count=1— four scenarios fail: three logrequests=2, server executions=2, approvals=1, and cancellation lacks the model-visible unknown-outcome explanation.go test -ldflags='-w -s' ./internal/mcp ./internal/agent/tools ./internal/agent -count=1— passed.go test -race -ldflags='-w -s' ./internal/mcp ./internal/agent/tools -run 'TestMCPCallRetry|TestIsToolCallRetrySafe|TestCallToolOAuthRefreshFailure|TestOAuthCall' -count=1— passed.go vet(all root packages except/docreader/) — passed.go test -ldflags='-w -s'(the same package list) — passed.go test -ldflags='-w -s' -tags desktop ./internal/container— passed.go build -ldflags='-w -s' ./cmd/server— passed (output written outside the worktree).golangci-lint run --concurrency=1 --timeout=10m --new-from-merge-base=097bd475f ./...— passed, 0 issues. It emits an existing malformednolintwarning in the unchanged OpenSearch test file.gofmt -landgit diff --check origin/main...HEAD— passed.node scripts/check-links.mjsandnode scripts/check-mermaid.mjsinwebsite-docs/— passed: 319 links and 60 existing diagrams checked.Checklist
git diff --check origin/main...HEADpassesScreenshots / Recordings
Not applicable; no UI layout or interaction implementation changes. Tests verify approval counts and the exact result supplied to the model.