Repository navigation
fix(server): type registerTool structuredContent from outputSchema - #2449
mattzcarey wants to merge 1 commit into
Conversation
🦋 Changeset detectedLatest commit: b1b9efb 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: |
ea5c666 to
d92a8bf
Compare
There was a problem hiding this comment.
2 optional notes from this repository's REVIEW.md or CLAUDE.md checks were not posted as comments, over this review's limit for such notes; they are on this commit's check card.
Findings marked 🟡 are optional suggestions and need no follow-up push.
Additional findings (outside the current diff — GitHub can't attach inline comments there):
-
🟡
skills/mcp-typescript-sdk-upgrade-to-v2/references/server-registration.md— nit: The upgrade skill describes theregisterTooloverload error and thecontentrule in ways this change makes false. Lines 113-117 say TS2769 lists "both overloads" with the cause in "the first overload's elaboration".registerToolnow has three overloads and the Standard Schema one is last (packages/server/src/server/mcp.ts:1088). Fix: reword each skill statement the new typing contradicts, which covers the 2 sites listed. Same instruction at 2 sites (server-registration.md:117, behavioral-changes.md:200). The second says the TypeScript surface requirescontenton handler results. [also at: packages/server/src/server/mcp.ts:1033 - nit: The upgrade guide's zod-version diagnosis no longer matches theregisterToolerror users see. skills/mcp-typescript-sdk-upgrade-to-v2/references/server-registration.md:113-119 describes a "two-overload error" with the cause in "the first overload's elaboration".; skills/mcp-typescript-sdk-upgrade-to-v2/references/server-registration.md:46 - nit: The upgrade skill's troubleshooting text no longer matches theregisterToolerror it describes. Lines 113-117 of this file sayTS2769lists "both overloads" with the cause in "the first overload's elaboration".] (Finding ID:f-fd569433)Why this was flagged
No runtime trigger; this is a words-are-true check on the upgrade skill that users and agents follow to diagnose migration compile errors. The diff reorders the
registerTooloverloads at packages/server/src/server/mcp.ts:1032-1105: two deprecated raw-shape overloads come first and the Standard Schema overload is third. With zod typings that lack~standard.jsonSchema, tsc now reports three overloads and theProperty 'jsonSchema' is missingelaboration sits under the last one, not the first, so the "two-overload error" cue at server-registration.md:117 no longer matches. The diff also makescontentoptional on typed successes (mcp.ts:1403), while skills/mcp-typescript-sdk-upgrade-to-v2/references/behavioral-changes.md:199-201 still says "the TypeScript surface requirescontenton handler results". On the base both statements matchedregisterTool. No safeguard applies: the skill text is not checked against the types.Verification: On the base,
registerToolhad two overloads with the Standard Schema one first, so the text was accurate. At HEAD there are three: raw-shape at packages/server/src/server/mcp.ts:1033, raw-shape output-only at :1048, Standard Schema last at :1088. The diff addsToolResultWithStructuredContentwithcontent?: CallToolResult['content'](mcp.ts:1403), socontentis optional on typed successes.
d92a8bf to
518c67c
Compare
There was a problem hiding this comment.
Code review completed
Nothing new to post: everything this review found is already covered by existing comments on this pull request or didn't merit a separate one.
3 further findings on code this push did not change were outside the scope of this re-review and not posted.
Still open from earlier reviews (7):
- 🔴
.changeset/register-tool-output-typing.md:5—Users upgrading get compile errors on correct tool callbacks that the changeset does not list as breaking. Line 5 names… - Also unresolved: 6 minor or pre-existing.
If you have decided not to act on one of these findings, resolve its thread (a reply alone leaves it open) and the next review stops counting it. To review this commit again now, use Re-run on its "Claude Code Review" check.
518c67c to
83b661f
Compare
83b661f to
ae97ba9
Compare
A tool's outputSchema now types its callback's successful result: structuredContent is required and must match the schema, for the Standard Schema overload and the deprecated raw-shape overload. isError results, input-required results and schema-less tools keep their existing types. Types only; runtime behaviour is unchanged. The required value satisfies both sides of the schema, because the server validates and sends the handler's raw value while tools/list advertises the output-side JSON Schema. undefined is excluded because the runtime treats it as absent; an unknown schema output accepts any value. The raw-shape overload moves ahead of the Standard Schema overload so overload errors point at structuredContent. A gate on config lets it match only configs that use a raw shape, so optional or any-typed schemas keep resolving as before. Closes #2754.
ae97ba9 to
b1b9efb
Compare
| * other registration must compile. `pnpm typecheck` enforces both. The runtime assertions only | ||
| * pin the authoring affordances the types rely on (content auto-fill). |
There was a problem hiding this comment.
🟡 nit (optional): The new test file's header describes runtime assertions for "content auto-fill" that the file does not contain, so maintainers are pointed at coverage and behaviour that do not exist. Lines 5-6 say "The runtime assertions only pin the authoring affordances the types rely on (content auto-fill)". The file has no expect( call, and line 12 still imports expect. The same file now asserts that content is required (lines 128-140). Fix: drop that sentence and the unused expect import so the header says the file is type-level only.
Why this was flagged
No runtime trigger; this is a wording check on a comment the diff adds. packages/server/test/server/registerToolOutputTyping.test.ts:5-6 claims runtime assertions that pin "content auto-fill". The file contains only expectTypeOf checks and @ ts-expect-error lines; expect is imported at line 12 and never called. The typed result declares content: CallToolResult['content'] as required (packages/server/src/server/mcp.ts ToolResultWithStructuredContent), and the test at lines 128-140 is titled 'requires content on every result, typed or not', so there is no auto-fill behaviour for the comment to refer to. The base branch has no such file. No safeguard catches it: lint runs eslint src/ only (packages/server/package.json:155), and the shared tsconfig sets no noUnusedLocals, so the unused import and the stale sentence pass CI.
Verification: nit — no runtime trigger; a stale header comment in a file this diff adds. packages/server/test/server/registerToolOutputTyping.test.ts:5-6 says "The runtime assertions only pin the authoring affordances the types rely on (content auto-fill)." expect is imported at line 12 and never used. So there are no runtime assertions at all, and nothing pins "content auto-fill".
Finding ID: f-1a117278
McpServer.registerToolnow type-checks a callback's successful result against the tool'soutputSchema.structuredContentis required and must match the schema, for Standard Schema and the deprecated raw-shape overload.isError: trueresults, input-required results and tools without anoutputSchemakeep their current types. Types only: runtime behaviour is unchanged. Closes #2754.Why it is shaped this way:
tools/listadvertises the output-side JSON Schema that clients validate against. So the required type is the schema's output when that is also a valid input, else input & output. A.default()field must be supplied, a coerced field must already have the output type, and an incoherent.transform()resolves tonever.undefinedis rejected (the runtime treats it as absent), including for optional roots likez.string().optional().nullis allowed when the schema allows it. Anunknownschema output (an untypedfromJsonSchema(...),z.unknown()) accepts any value, so forwardedunknownresults still compile;structuredContentis still required.configlets it match only configs that really use a raw shape. TypeScript reports the last overload's error, so a mismatch now points atstructuredContentrather than atZodRawShape, and zero-argument raw-shape callbacks returningisError: trueno longer hit a false positive.ToolResultFor,ToolStructuredContent,ToolResultWithStructuredContent,ToolErrorResult.ToolCallback<In, Out?>takes an optional output schema.This is a compile-time break for tools with an
outputSchema, so the changeset is a server minor and lists each case. Besides mismatched or missingstructuredContent, it breaks callbacks whose result is typed as plainCallToolResult, and registries forwarding anoutputSchematypedS | undefined, which TypeScript checks as if present.Runtime validation is unchanged and still authoritative for refinements,
fromJsonSchemaschemas, casts and JavaScript callers.Implements SEP-2106's guidance that SDKs provide generics over a tool's
outputSchema. Supersedes #2438 and #2800, which type the same callback; this version also catches missingstructuredContent, keepscontenttyped and handles transforms. Compile-time tests cover the #2754 snippet, Zod, ArkType, Valibot and raw shapes under tsgo and tsc.