Skip to content

fix(sequentialthinking): keep nextThoughtNeeded in inputSchema required - #4701

Open
Golchic wants to merge 1 commit into
modelcontextprotocol:mainfrom
Golchic:fix/sequential-thinking-required-boolean
Open

fix(sequentialthinking): keep nextThoughtNeeded in inputSchema required#4701
Golchic wants to merge 1 commit into
modelcontextprotocol:mainfrom
Golchic:fix/sequential-thinking-required-boolean

Conversation

@Golchic

@Golchic Golchic commented Aug 27, 2026

Copy link
Copy Markdown

Description

nextThoughtNeeded is declared as a non-optional field on the sequentialthinking
tool, but it is missing from the generated JSON Schema's required array. Clients
that build call arguments from the advertised schema omit it and get
-32602 Input validation error, which contradicts the schema.

Root cause: the coercedBoolean helper was z.preprocess(fn, z.boolean()). Under
Zod v4, z.preprocess widens the input type to unknown, so the SDK's JSON
Schema generation (io: "input") treats the field as optional and drops it from
required — even though it is not .optional(). (Regression from #3533.)

Fix: reimplement coercedBoolean as z.union([z.boolean(), z.string().transform(...)]).
The union keeps a concrete input type, so non-optional fields stay in required,
while still coercing "true"/"false" (case-insensitively) and now rejecting other
strings with a clearer message. isRevision / needsMoreThoughts remain optional.

The only schema shape change: these boolean fields now serialize as
{ "anyOf": [{ "type": "boolean" }, { "type": "string" }] }, which accurately
reflects the accepted input.

Server Details

  • Server: sequentialthinking
  • Changes to: tools (input schema for sequentialthinking)

Motivation and Context

Fixes #4651. Without this, schema-driven MCP clients cannot call the tool without
manually knowing nextThoughtNeeded is required despite the schema saying otherwise.

How Has This Been Tested?

  • New src/sequentialthinking/__tests__/schema.test.ts:
    • Round-trips a probe tool through the MCP SDK (McpServer + Client over
      InMemoryTransport) and asserts a required coercedBoolean field stays in
      inputSchema.required, while an .optional() one does not.
    • Value coercion: true/false, "true"/"false"/"TRUE"/"False", and
      rejection of "yes", "", 1, null.
  • npm run build and npm test pass in src/sequentialthinking (19 tests).
  • Verified against the SDK's actual conversion path (Zod v4 → zod/v4-mini
    toJSONSchema, io: "input"), not a hand-rolled converter.

Breaking Changes

None for tool callers — the field was always meant to be required and the runtime
validation already required it. MCP client configs need no changes.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)

Checklist

  • I have read the MCP Protocol Documentation
  • My changes follows MCP security best practices
  • I have updated the server's README accordingly — not needed; the README
    already lists nextThoughtNeeded as a non-optional (boolean) input
  • I have tested this with an LLM client — tested with the MCP SDK client and
    automated tests
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have documented all environment variables and configuration options — n/a

Additional context

coercedBoolean moved from index.ts to lib.ts (exported) so it can be unit
tested; index.ts now imports it. zod is added as an explicit dependency of the
package since it is imported directly (matching the everything server).

The `coercedBoolean` helper used `z.preprocess(fn, z.boolean())` to accept
string "true"/"false" from clients. With Zod v4, `z.preprocess` widens the
input type to `unknown`, so the SDK's JSON Schema generation (io: "input")
drops the field from `required` even though it is not `.optional()`.

Clients that build arguments from the advertised schema then omit
`nextThoughtNeeded` and get `-32602 Input validation error`, which
contradicts the schema. Regression from modelcontextprotocol#3533.

Reimplement `coercedBoolean` as a union of `z.boolean()` and a string
transform. The union keeps a concrete input type, so required fields stay
in `required`, while still coercing "true"/"false" (case-insensitively)
and now rejecting other strings with a clearer message.

- Move `coercedBoolean` into lib.ts and export it so it can be unit-tested
- Declare zod as an explicit dependency (already imported directly; matches
  the everything server)
- Add schema.test.ts: round-trips a probe tool through the SDK and asserts
  a required coercedBoolean field stays in `required` while an optional one
  does not, plus value-coercion cases

Fixes modelcontextprotocol#4651
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

sequential-thinking: nextThoughtNeeded missing from inputSchema.required but required at runtime (regression from #3533)

1 participant