Skip to content

Commit 88a2ac4

Browse files
authored
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
1 parent 4dc24cf commit 88a2ac4

File tree

4 files changed

+76
-271
lines changed

4 files changed

+76
-271
lines changed

src/sequentialthinking/__tests__/lib.test.ts

Lines changed: 2 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -22,107 +22,8 @@ describe('SequentialThinkingServer', () => {
2222
server = new SequentialThinkingServer();
2323
});
2424

25-
describe('processThought - validation', () => {
26-
it('should reject input with missing thought', () => {
27-
const input = {
28-
thoughtNumber: 1,
29-
totalThoughts: 3,
30-
nextThoughtNeeded: true
31-
};
32-
33-
const result = server.processThought(input);
34-
expect(result.isError).toBe(true);
35-
expect(result.content[0].text).toContain('Invalid thought');
36-
});
37-
38-
it('should reject input with non-string thought', () => {
39-
const input = {
40-
thought: 123,
41-
thoughtNumber: 1,
42-
totalThoughts: 3,
43-
nextThoughtNeeded: true
44-
};
45-
46-
const result = server.processThought(input);
47-
expect(result.isError).toBe(true);
48-
expect(result.content[0].text).toContain('Invalid thought');
49-
});
50-
51-
it('should reject input with missing thoughtNumber', () => {
52-
const input = {
53-
thought: 'Test thought',
54-
totalThoughts: 3,
55-
nextThoughtNeeded: true
56-
};
57-
58-
const result = server.processThought(input);
59-
expect(result.isError).toBe(true);
60-
expect(result.content[0].text).toContain('Invalid thoughtNumber');
61-
});
62-
63-
it('should reject input with non-number thoughtNumber', () => {
64-
const input = {
65-
thought: 'Test thought',
66-
thoughtNumber: '1',
67-
totalThoughts: 3,
68-
nextThoughtNeeded: true
69-
};
70-
71-
const result = server.processThought(input);
72-
expect(result.isError).toBe(true);
73-
expect(result.content[0].text).toContain('Invalid thoughtNumber');
74-
});
75-
76-
it('should reject input with missing totalThoughts', () => {
77-
const input = {
78-
thought: 'Test thought',
79-
thoughtNumber: 1,
80-
nextThoughtNeeded: true
81-
};
82-
83-
const result = server.processThought(input);
84-
expect(result.isError).toBe(true);
85-
expect(result.content[0].text).toContain('Invalid totalThoughts');
86-
});
87-
88-
it('should reject input with non-number totalThoughts', () => {
89-
const input = {
90-
thought: 'Test thought',
91-
thoughtNumber: 1,
92-
totalThoughts: '3',
93-
nextThoughtNeeded: true
94-
};
95-
96-
const result = server.processThought(input);
97-
expect(result.isError).toBe(true);
98-
expect(result.content[0].text).toContain('Invalid totalThoughts');
99-
});
100-
101-
it('should reject input with missing nextThoughtNeeded', () => {
102-
const input = {
103-
thought: 'Test thought',
104-
thoughtNumber: 1,
105-
totalThoughts: 3
106-
};
107-
108-
const result = server.processThought(input);
109-
expect(result.isError).toBe(true);
110-
expect(result.content[0].text).toContain('Invalid nextThoughtNeeded');
111-
});
112-
113-
it('should reject input with non-boolean nextThoughtNeeded', () => {
114-
const input = {
115-
thought: 'Test thought',
116-
thoughtNumber: 1,
117-
totalThoughts: 3,
118-
nextThoughtNeeded: 'true'
119-
};
120-
121-
const result = server.processThought(input);
122-
expect(result.isError).toBe(true);
123-
expect(result.content[0].text).toContain('Invalid nextThoughtNeeded');
124-
});
125-
});
25+
// Note: Input validation tests removed - validation now happens at the tool
26+
// registration layer via Zod schemas before processThought is called
12627

12728
describe('processThought - valid inputs', () => {
12829
it('should accept valid basic thought', () => {
@@ -275,19 +176,6 @@ describe('SequentialThinkingServer', () => {
275176
});
276177

277178
describe('processThought - edge cases', () => {
278-
it('should reject empty thought string', () => {
279-
const input = {
280-
thought: '',
281-
thoughtNumber: 1,
282-
totalThoughts: 1,
283-
nextThoughtNeeded: false
284-
};
285-
286-
const result = server.processThought(input);
287-
expect(result.isError).toBe(true);
288-
expect(result.content[0].text).toContain('Invalid thought');
289-
});
290-
291179
it('should handle very long thought strings', () => {
292180
const input = {
293181
thought: 'a'.repeat(10000),
@@ -349,25 +237,6 @@ describe('SequentialThinkingServer', () => {
349237
expect(result.content[0]).toHaveProperty('text');
350238
});
351239

352-
it('should return correct error structure on failure', () => {
353-
const input = {
354-
thought: 'Test',
355-
thoughtNumber: 1,
356-
totalThoughts: 1
357-
// missing nextThoughtNeeded
358-
};
359-
360-
const result = server.processThought(input);
361-
362-
expect(result).toHaveProperty('isError', true);
363-
expect(result).toHaveProperty('content');
364-
expect(Array.isArray(result.content)).toBe(true);
365-
366-
const errorData = JSON.parse(result.content[0].text);
367-
expect(errorData).toHaveProperty('error');
368-
expect(errorData).toHaveProperty('status', 'failed');
369-
});
370-
371240
it('should return valid JSON in response', () => {
372241
const input = {
373242
thought: 'Test thought',

src/sequentialthinking/index.ts

Lines changed: 50 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
11
#!/usr/bin/env node
22

3-
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
44
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5-
import {
6-
CallToolRequestSchema,
7-
ListToolsRequestSchema,
8-
Tool,
9-
} from "@modelcontextprotocol/sdk/types.js";
5+
import { z } from "zod";
106
import { SequentialThinkingServer } from './lib.js';
117

12-
const SEQUENTIAL_THINKING_TOOL: Tool = {
13-
name: "sequentialthinking",
14-
description: `A detailed tool for dynamic and reflective problem-solving through thoughts.
8+
const server = new McpServer({
9+
name: "sequential-thinking-server",
10+
version: "0.2.0",
11+
});
12+
13+
const thinkingServer = new SequentialThinkingServer();
14+
15+
server.registerTool(
16+
"sequentialthinking",
17+
{
18+
title: "Sequential Thinking",
19+
description: `A detailed tool for dynamic and reflective problem-solving through thoughts.
1520
This tool helps analyze problems through a flexible thinking process that can adapt and evolve.
1621
Each thought can build on, question, or revise previous insights as understanding deepens.
1722
@@ -37,13 +42,13 @@ Key features:
3742
3843
Parameters explained:
3944
- thought: Your current thinking step, which can include:
40-
* Regular analytical steps
41-
* Revisions of previous thoughts
42-
* Questions about previous decisions
43-
* Realizations about needing more analysis
44-
* Changes in approach
45-
* Hypothesis generation
46-
* Hypothesis verification
45+
* Regular analytical steps
46+
* Revisions of previous thoughts
47+
* Questions about previous decisions
48+
* Realizations about needing more analysis
49+
* Changes in approach
50+
* Hypothesis generation
51+
* Hypothesis verification
4752
- nextThoughtNeeded: True if you need more thinking, even if at what seemed like the end
4853
- thoughtNumber: Current number in sequence (can go beyond initial total if needed)
4954
- totalThoughts: Current estimate of thoughts needed (can be adjusted up/down)
@@ -65,85 +70,41 @@ You should:
6570
9. Repeat the process until satisfied with the solution
6671
10. Provide a single, ideally correct answer as the final output
6772
11. Only set next_thought_needed to false when truly done and a satisfactory answer is reached`,
68-
inputSchema: {
69-
type: "object",
70-
properties: {
71-
thought: {
72-
type: "string",
73-
description: "Your current thinking step"
74-
},
75-
nextThoughtNeeded: {
76-
type: "boolean",
77-
description: "Whether another thought step is needed"
78-
},
79-
thoughtNumber: {
80-
type: "integer",
81-
description: "Current thought number (numeric value, e.g., 1, 2, 3)",
82-
minimum: 1
83-
},
84-
totalThoughts: {
85-
type: "integer",
86-
description: "Estimated total thoughts needed (numeric value, e.g., 5, 10)",
87-
minimum: 1
88-
},
89-
isRevision: {
90-
type: "boolean",
91-
description: "Whether this revises previous thinking"
92-
},
93-
revisesThought: {
94-
type: "integer",
95-
description: "Which thought is being reconsidered",
96-
minimum: 1
97-
},
98-
branchFromThought: {
99-
type: "integer",
100-
description: "Branching point thought number",
101-
minimum: 1
102-
},
103-
branchId: {
104-
type: "string",
105-
description: "Branch identifier"
106-
},
107-
needsMoreThoughts: {
108-
type: "boolean",
109-
description: "If more thoughts are needed"
110-
}
73+
inputSchema: {
74+
thought: z.string().describe("Your current thinking step"),
75+
nextThoughtNeeded: z.boolean().describe("Whether another thought step is needed"),
76+
thoughtNumber: z.number().int().min(1).describe("Current thought number (numeric value, e.g., 1, 2, 3)"),
77+
totalThoughts: z.number().int().min(1).describe("Estimated total thoughts needed (numeric value, e.g., 5, 10)"),
78+
isRevision: z.boolean().optional().describe("Whether this revises previous thinking"),
79+
revisesThought: z.number().int().min(1).optional().describe("Which thought is being reconsidered"),
80+
branchFromThought: z.number().int().min(1).optional().describe("Branching point thought number"),
81+
branchId: z.string().optional().describe("Branch identifier"),
82+
needsMoreThoughts: z.boolean().optional().describe("If more thoughts are needed")
11183
},
112-
required: ["thought", "nextThoughtNeeded", "thoughtNumber", "totalThoughts"]
113-
}
114-
};
115-
116-
const server = new Server(
117-
{
118-
name: "sequential-thinking-server",
119-
version: "0.2.0",
120-
},
121-
{
122-
capabilities: {
123-
tools: {},
84+
outputSchema: {
85+
thoughtNumber: z.number(),
86+
totalThoughts: z.number(),
87+
nextThoughtNeeded: z.boolean(),
88+
branches: z.array(z.string()),
89+
thoughtHistoryLength: z.number()
12490
},
125-
}
126-
);
91+
},
92+
async (args) => {
93+
const result = thinkingServer.processThought(args);
12794

128-
const thinkingServer = new SequentialThinkingServer();
95+
if (result.isError) {
96+
return result;
97+
}
12998

130-
server.setRequestHandler(ListToolsRequestSchema, async () => ({
131-
tools: [SEQUENTIAL_THINKING_TOOL],
132-
}));
99+
// Parse the JSON response to get structured content
100+
const parsedContent = JSON.parse(result.content[0].text);
133101

134-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
135-
if (request.params.name === "sequentialthinking") {
136-
return thinkingServer.processThought(request.params.arguments);
102+
return {
103+
content: result.content,
104+
structuredContent: parsedContent
105+
};
137106
}
138-
139-
return {
140-
content: [{
141-
type: "text",
142-
text: `Unknown tool: ${request.params.name}`
143-
}],
144-
isError: true
145-
};
146-
});
107+
);
147108

148109
async function runServer() {
149110
const transport = new StdioServerTransport();

0 commit comments

Comments
 (0)