fix(server): accept empty {} annotations in tool() overload#1980
Open
AP3X-Dev wants to merge 1 commit intomodelcontextprotocol:v1.xfrom
Open
fix(server): accept empty {} annotations in tool() overload#1980AP3X-Dev wants to merge 1 commit intomodelcontextprotocol:v1.xfrom
AP3X-Dev wants to merge 1 commit intomodelcontextprotocol:v1.xfrom
Conversation
server.tool(name, desc, schema, {}, cb) was misclassifying the empty
annotations object as a second schema. isZodRawShapeCompat({}) returns
true to support no-arg-tool schemas, so the annotations branch was
skipped and the callback position fell through to {}. Calls then threw
"typedHandler is not a function" at dispatch.
The annotations-position parser now accepts {} after a schema has
already been consumed, since an empty raw shape can't appear in the
annotations slot ambiguously once the schema slot is filled.
🦋 Changeset detectedLatest commit: 88f46af The changes in this PR will be included in the next version bump. 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 |
commit: |
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Summary
server.tool(name, description, schema, {}, callback)registers successfully but throwstypedHandler is not a functionat first call.The overload parser at
src/server/mcp.ts:993usesisZodRawShapeCompat()to disambiguate between a Zod raw shape and aToolAnnotationsobject in the slot after the schema.isZodRawShapeCompat({})returnstrue(intentionally — to accept tools with no parameters in the schema slot), so an empty annotations object passed after a populated schema is misclassified as a second schema, the annotations branch is skipped, andcallback = rest[0]ends up as{}instead of the function.At dispatch time,
executeToolHandlerdoes:```ts
const typedHandler = handler;
return await Promise.resolve(typedHandler(args, extra));
```
…which throws
TypeError: typedHandler is not a function.Root cause
src/server/mcp.ts:1021(pre-fix):```ts
if (rest.length > 1 && typeof rest[0] === 'object' && rest[0] !== null && !isZodRawShapeCompat(rest[0])) {
annotations = rest.shift() as ToolAnnotations;
}
```
For empty
{},!isZodRawShapeCompat({})isfalse, so annotations are not consumed. The handler slot then receives{}.Fix
Once the schema slot has been filled above, an empty object in the next slot is unambiguously annotations (a populated raw shape would have at least one Zod value). The annotations check now accepts empty objects:
```ts
if (
rest.length > 1 &&
typeof rest[0] === 'object' &&
rest[0] !== null &&
(Object.keys(rest[0] as object).length === 0 || !isZodRawShapeCompat(rest[0]))
) {
annotations = rest.shift() as ToolAnnotations;
}
```
Non-empty objects retain their existing protective check (a populated Zod raw shape in the annotations slot stays an error rather than being silently consumed as annotations).
Test plan
test/server/mcp.test.tscoveringtool(name, desc, schema, {}, cb)— registers, lists, and calls successfully (passes against both Zod v3 and Zod v4 fixtures).test/server/mcp.test.tssuite passes (234/234).npm run typecheckpasses.npx prettier --checkpasses on touched files.npx eslint src/server/mcp.tspasses.Real-world impact
Any tool registered with
server.tool(name, desc, schema, {}, cb)is unreachable. Encountered this in a downstream MCP server where 6 write-tool registrations all used{}as a placeholder for "no annotations to declare yet" — every write call surfaced astypedHandler is not a functionwhile reads (which used populated annotations) worked.Affects v1.x line. Not present on
main— the 2.0 rewrite uses a different dispatch path.