fix(sequential-thinking): use z.coerce for number and boolean params (#3533)

fix(sequential-thinking): use z.coerce for number and safe preprocess for boolean params

Uses z.coerce.number() for number fields and a z.preprocess() helper for boolean fields to handle string-typed parameters from LLM clients. The preprocess approach correctly handles "false" → false, avoiding the z.coerce.boolean() footgun where Boolean("false") === true.

Fixes #3428
This commit is contained in:
Niels Kaspers
2026-03-15 19:09:04 +02:00
committed by GitHub
parent c7e0c7e730
commit 1cdf806d21

View File

@@ -5,6 +5,16 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
import { z } from "zod";
import { SequentialThinkingServer } from './lib.js';
/** Safe boolean coercion that correctly handles string "false" */
const coercedBoolean = z.preprocess((val) => {
if (typeof val === "boolean") return val;
if (typeof val === "string") {
if (val.toLowerCase() === "true") return true;
if (val.toLowerCase() === "false") return false;
}
return val;
}, z.boolean());
const server = new McpServer({
name: "sequential-thinking-server",
version: "0.2.0",
@@ -72,14 +82,14 @@ You should:
11. Only set nextThoughtNeeded to false when truly done and a satisfactory answer is reached`,
inputSchema: {
thought: z.string().describe("Your current thinking step"),
nextThoughtNeeded: z.boolean().describe("Whether another thought step is needed"),
thoughtNumber: z.number().int().min(1).describe("Current thought number (numeric value, e.g., 1, 2, 3)"),
totalThoughts: z.number().int().min(1).describe("Estimated total thoughts needed (numeric value, e.g., 5, 10)"),
isRevision: z.boolean().optional().describe("Whether this revises previous thinking"),
revisesThought: z.number().int().min(1).optional().describe("Which thought is being reconsidered"),
branchFromThought: z.number().int().min(1).optional().describe("Branching point thought number"),
nextThoughtNeeded: coercedBoolean.describe("Whether another thought step is needed"),
thoughtNumber: z.coerce.number().int().min(1).describe("Current thought number (numeric value, e.g., 1, 2, 3)"),
totalThoughts: z.coerce.number().int().min(1).describe("Estimated total thoughts needed (numeric value, e.g., 5, 10)"),
isRevision: coercedBoolean.optional().describe("Whether this revises previous thinking"),
revisesThought: z.coerce.number().int().min(1).optional().describe("Which thought is being reconsidered"),
branchFromThought: z.coerce.number().int().min(1).optional().describe("Branching point thought number"),
branchId: z.string().optional().describe("Branch identifier"),
needsMoreThoughts: z.boolean().optional().describe("If more thoughts are needed")
needsMoreThoughts: coercedBoolean.optional().describe("If more thoughts are needed")
},
annotations: {
readOnlyHint: true,