-
Notifications
You must be signed in to change notification settings - Fork 9.7k
Description
Problem
Multiple MCP clients (Claude Code with Opus 4.6/Sonnet 4.5, Augment.AI, and others) intermittently send thoughtNumber, totalThoughts, and nextThoughtNeeded as strings instead of native JSON numbers/booleans. For example, "1" instead of 1, or "true" instead of true.
This causes a Zod validation error:
Invalid arguments for tool sequentialthinking:
"expected": "number", "received": "string" (thoughtNumber)
"expected": "number", "received": "string" (totalThoughts)
"expected": "boolean", "received": "string" (nextThoughtNeeded)
This is the same issue reported in #2792, with a fix proposed (but not merged) in #2812.
Root cause
The behavior is non-deterministic on the LLM side — the same model in the same session may send correct types for one call and string types for the next. This has been independently confirmed by multiple users across different clients and models.
The current inputSchema uses strict Zod types:
thoughtNumber: z.number().int().min(1)
nextThoughtNeeded: z.boolean()These reject any string input, even valid ones like "1" or "true".
Proposed fix: z.coerce
Replace z.number() with z.coerce.number() and z.boolean() with z.coerce.boolean() in inputSchema only:
--- a/src/sequentialthinking/index.ts
+++ b/src/sequentialthinking/index.ts
@@ -66,13 +66,13 @@
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: z.coerce.boolean().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: z.coerce.boolean().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: z.coerce.boolean().optional().describe("If more thoughts are needed")
},Why z.coerce is the right approach
| Approach | Lines changed | Drawbacks |
|---|---|---|
| Node.js wrapper (stdin proxy) | ~100 | Extra process, fragile JSON parsing, maintenance burden |
Manual sanitizeNumericParam() (#2812) |
~30 | Duplicates Zod's built-in capability, needs tests for edge cases |
z.coerce (this proposal) |
9 | None — uses Zod's native type coercion |
z.coerce.number() is a first-class Zod API (docs) that:
- Accepts both
1(number) and"1"(string) → coerces to1 - Rejects
"abc"→ validation error (same as before) - Preserves
.int().min(1)chain — all downstream validators still apply - Requires zero additional code — just a prefix change
z.coerce.boolean() similarly:
- Accepts both
trueand"true"→ coerces totrue - Works with the existing
.optional()chain
What stays unchanged
outputSchema— remains strict (z.number(),z.boolean()), since the server controls its own output types- All existing validation constraints (
.int(),.min(1),.optional()) — fully preserved - No new dependencies —
z.coercehas been available since Zod 3.20
Tested with
- Claude Code (Opus 4.6) on Windows 11 — previously failing, now works reliably
- Applied as a local patch to
dist/index.js(npm v2025.12.18), confirmed working across multiple sessions
Related
- Claude Code Sonnet 4.5 randomly crash with sequentialthinking (MCP) #2792 — Original bug report (open)
- fix(sequential-thinking): Add input sanitization for numeric parameters #2812 — Previous fix attempt via manual sanitization (closed without merge)
I would be happy to submit a PR with this change and corresponding tests if the maintainers are interested. Thank you for your work on this server!