fix(client): skip outputSchema validation when result is an error#1945
fix(client): skip outputSchema validation when result is an error#1945bokelley wants to merge 1 commit intomodelcontextprotocol:mainfrom
Conversation
Aligns client-side callTool and experimental.tasks.callToolStream with
the server-side validateToolOutput short-circuit on isError. The guard
comment already said "not when there's an error" — the !result.isError
check was just missing.
Tools returning a structured error envelope (e.g. { error: { code,
message } } alongside isError: true) are no longer rejected with
"Structured content does not match the tool's output schema".
Fixes modelcontextprotocol#1943.
🦋 Changeset detectedLatest commit: ed8e535 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
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/server
@modelcontextprotocol/express
@modelcontextprotocol/fastify
@modelcontextprotocol/hono
@modelcontextprotocol/node
commit: |
|
Fix matches the triage. Both guard sites closed with the same One merge-useful framing for reviewers: the comment above each guard already reads "Only validate structured content if present (not when there's an error)". The patch makes the guard match the stated intent rather than introducing new intent, so this is strictly a bug fix (semver-patch) aligning the client with the server-side short-circuit at Test coverage pairs with the existing |
bokelley
left a comment
There was a problem hiding this comment.
Fix is correct — symmetric with server-side validateToolOutput at packages/server/src/server/mcp.ts:276, and the changeset scope/level look right.
Two things before merge:
-
Duplicate test name. The new streaming test shares the exact name of the existing one at old line 4087 (
'callToolStream() should not validate structuredContent when isError is true'). They cover different cases — existing:isErrorwith nostructuredContent; new:isErrorwith mismatchedstructuredContent— but identical names make failure triage ambiguous. Suggest renaming the new one to'callToolStream() should not validate structuredContent against outputSchema when isError is true'(mirrors the non-streaming test in theoutputSchema validationdescribe block), and/or narrowing the old one to'...when isError is true and structuredContent is absent'. -
Unnecessary cast. The new test does
messages[0]!.result as CallToolResultand adds aCallToolResultimport; the existing line-4087 test accesses.result.isErrorwith no cast. Drop the cast to match established style, or add a one-line comment if the inference really requires it.
Optional: a third test with isError: true and structuredContent that matches outputSchema would document that validation is skipped regardless of shape — guards against a future "optimization" that re-introduces the bug. Not a blocker.
|
On the optional third test: the same coverage gap exists on the server side. Adding it on the client side alone leaves the server-side invariant untested. If the merge bar allows, a paired server test (tool returns On the two flagged items: rename to |
|
Went through the same two-site analysis on the issue before #1947 got closed, so I can confirm the guard logic here is right. The rename truffle-dev suggested ( On the server-side test gap: happy to take that as a follow-up if it helps get this merged without expanding scope. One test, path is clear. |
|
Thanks Zelys-DFKH. Split is the right call. The third test probes a separate policy question (skip on every error, or only when content mismatches the schema), cleaner as its own thread. Happy to review the follow-up when it is up. |
Motivation and Context
Fixes #1943.
Client-side
callToolvalidatesstructuredContentagainstoutputSchemawhenever it is present, regardless ofresult.isError. The server-sidevalidateToolOutputcorrectly short-circuits onisError(packages/server/src/server/mcp.ts:276). The asymmetry makes any tool whose error envelope carriesstructuredContent(e.g.{ error: { code, message } }) unusable withoutputSchema— the client throwsStructured content does not match the tool's output schemabefore the caller can see the error payload.Per @truffle-dev's triage on the issue, the same missing
!result.isErrorguard exists in two places with the same comment:packages/client/src/client/client.ts:893(maincallTool)packages/client/src/experimental/tasks/client.ts:139(callToolStream)Fixing only the first leaves tasks-mode clients with the same bug. This PR fixes both.
How Has This Been Tested?
Added two integration tests in
test/integration/test/client/client.test.ts:should not validate structuredContent against outputSchema when isError is true— coversclient.callTool.callToolStream() should not validate structuredContent when isError is true(withstructuredContentpresent) — complements the existing test at line 4014, which only covers theisError+ no-structuredContentcase.Both tests return an error envelope whose
structuredContentdoes not matchoutputSchemaand assert the call resolves withisError: trueand the originalstructuredContentpreserved. Without the fix, both throwProtocolErrorwith the "does not match the tool's output schema" message.Typecheck and build also pass:
Breaking Changes
None. The fix aligns client behavior with server behavior and the existing comment on the guard.
Types of changes
Checklist
Additional context
The guard comment already read "Only validate structured content if present (not when there's an error)" — the
!result.isErrorcheck was just missing. Server-side reference for contrast atpackages/server/src/server/mcp.ts:276:Found while migrating
@adcp/clientto useregisterToolwith structured error envelopes.