Summary
sequentialthinking declares nextThoughtNeeded as an optional parameter in its advertised inputSchema, but runtime validation requires it. Any client that builds arguments from the declared schema omits the field and gets -32602.
This is a regression from #3533, which replaced z.boolean() with a z.preprocess()-based coercedBoolean.
Reproduction
Start the server and call tools/list:
"required": ["thought", "thoughtNumber", "totalThoughts"]
nextThoughtNeeded is absent, yet it is declared as a plain (non-optional) property:
"nextThoughtNeeded": {"description": "Whether another thought step is needed", "type": "boolean"}
Calling the tool with exactly the declared required fields fails:
tools/call sequentialthinking {"thought": "example", "thoughtNumber": 1, "totalThoughts": 1}
MCP error -32602: Input validation error: Invalid arguments for tool sequentialthinking:
Invalid input: expected boolean, received undefined at nextThoughtNeeded
Observed with @modelcontextprotocol/server-sequential-thinking@2026.7.4. The current main of src/sequentialthinking/index.ts has the same definition, so it is not fixed there either.
Root cause
z.preprocess() produces a schema whose input type accepts unknown, so the zod-to-JSON-Schema conversion treats it as optional and omits it from required. Minimal repro with zod 4.4.3:
const coercedBoolean = z.preprocess((v) => v, z.boolean());
const schema = z.object({
req: z.boolean(),
pre: coercedBoolean,
coerced: z.coerce.number(),
opt: z.boolean().optional(),
});
z.toJSONSchema(schema, { io: "input" }).required;
// => ["req", "coerced"] <-- "pre" is missing
The same file already contains the contrast: thoughtNumber: z.coerce.number() stays in required, because z.coerce does not wrap the schema the way z.preprocess does. Only the three coercedBoolean fields are affected, and two of them (isRevision, needsMoreThoughts) are intentionally .optional(), so nextThoughtNeeded is the only one where the declaration and the validation disagree.
Before #3533:
- nextThoughtNeeded: z.boolean().describe("Whether another thought step is needed"),
+ nextThoughtNeeded: coercedBoolean.describe("Whether another thought step is needed"),
Impact
The advertised schema is the only contract a client has. A client that respects it produces a call the server rejects, and the rejection points at a field the schema said was optional. Clients that happen to always send nextThoughtNeeded (most LLM callers, since the description prompts for it) never see this.
Suggested fix
Keep the string coercion but produce a schema that survives JSON Schema conversion, for example by unioning instead of preprocessing:
const coercedBoolean = z.union([
z.boolean(),
z.enum(["true", "false"]).transform((v) => v === "true"),
]);
Any approach that leaves nextThoughtNeeded in the emitted required list works. Happy to send a PR if you would like one.
How this was found
Found by an automated MCP server test-generation tool that derives cases from the declared inputSchema. The happy-path case it generated used exactly the declared required fields, which is why the mismatch surfaced immediately.
Summary
sequentialthinkingdeclaresnextThoughtNeededas an optional parameter in its advertisedinputSchema, but runtime validation requires it. Any client that builds arguments from the declared schema omits the field and gets-32602.This is a regression from #3533, which replaced
z.boolean()with az.preprocess()-basedcoercedBoolean.Reproduction
Start the server and call
tools/list:nextThoughtNeededis absent, yet it is declared as a plain (non-optional) property:Calling the tool with exactly the declared required fields fails:
Observed with
@modelcontextprotocol/server-sequential-thinking@2026.7.4. The currentmainofsrc/sequentialthinking/index.tshas the same definition, so it is not fixed there either.Root cause
z.preprocess()produces a schema whose input type acceptsunknown, so the zod-to-JSON-Schema conversion treats it as optional and omits it fromrequired. Minimal repro with zod 4.4.3:The same file already contains the contrast:
thoughtNumber: z.coerce.number()stays inrequired, becausez.coercedoes not wrap the schema the wayz.preprocessdoes. Only the threecoercedBooleanfields are affected, and two of them (isRevision,needsMoreThoughts) are intentionally.optional(), sonextThoughtNeededis the only one where the declaration and the validation disagree.Before #3533:
Impact
The advertised schema is the only contract a client has. A client that respects it produces a call the server rejects, and the rejection points at a field the schema said was optional. Clients that happen to always send
nextThoughtNeeded(most LLM callers, since the description prompts for it) never see this.Suggested fix
Keep the string coercion but produce a schema that survives JSON Schema conversion, for example by unioning instead of preprocessing:
Any approach that leaves
nextThoughtNeededin the emittedrequiredlist works. Happy to send a PR if you would like one.How this was found
Found by an automated MCP server test-generation tool that derives cases from the declared
inputSchema. The happy-path case it generated used exactly the declared required fields, which is why the mismatch surfaced immediately.