@gotza02/sequential-thinking 2026.1.19 → 2026.1.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -1
- package/dist/graph.js +37 -18
- package/dist/index.js +23 -4
- package/dist/lib.js +47 -13
- package/dist/server.test.js +22 -12
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -14,6 +14,16 @@ An MCP server implementation that provides a tool for dynamic and reflective pro
|
|
|
14
14
|
- **Self-Reflexion**: Review and correct previous thoughts (`reflexion`) to improve accuracy.
|
|
15
15
|
- **Dynamic Adjustment**: Adjust the total number of thoughts dynamically as understanding deepens.
|
|
16
16
|
|
|
17
|
+
## System Instruction for AI
|
|
18
|
+
|
|
19
|
+
When connected to this server, you should follow these guidelines to maximize problem-solving effectiveness:
|
|
20
|
+
|
|
21
|
+
1. **Iterative Thinking**: Use the `sequentialthinking` tool for every complex task. Do not try to provide a final answer immediately. Instead, break the problem into steps, generate hypotheses, and evaluate them.
|
|
22
|
+
2. **Context Discovery**: Before modifying code, always use `build_project_graph` followed by `get_project_graph_summary` to understand the project architecture. Use `get_file_relationships` to see how specific files are connected.
|
|
23
|
+
3. **Fact Verification**: Use `web_search` to verify current facts, library versions, or documentation. Use `fetch` to retrieve raw data from external sources when needed.
|
|
24
|
+
4. **Local Execution**: Use `shell_execute` to run tests, linters, or build commands to verify your changes. Always read files using `read_file` before attempting to write or modify them.
|
|
25
|
+
5. **Persistence**: The thinking process is saved automatically. You can resume previous sessions by reviewing the `thoughts_history.json` file if needed.
|
|
26
|
+
|
|
17
27
|
## Tool
|
|
18
28
|
|
|
19
29
|
### sequentialthinking
|
|
@@ -31,7 +41,10 @@ Search the web using Brave or Exa APIs.
|
|
|
31
41
|
- `provider` (enum, optional): 'brave', 'exa', or 'google'.
|
|
32
42
|
|
|
33
43
|
**Configuration:**
|
|
34
|
-
Requires
|
|
44
|
+
Requires one of the following environment variable sets:
|
|
45
|
+
- `BRAVE_API_KEY` (for Brave Search)
|
|
46
|
+
- `EXA_API_KEY` (for Exa Search)
|
|
47
|
+
- `GOOGLE_API_KEY` and `GOOGLE_CX` (for Google Custom Search)
|
|
35
48
|
|
|
36
49
|
### fetch
|
|
37
50
|
|
|
@@ -127,6 +140,23 @@ npm run build
|
|
|
127
140
|
npm test
|
|
128
141
|
```
|
|
129
142
|
|
|
143
|
+
## Recent Updates (v2026.1.21)
|
|
144
|
+
|
|
145
|
+
- **Performance & Accuracy**:
|
|
146
|
+
- Replaced Regex-based code analysis with **TypeScript Compiler API (AST)** for 100% accurate import/export detection.
|
|
147
|
+
- Improved `web_search` robustness and error handling.
|
|
148
|
+
- **Persistence**:
|
|
149
|
+
- Implemented **File-based Persistence** for the thinking process. Your thoughts are now saved to `thoughts_history.json` automatically.
|
|
150
|
+
- Switched to **Asynchronous File I/O** to prevent server blocking.
|
|
151
|
+
- **Bug Fixes**:
|
|
152
|
+
- Fixed duplicate import entries in the project graph.
|
|
153
|
+
- Resolved memory growth issues in long-running sessions.
|
|
154
|
+
|
|
155
|
+
## Recent Updates (v2026.1.20)
|
|
156
|
+
|
|
157
|
+
- **New Features**:
|
|
158
|
+
- Added support for **Google Custom Search** in the `web_search` tool. (Requires `GOOGLE_API_KEY` and `GOOGLE_CX`).
|
|
159
|
+
|
|
130
160
|
## Recent Updates (v2026.1.18)
|
|
131
161
|
|
|
132
162
|
- **Bug Fixes**:
|
package/dist/graph.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as fs from 'fs/promises';
|
|
2
2
|
import * as path from 'path';
|
|
3
|
+
import ts from 'typescript';
|
|
3
4
|
export class ProjectKnowledgeGraph {
|
|
4
5
|
nodes = new Map();
|
|
5
6
|
rootDir = '';
|
|
@@ -45,30 +46,48 @@ export class ProjectKnowledgeGraph {
|
|
|
45
46
|
}
|
|
46
47
|
async parseFile(filePath) {
|
|
47
48
|
try {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
49
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
50
|
+
const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
|
|
51
|
+
const imports = [];
|
|
52
|
+
const visit = (node) => {
|
|
53
|
+
// 1. Static imports: import ... from '...'
|
|
54
|
+
if (ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) {
|
|
55
|
+
if (node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) {
|
|
56
|
+
imports.push(node.moduleSpecifier.text);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// 2. Dynamic imports: import('...')
|
|
60
|
+
else if (ts.isCallExpression(node)) {
|
|
61
|
+
if (node.expression.kind === ts.SyntaxKind.ImportKeyword && node.arguments.length > 0) {
|
|
62
|
+
const arg = node.arguments[0];
|
|
63
|
+
if (ts.isStringLiteral(arg)) {
|
|
64
|
+
imports.push(arg.text);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// 3. CommonJS: require('...')
|
|
68
|
+
else if (ts.isIdentifier(node.expression) && node.expression.text === 'require' && node.arguments.length > 0) {
|
|
69
|
+
const arg = node.arguments[0];
|
|
70
|
+
if (ts.isStringLiteral(arg)) {
|
|
71
|
+
imports.push(arg.text);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
ts.forEachChild(node, visit);
|
|
76
|
+
};
|
|
77
|
+
visit(sourceFile);
|
|
62
78
|
const currentNode = this.nodes.get(filePath);
|
|
63
79
|
if (!currentNode)
|
|
64
80
|
return;
|
|
65
|
-
for (const
|
|
66
|
-
const importPath = match[1];
|
|
81
|
+
for (const importPath of imports) {
|
|
67
82
|
if (importPath.startsWith('.')) {
|
|
68
83
|
const resolvedPath = await this.resolvePath(path.dirname(filePath), importPath);
|
|
69
84
|
if (resolvedPath && this.nodes.has(resolvedPath)) {
|
|
70
|
-
currentNode.imports.
|
|
71
|
-
|
|
85
|
+
if (!currentNode.imports.includes(resolvedPath)) {
|
|
86
|
+
currentNode.imports.push(resolvedPath);
|
|
87
|
+
}
|
|
88
|
+
if (!this.nodes.get(resolvedPath)?.importedBy.includes(filePath)) {
|
|
89
|
+
this.nodes.get(resolvedPath)?.importedBy.push(filePath);
|
|
90
|
+
}
|
|
72
91
|
}
|
|
73
92
|
}
|
|
74
93
|
}
|
package/dist/index.js
CHANGED
|
@@ -12,7 +12,7 @@ const server = new McpServer({
|
|
|
12
12
|
name: "sequential-thinking-server",
|
|
13
13
|
version: "2026.1.18",
|
|
14
14
|
});
|
|
15
|
-
const thinkingServer = new SequentialThinkingServer();
|
|
15
|
+
const thinkingServer = new SequentialThinkingServer(process.env.THOUGHTS_STORAGE_PATH || 'thoughts_history.json');
|
|
16
16
|
const knowledgeGraph = new ProjectKnowledgeGraph();
|
|
17
17
|
// --- Sequential Thinking Tool ---
|
|
18
18
|
server.tool("sequentialthinking", `A detailed tool for dynamic and reflective problem-solving through thoughts.
|
|
@@ -92,7 +92,7 @@ You should:
|
|
|
92
92
|
options: z.array(z.string()).optional().describe("List of options generated"),
|
|
93
93
|
selectedOption: z.string().optional().describe("The option selected")
|
|
94
94
|
}, async (args) => {
|
|
95
|
-
const result = thinkingServer.processThought(args);
|
|
95
|
+
const result = await thinkingServer.processThought(args);
|
|
96
96
|
return {
|
|
97
97
|
content: result.content,
|
|
98
98
|
isError: result.isError
|
|
@@ -105,15 +105,17 @@ server.tool("web_search", "Search the web using Brave or Exa APIs (requires API
|
|
|
105
105
|
provider: z.enum(['brave', 'exa', 'google']).optional().describe("Preferred search provider")
|
|
106
106
|
}, async ({ query, provider }) => {
|
|
107
107
|
try {
|
|
108
|
-
// Priority: User Preference > Brave > Exa
|
|
108
|
+
// Priority: User Preference > Brave > Exa > Google
|
|
109
109
|
let selectedProvider = provider;
|
|
110
110
|
if (!selectedProvider) {
|
|
111
111
|
if (process.env.BRAVE_API_KEY)
|
|
112
112
|
selectedProvider = 'brave';
|
|
113
113
|
else if (process.env.EXA_API_KEY)
|
|
114
114
|
selectedProvider = 'exa';
|
|
115
|
+
else if (process.env.GOOGLE_API_KEY)
|
|
116
|
+
selectedProvider = 'google';
|
|
115
117
|
else
|
|
116
|
-
return { content: [{ type: "text", text: "Error: No search provider configured. Please set BRAVE_API_KEY or
|
|
118
|
+
return { content: [{ type: "text", text: "Error: No search provider configured. Please set BRAVE_API_KEY, EXA_API_KEY, or GOOGLE_API_KEY." }], isError: true };
|
|
117
119
|
}
|
|
118
120
|
if (selectedProvider === 'brave') {
|
|
119
121
|
if (!process.env.BRAVE_API_KEY)
|
|
@@ -142,6 +144,23 @@ server.tool("web_search", "Search the web using Brave or Exa APIs (requires API
|
|
|
142
144
|
const data = await response.json();
|
|
143
145
|
return { content: [{ type: "text", text: JSON.stringify(data.results || data, null, 2) }] };
|
|
144
146
|
}
|
|
147
|
+
if (selectedProvider === 'google') {
|
|
148
|
+
if (!process.env.GOOGLE_API_KEY)
|
|
149
|
+
throw new Error("GOOGLE_API_KEY not found");
|
|
150
|
+
if (!process.env.GOOGLE_CX)
|
|
151
|
+
throw new Error("GOOGLE_CX (Search Engine ID) not found");
|
|
152
|
+
const response = await fetch(`https://www.googleapis.com/customsearch/v1?key=${process.env.GOOGLE_API_KEY}&cx=${process.env.GOOGLE_CX}&q=${encodeURIComponent(query)}&num=5`);
|
|
153
|
+
if (!response.ok)
|
|
154
|
+
throw new Error(`Google API error: ${response.statusText}`);
|
|
155
|
+
const data = await response.json();
|
|
156
|
+
// Extract relevant fields to keep output clean
|
|
157
|
+
const results = data.items?.map((item) => ({
|
|
158
|
+
title: item.title,
|
|
159
|
+
link: item.link,
|
|
160
|
+
snippet: item.snippet
|
|
161
|
+
})) || [];
|
|
162
|
+
return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] };
|
|
163
|
+
}
|
|
145
164
|
return { content: [{ type: "text", text: "Error: Unsupported or unconfigured provider." }], isError: true };
|
|
146
165
|
}
|
|
147
166
|
catch (error) {
|
package/dist/lib.js
CHANGED
|
@@ -1,10 +1,53 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
+
import * as fs from 'fs/promises';
|
|
3
|
+
import { existsSync, readFileSync } from 'fs';
|
|
4
|
+
import * as path from 'path';
|
|
2
5
|
export class SequentialThinkingServer {
|
|
3
6
|
thoughtHistory = [];
|
|
4
7
|
branches = {};
|
|
5
8
|
disableThoughtLogging;
|
|
6
|
-
|
|
9
|
+
storagePath;
|
|
10
|
+
constructor(storagePath = 'thoughts_history.json') {
|
|
7
11
|
this.disableThoughtLogging = (process.env.DISABLE_THOUGHT_LOGGING || "").toLowerCase() === "true";
|
|
12
|
+
this.storagePath = path.resolve(storagePath);
|
|
13
|
+
this.loadHistory();
|
|
14
|
+
}
|
|
15
|
+
loadHistory() {
|
|
16
|
+
try {
|
|
17
|
+
if (existsSync(this.storagePath)) {
|
|
18
|
+
const data = readFileSync(this.storagePath, 'utf-8');
|
|
19
|
+
const history = JSON.parse(data);
|
|
20
|
+
if (Array.isArray(history)) {
|
|
21
|
+
this.thoughtHistory = []; // Reset to avoid duplicates
|
|
22
|
+
this.branches = {};
|
|
23
|
+
history.forEach(thought => this.addToMemory(thought));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
console.error(`Error loading history from ${this.storagePath}:`, error);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
async saveHistory() {
|
|
32
|
+
try {
|
|
33
|
+
await fs.writeFile(this.storagePath, JSON.stringify(this.thoughtHistory, null, 2), 'utf-8');
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
console.error(`Error saving history to ${this.storagePath}:`, error);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
addToMemory(input) {
|
|
40
|
+
if (input.thoughtNumber > input.totalThoughts) {
|
|
41
|
+
input.totalThoughts = input.thoughtNumber;
|
|
42
|
+
}
|
|
43
|
+
this.thoughtHistory.push(input);
|
|
44
|
+
if (input.branchFromThought && input.branchId) {
|
|
45
|
+
const branchKey = `${input.branchFromThought}-${input.branchId}`;
|
|
46
|
+
if (!this.branches[branchKey]) {
|
|
47
|
+
this.branches[branchKey] = [];
|
|
48
|
+
}
|
|
49
|
+
this.branches[branchKey].push(input);
|
|
50
|
+
}
|
|
8
51
|
}
|
|
9
52
|
formatThought(thoughtData) {
|
|
10
53
|
const { thoughtNumber, totalThoughts, thought, isRevision, revisesThought, branchFromThought, branchId, thoughtType, score, options, selectedOption } = thoughtData;
|
|
@@ -52,19 +95,10 @@ export class SequentialThinkingServer {
|
|
|
52
95
|
│ ${thought.padEnd(borderLength - 2)} │${extraContent}
|
|
53
96
|
└${border}┘`;
|
|
54
97
|
}
|
|
55
|
-
processThought(input) {
|
|
98
|
+
async processThought(input) {
|
|
56
99
|
try {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
this.thoughtHistory.push(input);
|
|
61
|
-
if (input.branchFromThought && input.branchId) {
|
|
62
|
-
const branchKey = `${input.branchFromThought}-${input.branchId}`;
|
|
63
|
-
if (!this.branches[branchKey]) {
|
|
64
|
-
this.branches[branchKey] = [];
|
|
65
|
-
}
|
|
66
|
-
this.branches[branchKey].push(input);
|
|
67
|
-
}
|
|
100
|
+
this.addToMemory(input);
|
|
101
|
+
await this.saveHistory();
|
|
68
102
|
if (!this.disableThoughtLogging) {
|
|
69
103
|
const formattedThought = this.formatThought(input);
|
|
70
104
|
console.error(formattedThought);
|
package/dist/server.test.js
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from 'vitest';
|
|
1
|
+
import { describe, it, expect, beforeEach, afterAll } from 'vitest';
|
|
2
2
|
import { SequentialThinkingServer } from './lib.js';
|
|
3
|
+
import * as fs from 'fs';
|
|
3
4
|
describe('SequentialThinkingServer', () => {
|
|
4
5
|
let server;
|
|
6
|
+
const testStoragePath = 'test_thoughts.json';
|
|
5
7
|
beforeEach(() => {
|
|
6
|
-
|
|
8
|
+
if (fs.existsSync(testStoragePath)) {
|
|
9
|
+
fs.unlinkSync(testStoragePath);
|
|
10
|
+
}
|
|
11
|
+
server = new SequentialThinkingServer(testStoragePath);
|
|
7
12
|
});
|
|
8
|
-
|
|
13
|
+
afterAll(() => {
|
|
14
|
+
if (fs.existsSync(testStoragePath)) {
|
|
15
|
+
fs.unlinkSync(testStoragePath);
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
it('should process a basic linear thought', async () => {
|
|
9
19
|
const input = {
|
|
10
20
|
thought: "First step",
|
|
11
21
|
thoughtNumber: 1,
|
|
@@ -13,15 +23,15 @@ describe('SequentialThinkingServer', () => {
|
|
|
13
23
|
nextThoughtNeeded: true,
|
|
14
24
|
thoughtType: 'analysis'
|
|
15
25
|
};
|
|
16
|
-
const result = server.processThought(input);
|
|
26
|
+
const result = await server.processThought(input);
|
|
17
27
|
expect(result.isError).toBeUndefined();
|
|
18
28
|
const content = JSON.parse(result.content[0].text);
|
|
19
29
|
expect(content.thoughtNumber).toBe(1);
|
|
20
30
|
expect(content.thoughtHistoryLength).toBe(1);
|
|
21
31
|
});
|
|
22
|
-
it('should handle branching correctly', () => {
|
|
32
|
+
it('should handle branching correctly', async () => {
|
|
23
33
|
// Initial thought
|
|
24
|
-
server.processThought({
|
|
34
|
+
await server.processThought({
|
|
25
35
|
thought: "Root thought",
|
|
26
36
|
thoughtNumber: 1,
|
|
27
37
|
totalThoughts: 3,
|
|
@@ -37,7 +47,7 @@ describe('SequentialThinkingServer', () => {
|
|
|
37
47
|
branchId: "branch-A",
|
|
38
48
|
thoughtType: 'generation'
|
|
39
49
|
};
|
|
40
|
-
const result1 = server.processThought(branch1Input);
|
|
50
|
+
const result1 = await server.processThought(branch1Input);
|
|
41
51
|
const content1 = JSON.parse(result1.content[0].text);
|
|
42
52
|
expect(content1.branches).toContain("1-branch-A");
|
|
43
53
|
// Branch 2
|
|
@@ -50,12 +60,12 @@ describe('SequentialThinkingServer', () => {
|
|
|
50
60
|
branchId: "branch-B",
|
|
51
61
|
thoughtType: 'generation'
|
|
52
62
|
};
|
|
53
|
-
const result2 = server.processThought(branch2Input);
|
|
63
|
+
const result2 = await server.processThought(branch2Input);
|
|
54
64
|
const content2 = JSON.parse(result2.content[0].text);
|
|
55
65
|
expect(content2.branches).toContain("1-branch-B");
|
|
56
66
|
expect(content2.branches.length).toBe(2);
|
|
57
67
|
});
|
|
58
|
-
it('should handle evaluation with scores', () => {
|
|
68
|
+
it('should handle evaluation with scores', async () => {
|
|
59
69
|
const input = {
|
|
60
70
|
thought: "Evaluating option X",
|
|
61
71
|
thoughtNumber: 3,
|
|
@@ -65,20 +75,20 @@ describe('SequentialThinkingServer', () => {
|
|
|
65
75
|
score: 8,
|
|
66
76
|
options: ['Option X', 'Option Y']
|
|
67
77
|
};
|
|
68
|
-
const result = server.processThought(input);
|
|
78
|
+
const result = await server.processThought(input);
|
|
69
79
|
expect(result.isError).toBeUndefined();
|
|
70
80
|
// Since we don't return the score in the simple JSON response (only in logs or history),
|
|
71
81
|
// we mainly check that it doesn't crash and processes correctly.
|
|
72
82
|
// If we exposed history in the response, we could check that too.
|
|
73
83
|
});
|
|
74
|
-
it('should adjust totalThoughts if thoughtNumber exceeds it', () => {
|
|
84
|
+
it('should adjust totalThoughts if thoughtNumber exceeds it', async () => {
|
|
75
85
|
const input = {
|
|
76
86
|
thought: "Unexpected long process",
|
|
77
87
|
thoughtNumber: 6,
|
|
78
88
|
totalThoughts: 5,
|
|
79
89
|
nextThoughtNeeded: true
|
|
80
90
|
};
|
|
81
|
-
const result = server.processThought(input);
|
|
91
|
+
const result = await server.processThought(input);
|
|
82
92
|
const content = JSON.parse(result.content[0].text);
|
|
83
93
|
expect(content.totalThoughts).toBe(6);
|
|
84
94
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gotza02/sequential-thinking",
|
|
3
|
-
"version": "2026.1.
|
|
3
|
+
"version": "2026.1.21",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@modelcontextprotocol/sdk": "^1.24.0",
|
|
32
32
|
"chalk": "^5.3.0",
|
|
33
|
+
"typescript": "^5.3.3",
|
|
33
34
|
"yargs": "^17.7.2"
|
|
34
35
|
},
|
|
35
36
|
"devDependencies": {
|
|
@@ -37,7 +38,6 @@
|
|
|
37
38
|
"@types/yargs": "^17.0.32",
|
|
38
39
|
"@vitest/coverage-v8": "^2.1.8",
|
|
39
40
|
"shx": "^0.3.4",
|
|
40
|
-
"typescript": "^5.3.3",
|
|
41
41
|
"vitest": "^2.1.8"
|
|
42
42
|
}
|
|
43
|
-
}
|
|
43
|
+
}
|