Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 2 additions & 133 deletions src/sequentialthinking/__tests__/lib.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,107 +22,8 @@ describe('SequentialThinkingServer', () => {
server = new SequentialThinkingServer();
});

describe('processThought - validation', () => {
it('should reject input with missing thought', () => {
const input = {
thoughtNumber: 1,
totalThoughts: 3,
nextThoughtNeeded: true
};

const result = server.processThought(input);
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Invalid thought');
});

it('should reject input with non-string thought', () => {
const input = {
thought: 123,
thoughtNumber: 1,
totalThoughts: 3,
nextThoughtNeeded: true
};

const result = server.processThought(input);
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Invalid thought');
});

it('should reject input with missing thoughtNumber', () => {
const input = {
thought: 'Test thought',
totalThoughts: 3,
nextThoughtNeeded: true
};

const result = server.processThought(input);
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Invalid thoughtNumber');
});

it('should reject input with non-number thoughtNumber', () => {
const input = {
thought: 'Test thought',
thoughtNumber: '1',
totalThoughts: 3,
nextThoughtNeeded: true
};

const result = server.processThought(input);
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Invalid thoughtNumber');
});

it('should reject input with missing totalThoughts', () => {
const input = {
thought: 'Test thought',
thoughtNumber: 1,
nextThoughtNeeded: true
};

const result = server.processThought(input);
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Invalid totalThoughts');
});

it('should reject input with non-number totalThoughts', () => {
const input = {
thought: 'Test thought',
thoughtNumber: 1,
totalThoughts: '3',
nextThoughtNeeded: true
};

const result = server.processThought(input);
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Invalid totalThoughts');
});

it('should reject input with missing nextThoughtNeeded', () => {
const input = {
thought: 'Test thought',
thoughtNumber: 1,
totalThoughts: 3
};

const result = server.processThought(input);
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Invalid nextThoughtNeeded');
});

it('should reject input with non-boolean nextThoughtNeeded', () => {
const input = {
thought: 'Test thought',
thoughtNumber: 1,
totalThoughts: 3,
nextThoughtNeeded: 'true'
};

const result = server.processThought(input);
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Invalid nextThoughtNeeded');
});
});
// Note: Input validation tests removed - validation now happens at the tool
// registration layer via Zod schemas before processThought is called

describe('processThought - valid inputs', () => {
it('should accept valid basic thought', () => {
Expand Down Expand Up @@ -275,19 +176,6 @@ describe('SequentialThinkingServer', () => {
});

describe('processThought - edge cases', () => {
it('should reject empty thought string', () => {
const input = {
thought: '',
thoughtNumber: 1,
totalThoughts: 1,
nextThoughtNeeded: false
};

const result = server.processThought(input);
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Invalid thought');
});

it('should handle very long thought strings', () => {
const input = {
thought: 'a'.repeat(10000),
Expand Down Expand Up @@ -349,25 +237,6 @@ describe('SequentialThinkingServer', () => {
expect(result.content[0]).toHaveProperty('text');
});

it('should return correct error structure on failure', () => {
const input = {
thought: 'Test',
thoughtNumber: 1,
totalThoughts: 1
// missing nextThoughtNeeded
};

const result = server.processThought(input);

expect(result).toHaveProperty('isError', true);
expect(result).toHaveProperty('content');
expect(Array.isArray(result.content)).toBe(true);

const errorData = JSON.parse(result.content[0].text);
expect(errorData).toHaveProperty('error');
expect(errorData).toHaveProperty('status', 'failed');
});

it('should return valid JSON in response', () => {
const input = {
thought: 'Test thought',
Expand Down
139 changes: 50 additions & 89 deletions src/sequentialthinking/index.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
#!/usr/bin/env node

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
Tool,
} from "@modelcontextprotocol/sdk/types.js";
import { z } from "zod";
import { SequentialThinkingServer } from './lib.js';

const SEQUENTIAL_THINKING_TOOL: Tool = {
name: "sequentialthinking",
description: `A detailed tool for dynamic and reflective problem-solving through thoughts.
const server = new McpServer({
name: "sequential-thinking-server",
version: "0.2.0",
});

const thinkingServer = new SequentialThinkingServer();

server.registerTool(
"sequentialthinking",
{
title: "Sequential Thinking",
description: `A detailed tool for dynamic and reflective problem-solving through thoughts.
This tool helps analyze problems through a flexible thinking process that can adapt and evolve.
Each thought can build on, question, or revise previous insights as understanding deepens.

Expand All @@ -37,13 +42,13 @@ Key features:

Parameters explained:
- thought: Your current thinking step, which can include:
* Regular analytical steps
* Revisions of previous thoughts
* Questions about previous decisions
* Realizations about needing more analysis
* Changes in approach
* Hypothesis generation
* Hypothesis verification
* Regular analytical steps
* Revisions of previous thoughts
* Questions about previous decisions
* Realizations about needing more analysis
* Changes in approach
* Hypothesis generation
* Hypothesis verification
- nextThoughtNeeded: True if you need more thinking, even if at what seemed like the end
- thoughtNumber: Current number in sequence (can go beyond initial total if needed)
- totalThoughts: Current estimate of thoughts needed (can be adjusted up/down)
Expand All @@ -65,85 +70,41 @@ You should:
9. Repeat the process until satisfied with the solution
10. Provide a single, ideally correct answer as the final output
11. Only set next_thought_needed to false when truly done and a satisfactory answer is reached`,
inputSchema: {
type: "object",
properties: {
thought: {
type: "string",
description: "Your current thinking step"
},
nextThoughtNeeded: {
type: "boolean",
description: "Whether another thought step is needed"
},
thoughtNumber: {
type: "integer",
description: "Current thought number (numeric value, e.g., 1, 2, 3)",
minimum: 1
},
totalThoughts: {
type: "integer",
description: "Estimated total thoughts needed (numeric value, e.g., 5, 10)",
minimum: 1
},
isRevision: {
type: "boolean",
description: "Whether this revises previous thinking"
},
revisesThought: {
type: "integer",
description: "Which thought is being reconsidered",
minimum: 1
},
branchFromThought: {
type: "integer",
description: "Branching point thought number",
minimum: 1
},
branchId: {
type: "string",
description: "Branch identifier"
},
needsMoreThoughts: {
type: "boolean",
description: "If more thoughts are needed"
}
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"),
branchId: z.string().optional().describe("Branch identifier"),
needsMoreThoughts: z.boolean().optional().describe("If more thoughts are needed")
},
required: ["thought", "nextThoughtNeeded", "thoughtNumber", "totalThoughts"]
}
};

const server = new Server(
{
name: "sequential-thinking-server",
version: "0.2.0",
},
{
capabilities: {
tools: {},
outputSchema: {
thoughtNumber: z.number(),
totalThoughts: z.number(),
nextThoughtNeeded: z.boolean(),
branches: z.array(z.string()),
thoughtHistoryLength: z.number()
},
}
);
},
async (args) => {
const result = thinkingServer.processThought(args);

const thinkingServer = new SequentialThinkingServer();
if (result.isError) {
return result;
}

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

server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name === "sequentialthinking") {
return thinkingServer.processThought(request.params.arguments);
return {
content: result.content,
structuredContent: parsedContent
};
}

return {
content: [{
type: "text",
text: `Unknown tool: ${request.params.name}`
}],
isError: true
};
});
);

async function runServer() {
const transport = new StdioServerTransport();
Expand Down
Loading
Loading