fix(sequential-thinking): convert to modern TypeScript SDK APIs (#3014)

* fix(sequential-thinking): convert to modern TypeScript SDK APIs

Convert the sequential-thinking server to use the modern McpServer API
instead of the low-level Server API.

Key changes:
- Replace Server with McpServer from @modelcontextprotocol/sdk/server/mcp.js
- Use registerTool() method instead of manual request handlers
- Use Zod schemas directly in inputSchema/outputSchema
- Add structuredContent to tool responses
- Fix type literals to use 'as const' assertions

The modern API provides:
- Less boilerplate code
- Better type safety with Zod
- More declarative tool registration
- Cleaner, more maintainable code

* fix: exclude test files from TypeScript build

Add exclude for test files and vitest.config.ts to tsconfig

* refactor: remove redundant validation now handled by Zod

Zod schema already validates all required fields and types. Removed
validateThoughtData() method and kept only business logic validation
(adjusting totalThoughts if needed).

* fix(sequentialthinking): add Zod validation to processThought method

The modern API migration removed manual validation from processThought(),
but tests call this method directly, bypassing the Zod validation in the
tool registration layer. This commit adds Zod validation directly in the
processThought() method to ensure validation works both when called via
MCP and when called directly (e.g., in tests).

Also improves error message formatting to match the expected error
messages in the tests.

* refactor: simplify by removing redundant validation

Since processThought() is only called through the tool registration in
production, validation always happens via Zod schemas at that layer.
Removed redundant validation logic from processThought() and updated
tests to reflect this architectural decision.

Changes:
- Remove Zod validation from processThought() method
- Accept ThoughtData type instead of unknown
- Remove 10 validation tests that are now handled at tool registration
- Add comment explaining validation approach
This commit is contained in:
adam jones
2025-11-20 19:05:30 +00:00
committed by GitHub
parent 4dc24cf349
commit 88a2ac4360
4 changed files with 78 additions and 273 deletions

View File

@@ -21,35 +21,6 @@ export class SequentialThinkingServer {
this.disableThoughtLogging = (process.env.DISABLE_THOUGHT_LOGGING || "").toLowerCase() === "true";
}
private validateThoughtData(input: unknown): ThoughtData {
const data = input as Record<string, unknown>;
if (!data.thought || typeof data.thought !== 'string') {
throw new Error('Invalid thought: must be a string');
}
if (!data.thoughtNumber || typeof data.thoughtNumber !== 'number') {
throw new Error('Invalid thoughtNumber: must be a number');
}
if (!data.totalThoughts || typeof data.totalThoughts !== 'number') {
throw new Error('Invalid totalThoughts: must be a number');
}
if (typeof data.nextThoughtNeeded !== 'boolean') {
throw new Error('Invalid nextThoughtNeeded: must be a boolean');
}
return {
thought: data.thought,
thoughtNumber: data.thoughtNumber,
totalThoughts: data.totalThoughts,
nextThoughtNeeded: data.nextThoughtNeeded,
isRevision: data.isRevision as boolean | undefined,
revisesThought: data.revisesThought as number | undefined,
branchFromThought: data.branchFromThought as number | undefined,
branchId: data.branchId as string | undefined,
needsMoreThoughts: data.needsMoreThoughts as boolean | undefined,
};
}
private formatThought(thoughtData: ThoughtData): string {
const { thoughtNumber, totalThoughts, thought, isRevision, revisesThought, branchFromThought, branchId } = thoughtData;
@@ -78,35 +49,35 @@ export class SequentialThinkingServer {
${border}`;
}
public processThought(input: unknown): { content: Array<{ type: string; text: string }>; isError?: boolean } {
public processThought(input: ThoughtData): { content: Array<{ type: "text"; text: string }>; isError?: boolean } {
try {
const validatedInput = this.validateThoughtData(input);
if (validatedInput.thoughtNumber > validatedInput.totalThoughts) {
validatedInput.totalThoughts = validatedInput.thoughtNumber;
// Validation happens at the tool registration layer via Zod
// Adjust totalThoughts if thoughtNumber exceeds it
if (input.thoughtNumber > input.totalThoughts) {
input.totalThoughts = input.thoughtNumber;
}
this.thoughtHistory.push(validatedInput);
this.thoughtHistory.push(input);
if (validatedInput.branchFromThought && validatedInput.branchId) {
if (!this.branches[validatedInput.branchId]) {
this.branches[validatedInput.branchId] = [];
if (input.branchFromThought && input.branchId) {
if (!this.branches[input.branchId]) {
this.branches[input.branchId] = [];
}
this.branches[validatedInput.branchId].push(validatedInput);
this.branches[input.branchId].push(input);
}
if (!this.disableThoughtLogging) {
const formattedThought = this.formatThought(validatedInput);
const formattedThought = this.formatThought(input);
console.error(formattedThought);
}
return {
content: [{
type: "text",
type: "text" as const,
text: JSON.stringify({
thoughtNumber: validatedInput.thoughtNumber,
totalThoughts: validatedInput.totalThoughts,
nextThoughtNeeded: validatedInput.nextThoughtNeeded,
thoughtNumber: input.thoughtNumber,
totalThoughts: input.totalThoughts,
nextThoughtNeeded: input.nextThoughtNeeded,
branches: Object.keys(this.branches),
thoughtHistoryLength: this.thoughtHistory.length
}, null, 2)
@@ -115,7 +86,7 @@ export class SequentialThinkingServer {
} catch (error) {
return {
content: [{
type: "text",
type: "text" as const,
text: JSON.stringify({
error: error instanceof Error ? error.message : String(error),
status: 'failed'