Repository navigation
fix(client): use one tool definition for mirroring and output validation - #3030
Poke-nushi wants to merge 1 commit into
Conversation
🦋 Changeset detectedLatest commit: f363369 The changes in this PR will be included in the next version bump. This PR includes changesets to release 6 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
@modelcontextprotocol/client
@modelcontextprotocol/codemod
@modelcontextprotocol/core
@modelcontextprotocol/server
@modelcontextprotocol/server-legacy
@modelcontextprotocol/express
@modelcontextprotocol/fastify
@modelcontextprotocol/hono
@modelcontextprotocol/node
commit: |
karaaslanz
left a comment
There was a problem hiding this comment.
Independent source-backed review of exact head f3633697b426a45354e85549b764bd2f11287fe9. I compared the actual Client.callTool() method at base 9bc40e903fde15534ec06ff4c1347d47000dbe16 (client.ts blob 8118b60cea757e3bdfdcdfa35cd2a95bb558ba99) and this PR (client.ts blob 75b0cb1c7346072c035ace30b09701097de12e4c) using Node 24's TypeScript stripping and a deterministic isolated method harness with a deferred cache read.
The harness pauses outputValidator() after capturing catalog generation Old, refreshes the catalog to generation New before releasing the read, and simulates a server that accepts only Mcp-Param-New-Region and replies with either a New or Old structured output. The harness executes the extracted source method from each commit, not a handwritten paraphrase of the client's callTool flow.
Results, 4/4 expected comparisons PASS:
- Unmodified base, New response: the headers come from New but the output validator is from Old; it rejects a valid New output with InvalidParams (
-32602). - Unmodified base, Old response: the same mixed-generation call incorrectly accepts Old output against the new-header call.
- PR head, New response: the initial Old header attempt is rejected with HEADER_MISMATCH; after one refresh, both header and validator use New, and the call succeeds.
- PR head, Old response: the same retry uses New metadata and rejects the obsolete Old output (
-32602).
This independently reproduces the pre-fix race and confirms the intended generation consistency in the patched code. The fix's one-per-attempt { tool, compiled } snapshot and metadata refresh on HEADER_MISMATCH align with the observed behavior. GitHub Actions workflows for the pinned head report success. Scope: isolated source-extracted method simulation, with mocked cache/transport/schema callbacks, not a full repository/E2E test. No repository files changed. I found no issue in the two tested race paths.
When an asynchronous response-cache read overlaps a
tools/listrefresh,Client.callTool()can buildMcp-Param-*headers from the new tool definition while validating its result against the old definition. A successful handler result then rejects withProtocolError -32602.This change memoizes the source tool alongside its compiled output validator. Each attempt derives headers and output validation from that same snapshot; the existing
HEADER_MISMATCHrecovery resolves a fresh snapshot for the retry.The regression cases pause a read through the public asynchronous
ResponseCacheStoreinterface, complete a catalog refresh, and resume the call. Both cases check that the stale-header attempt is rejected before handler execution, the retry uses the new definition, and the handler executes exactly once. The positive case accepts output matching the new schema; the negative case rejects output valid only under the old schema. The tests use an in-memory transport and no sleeps.Validation
All 14 PR checks have passed on GitHub, including Node.js 20/22/24 unit tests and E2E, Bun/Deno, build, examples, and client/server conformance.
Base:
modelcontextprotocol/typescript-sdkmain at9bc40e903fde15534ec06ff4c1347d47000dbe16. Local Node.js 22.16.0, pnpm 10.26.1.-32602.pnpm run build:allandpnpm run lint:allpass.pnpm --filter @modelcontextprotocol/client testAI disclosure: Codex investigated the race, wrote the patch and regression test, drafted this description, and ran the local checks.