@gotza02/sequential-thinking 2026.1.16
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 +132 -0
- package/dist/graph.js +120 -0
- package/dist/index.js +301 -0
- package/dist/lib.js +97 -0
- package/dist/server.test.js +85 -0
- package/package.json +43 -0
package/README.md
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# Sequential Thinking MCP Server
|
|
2
|
+
|
|
3
|
+
An MCP server implementation that provides a tool for dynamic and reflective problem-solving through a structured thinking process (Tree of Thoughts, Self-Reflexion).
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Sequential Analysis**: Break down complex problems into manageable linear steps.
|
|
8
|
+
- **Iterative Reasoning**: Think step-by-step in a structured manner, refining insights in loops.
|
|
9
|
+
- **Tree of Thoughts**: Generate and evaluate multiple options, exploring Conservative, Balanced, and Aggressive strategies.
|
|
10
|
+
- **Self-Critique**: Actively check for risks, biases, and potential errors in the thinking process.
|
|
11
|
+
- **Branch Merging**: Synthesize and combine insights from multiple divergent reasoning paths.
|
|
12
|
+
- **Hypothesis Testing**: Formulate specific hypotheses and verify them against evidence or logic.
|
|
13
|
+
- **Option Evaluation**: Score and weigh different options (`evaluation`) to make informed decisions.
|
|
14
|
+
- **Self-Reflexion**: Review and correct previous thoughts (`reflexion`) to improve accuracy.
|
|
15
|
+
- **Dynamic Adjustment**: Adjust the total number of thoughts dynamically as understanding deepens.
|
|
16
|
+
|
|
17
|
+
## Tool
|
|
18
|
+
|
|
19
|
+
### sequentialthinking
|
|
20
|
+
|
|
21
|
+
Facilitates a detailed, step-by-step thinking process for problem-solving and analysis.
|
|
22
|
+
|
|
23
|
+
**(See inputs above)**
|
|
24
|
+
|
|
25
|
+
### web_search
|
|
26
|
+
|
|
27
|
+
Search the web using Brave or Exa APIs.
|
|
28
|
+
|
|
29
|
+
**Inputs:**
|
|
30
|
+
- `query` (string): The search query.
|
|
31
|
+
- `provider` (enum, optional): 'brave', 'exa', or 'google'.
|
|
32
|
+
|
|
33
|
+
**Configuration:**
|
|
34
|
+
Requires `BRAVE_API_KEY` or `EXA_API_KEY` environment variables.
|
|
35
|
+
|
|
36
|
+
### fetch
|
|
37
|
+
|
|
38
|
+
Perform an HTTP request.
|
|
39
|
+
|
|
40
|
+
**Inputs:**
|
|
41
|
+
- `url` (string): URL to fetch.
|
|
42
|
+
- `method` (string, optional): HTTP method (GET, POST, etc.).
|
|
43
|
+
- `headers` (object, optional): Request headers.
|
|
44
|
+
- `body` (string, optional): Request body.
|
|
45
|
+
|
|
46
|
+
### shell_execute
|
|
47
|
+
|
|
48
|
+
Execute a shell command. **Use with caution.**
|
|
49
|
+
|
|
50
|
+
**Inputs:**
|
|
51
|
+
- `command` (string): The command to run.
|
|
52
|
+
|
|
53
|
+
### read_file / write_file
|
|
54
|
+
|
|
55
|
+
Manage files on the local system.
|
|
56
|
+
|
|
57
|
+
**Inputs:**
|
|
58
|
+
- `path` (string): File path.
|
|
59
|
+
- `content` (string, for write_file): Content to write.
|
|
60
|
+
|
|
61
|
+
### build_project_graph
|
|
62
|
+
|
|
63
|
+
Scan directory and build dependency graph.
|
|
64
|
+
|
|
65
|
+
**Inputs:**
|
|
66
|
+
- `path` (string, optional): Root directory to scan.
|
|
67
|
+
|
|
68
|
+
### get_file_relationships
|
|
69
|
+
|
|
70
|
+
Get imports and references for a specific file.
|
|
71
|
+
|
|
72
|
+
**Inputs:**
|
|
73
|
+
- `filePath` (string): Path to the file.
|
|
74
|
+
|
|
75
|
+
### get_project_graph_summary
|
|
76
|
+
|
|
77
|
+
Get overview of project structure and most referenced files.
|
|
78
|
+
|
|
79
|
+
## Usage
|
|
80
|
+
|
|
81
|
+
The Sequential Thinking tool is designed for:
|
|
82
|
+
- Breaking down complex problems into steps
|
|
83
|
+
- Planning and design with room for revision
|
|
84
|
+
- Analysis that might need course correction
|
|
85
|
+
- Problems where the full scope might not be clear initially
|
|
86
|
+
- Tasks that need to maintain context over multiple steps
|
|
87
|
+
- Situations where irrelevant information needs to be filtered out
|
|
88
|
+
|
|
89
|
+
## Configuration
|
|
90
|
+
|
|
91
|
+
### Usage with Claude Desktop
|
|
92
|
+
|
|
93
|
+
Add this to your `claude_desktop_config.json`:
|
|
94
|
+
|
|
95
|
+
#### npx
|
|
96
|
+
|
|
97
|
+
```json
|
|
98
|
+
{
|
|
99
|
+
"mcpServers": {
|
|
100
|
+
"sequential-thinking": {
|
|
101
|
+
"command": "npx",
|
|
102
|
+
"args": [
|
|
103
|
+
"-y",
|
|
104
|
+
"@modelcontextprotocol/server-sequential-thinking"
|
|
105
|
+
]
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Usage with VS Code
|
|
112
|
+
|
|
113
|
+
For quick installation, click one of the installation buttons below...
|
|
114
|
+
|
|
115
|
+
[](https://insiders.vscode.dev/redirect/mcp/install?name=sequentialthinking&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40modelcontextprotocol%2Fserver-sequential-thinking%22%5D%7D)
|
|
116
|
+
|
|
117
|
+
## Building
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
npm install
|
|
121
|
+
npm run build
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Testing
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
npm test
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## License
|
|
131
|
+
|
|
132
|
+
This MCP server is licensed under the MIT License.
|
package/dist/graph.js
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import * as fs from 'fs/promises';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
export class ProjectKnowledgeGraph {
|
|
4
|
+
nodes = new Map();
|
|
5
|
+
rootDir = '';
|
|
6
|
+
constructor() { }
|
|
7
|
+
async build(rootDir) {
|
|
8
|
+
this.rootDir = path.resolve(rootDir);
|
|
9
|
+
this.nodes.clear();
|
|
10
|
+
const files = await this.getAllFiles(this.rootDir);
|
|
11
|
+
// Step 1: Initialize nodes
|
|
12
|
+
for (const file of files) {
|
|
13
|
+
this.nodes.set(file, {
|
|
14
|
+
path: file,
|
|
15
|
+
imports: [],
|
|
16
|
+
importedBy: []
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
// Step 2: Parse imports and build edges
|
|
20
|
+
for (const file of files) {
|
|
21
|
+
await this.parseFile(file);
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
nodeCount: this.nodes.size,
|
|
25
|
+
totalFiles: files.length
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
async getAllFiles(dir) {
|
|
29
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
30
|
+
const files = [];
|
|
31
|
+
for (const entry of entries) {
|
|
32
|
+
const res = path.resolve(dir, entry.name);
|
|
33
|
+
if (entry.isDirectory()) {
|
|
34
|
+
if (entry.name === 'node_modules' || entry.name === '.git' || entry.name === 'dist')
|
|
35
|
+
continue;
|
|
36
|
+
files.push(...await this.getAllFiles(res));
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
if (/\.(ts|js|tsx|jsx|json)$/.test(entry.name)) {
|
|
40
|
+
files.push(res);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return files;
|
|
45
|
+
}
|
|
46
|
+
async parseFile(filePath) {
|
|
47
|
+
try {
|
|
48
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
49
|
+
const importRegex = /import\s+(?:[\w\s{},*]+from\s+)?['"]([^'"]+)['"]/g;
|
|
50
|
+
const dynamicImportRegex = /import\(['"]([^'"]+)['"]\)/g;
|
|
51
|
+
const requireRegex = /require\(['"]([^'"]+)['"]\)/g;
|
|
52
|
+
const allMatches = [
|
|
53
|
+
...content.matchAll(importRegex),
|
|
54
|
+
...content.matchAll(dynamicImportRegex),
|
|
55
|
+
...content.matchAll(requireRegex)
|
|
56
|
+
];
|
|
57
|
+
const currentNode = this.nodes.get(filePath);
|
|
58
|
+
if (!currentNode)
|
|
59
|
+
return;
|
|
60
|
+
for (const match of allMatches) {
|
|
61
|
+
const importPath = match[1];
|
|
62
|
+
if (importPath.startsWith('.')) {
|
|
63
|
+
const resolvedPath = await this.resolvePath(path.dirname(filePath), importPath);
|
|
64
|
+
if (resolvedPath && this.nodes.has(resolvedPath)) {
|
|
65
|
+
currentNode.imports.push(resolvedPath);
|
|
66
|
+
this.nodes.get(resolvedPath)?.importedBy.push(filePath);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
console.error(`Error parsing file ${filePath}:`, error);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
async resolvePath(dir, relativePath) {
|
|
76
|
+
const extensions = ['', '.ts', '.js', '.tsx', '.jsx', '.json', '/index.ts', '/index.js'];
|
|
77
|
+
const absolutePath = path.resolve(dir, relativePath);
|
|
78
|
+
for (const ext of extensions) {
|
|
79
|
+
const p = absolutePath + ext;
|
|
80
|
+
if (this.nodes.has(p)) {
|
|
81
|
+
return p;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
getRelationships(filePath) {
|
|
87
|
+
const absolutePath = path.resolve(this.rootDir, filePath);
|
|
88
|
+
// Try to match exact or with extensions
|
|
89
|
+
let node = this.nodes.get(absolutePath);
|
|
90
|
+
if (!node) {
|
|
91
|
+
// Fallback search
|
|
92
|
+
for (const [key, value] of this.nodes.entries()) {
|
|
93
|
+
if (key.endsWith(filePath)) {
|
|
94
|
+
node = value;
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (!node)
|
|
100
|
+
return null;
|
|
101
|
+
return {
|
|
102
|
+
path: node.path,
|
|
103
|
+
imports: node.imports.map(p => path.relative(this.rootDir, p)),
|
|
104
|
+
importedBy: node.importedBy.map(p => path.relative(this.rootDir, p))
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
getSummary() {
|
|
108
|
+
return {
|
|
109
|
+
root: this.rootDir,
|
|
110
|
+
fileCount: this.nodes.size,
|
|
111
|
+
mostReferencedFiles: [...this.nodes.values()]
|
|
112
|
+
.sort((a, b) => b.importedBy.length - a.importedBy.length)
|
|
113
|
+
.slice(0, 5)
|
|
114
|
+
.map(n => ({
|
|
115
|
+
file: path.relative(this.rootDir, n.path),
|
|
116
|
+
referencedBy: n.importedBy.length
|
|
117
|
+
}))
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { SequentialThinkingServer } from './lib.js';
|
|
6
|
+
import * as fs from 'fs/promises';
|
|
7
|
+
import { exec } from 'child_process';
|
|
8
|
+
import { promisify } from 'util';
|
|
9
|
+
import { ProjectKnowledgeGraph } from './graph.js';
|
|
10
|
+
const execAsync = promisify(exec);
|
|
11
|
+
const server = new McpServer({
|
|
12
|
+
name: "sequential-thinking-server",
|
|
13
|
+
version: "0.2.0",
|
|
14
|
+
});
|
|
15
|
+
const thinkingServer = new SequentialThinkingServer();
|
|
16
|
+
const knowledgeGraph = new ProjectKnowledgeGraph();
|
|
17
|
+
// --- Sequential Thinking Tool ---
|
|
18
|
+
server.tool("sequentialthinking", `A detailed tool for dynamic and reflective problem-solving through thoughts.
|
|
19
|
+
This tool helps analyze problems through a flexible thinking process that can adapt and evolve.
|
|
20
|
+
Each thought can build on, question, or revise previous insights as understanding deepens.
|
|
21
|
+
|
|
22
|
+
When to use this tool:
|
|
23
|
+
- Breaking down complex problems into steps
|
|
24
|
+
- Planning and design with room for revision
|
|
25
|
+
- Analysis that might need course correction
|
|
26
|
+
- Problems where the full scope might not be clear initially
|
|
27
|
+
- Problems that require a multi-step solution
|
|
28
|
+
- Tasks that need to maintain context over multiple steps
|
|
29
|
+
- Situations where irrelevant information needs to be filtered out
|
|
30
|
+
|
|
31
|
+
Key features:
|
|
32
|
+
- You can adjust total_thoughts up or down as you progress
|
|
33
|
+
- You can question or revise previous thoughts
|
|
34
|
+
- You can add more thoughts even after reaching what seemed like the end
|
|
35
|
+
- You can express uncertainty and explore alternative approaches
|
|
36
|
+
- Not every thought needs to build linearly - you can branch or backtrack
|
|
37
|
+
- Iterative Reasoning: Think step-by-step in a structured manner
|
|
38
|
+
- Tree of Thoughts: Generate and evaluate multiple options (Conservative/Balanced/Aggressive)
|
|
39
|
+
- Self-Critique: Check for risks, biases, and errors in thinking
|
|
40
|
+
- Branch Merging: Combine insights from multiple divergent paths
|
|
41
|
+
- Hypothesis Testing: Formulate and verify hypotheses
|
|
42
|
+
- Generates a solution hypothesis
|
|
43
|
+
- Verifies the hypothesis based on the Chain of Thought steps
|
|
44
|
+
- Repeats the process until satisfied
|
|
45
|
+
- Provides a correct answer
|
|
46
|
+
|
|
47
|
+
Parameters explained:
|
|
48
|
+
- thought: Your current thinking step, which can include:
|
|
49
|
+
* Regular analytical steps
|
|
50
|
+
* Revisions of previous thoughts
|
|
51
|
+
* Questions about previous decisions
|
|
52
|
+
* Realizations about needing more analysis
|
|
53
|
+
* Changes in approach
|
|
54
|
+
* Hypothesis generation
|
|
55
|
+
* Hypothesis verification
|
|
56
|
+
- nextThoughtNeeded: True if you need more thinking, even if at what seemed like the end
|
|
57
|
+
- thoughtNumber: Current number in sequence (can go beyond initial total if needed)
|
|
58
|
+
- totalThoughts: Current estimate of thoughts needed (can be adjusted up/down)
|
|
59
|
+
- isRevision: A boolean indicating if this thought revises previous thinking
|
|
60
|
+
- revisesThought: If is_revision is true, which thought number is being reconsidered
|
|
61
|
+
- branchFromThought: If branching, which thought number is the branching point
|
|
62
|
+
- branchId: Identifier for the current branch (if any)
|
|
63
|
+
- needsMoreThoughts: If reaching end but realizing more thoughts needed
|
|
64
|
+
- thoughtType: The type of thought (analysis, generation, evaluation, reflexion, selection)
|
|
65
|
+
- score: Score for evaluation (1-10)
|
|
66
|
+
- options: List of options generated
|
|
67
|
+
- selectedOption: The option selected
|
|
68
|
+
|
|
69
|
+
You should:
|
|
70
|
+
1. Start with an initial estimate of needed thoughts, but be ready to adjust
|
|
71
|
+
2. Feel free to question or revise previous thoughts
|
|
72
|
+
3. Don't hesitate to add more thoughts if needed, even at the "end"
|
|
73
|
+
4. Express uncertainty when present
|
|
74
|
+
5. Mark thoughts that revise previous thinking or branch into new paths
|
|
75
|
+
6. Ignore information that is irrelevant to the current step
|
|
76
|
+
7. Generate a solution hypothesis when appropriate
|
|
77
|
+
8. Verify the hypothesis based on the Chain of Thought steps
|
|
78
|
+
9. Repeat the process until satisfied with the solution
|
|
79
|
+
10. Provide a single, ideally correct answer as the final output
|
|
80
|
+
11. Only set nextThoughtNeeded to false when truly done and a satisfactory answer is reached`, {
|
|
81
|
+
thought: z.string().describe("Your current thinking step"),
|
|
82
|
+
nextThoughtNeeded: z.boolean().describe("Whether another thought step is needed"),
|
|
83
|
+
thoughtNumber: z.number().int().min(1).describe("Current thought number (numeric value, e.g., 1, 2, 3)"),
|
|
84
|
+
totalThoughts: z.number().int().min(1).describe("Estimated total thoughts needed (numeric value, e.g., 5, 10)"),
|
|
85
|
+
isRevision: z.boolean().optional().describe("Whether this revises previous thinking"),
|
|
86
|
+
revisesThought: z.number().int().min(1).optional().describe("Which thought is being reconsidered"),
|
|
87
|
+
branchFromThought: z.number().int().min(1).optional().describe("Branching point thought number"),
|
|
88
|
+
branchId: z.string().optional().describe("Branch identifier"),
|
|
89
|
+
needsMoreThoughts: z.boolean().optional().describe("If more thoughts are needed"),
|
|
90
|
+
thoughtType: z.enum(['analysis', 'generation', 'evaluation', 'reflexion', 'selection']).optional().describe("The type of thought"),
|
|
91
|
+
score: z.number().min(1).max(10).optional().describe("Score for evaluation (1-10)"),
|
|
92
|
+
options: z.array(z.string()).optional().describe("List of options generated"),
|
|
93
|
+
selectedOption: z.string().optional().describe("The option selected")
|
|
94
|
+
}, async (args) => {
|
|
95
|
+
const result = thinkingServer.processThought(args);
|
|
96
|
+
return {
|
|
97
|
+
content: result.content,
|
|
98
|
+
isError: result.isError
|
|
99
|
+
};
|
|
100
|
+
});
|
|
101
|
+
// --- New Tools ---
|
|
102
|
+
// 1. web_search
|
|
103
|
+
server.tool("web_search", "Search the web using Brave or Exa APIs (requires API keys in environment variables: BRAVE_API_KEY or EXA_API_KEY).", {
|
|
104
|
+
query: z.string().describe("The search query"),
|
|
105
|
+
provider: z.enum(['brave', 'exa', 'google']).optional().describe("Preferred search provider")
|
|
106
|
+
}, async ({ query, provider }) => {
|
|
107
|
+
try {
|
|
108
|
+
// Priority: User Preference > Brave > Exa
|
|
109
|
+
let selectedProvider = provider;
|
|
110
|
+
if (!selectedProvider) {
|
|
111
|
+
if (process.env.BRAVE_API_KEY)
|
|
112
|
+
selectedProvider = 'brave';
|
|
113
|
+
else if (process.env.EXA_API_KEY)
|
|
114
|
+
selectedProvider = 'exa';
|
|
115
|
+
else
|
|
116
|
+
return { content: [{ type: "text", text: "Error: No search provider configured. Please set BRAVE_API_KEY or EXA_API_KEY." }], isError: true };
|
|
117
|
+
}
|
|
118
|
+
if (selectedProvider === 'brave') {
|
|
119
|
+
if (!process.env.BRAVE_API_KEY)
|
|
120
|
+
throw new Error("BRAVE_API_KEY not found");
|
|
121
|
+
const response = await fetch(`https://api.search.brave.com/res/v1/web/search?q=${encodeURIComponent(query)}&count=5`, {
|
|
122
|
+
headers: { 'X-Subscription-Token': process.env.BRAVE_API_KEY }
|
|
123
|
+
});
|
|
124
|
+
if (!response.ok)
|
|
125
|
+
throw new Error(`Brave API error: ${response.statusText}`);
|
|
126
|
+
const data = await response.json();
|
|
127
|
+
return { content: [{ type: "text", text: JSON.stringify(data.web?.results || data, null, 2) }] };
|
|
128
|
+
}
|
|
129
|
+
if (selectedProvider === 'exa') {
|
|
130
|
+
if (!process.env.EXA_API_KEY)
|
|
131
|
+
throw new Error("EXA_API_KEY not found");
|
|
132
|
+
const response = await fetch('https://api.exa.ai/search', {
|
|
133
|
+
method: 'POST',
|
|
134
|
+
headers: {
|
|
135
|
+
'x-api-key': process.env.EXA_API_KEY,
|
|
136
|
+
'Content-Type': 'application/json'
|
|
137
|
+
},
|
|
138
|
+
body: JSON.stringify({ query, numResults: 5 })
|
|
139
|
+
});
|
|
140
|
+
if (!response.ok)
|
|
141
|
+
throw new Error(`Exa API error: ${response.statusText}`);
|
|
142
|
+
const data = await response.json();
|
|
143
|
+
return { content: [{ type: "text", text: JSON.stringify(data.results || data, null, 2) }] };
|
|
144
|
+
}
|
|
145
|
+
return { content: [{ type: "text", text: "Error: Unsupported or unconfigured provider." }], isError: true };
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
return {
|
|
149
|
+
content: [{ type: "text", text: `Search Error: ${error instanceof Error ? error.message : String(error)}` }],
|
|
150
|
+
isError: true
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
// 2. fetch
|
|
155
|
+
server.tool("fetch", "Perform an HTTP request to a specific URL.", {
|
|
156
|
+
url: z.string().url().describe("The URL to fetch"),
|
|
157
|
+
method: z.enum(['GET', 'POST', 'PUT', 'DELETE']).optional().default('GET').describe("HTTP Method"),
|
|
158
|
+
headers: z.record(z.string(), z.string()).optional().describe("HTTP Headers"),
|
|
159
|
+
body: z.string().optional().describe("Request body (for POST/PUT)")
|
|
160
|
+
}, async ({ url, method, headers, body }) => {
|
|
161
|
+
try {
|
|
162
|
+
const response = await fetch(url, {
|
|
163
|
+
method,
|
|
164
|
+
headers: headers || {},
|
|
165
|
+
body: body
|
|
166
|
+
});
|
|
167
|
+
const text = await response.text();
|
|
168
|
+
return {
|
|
169
|
+
content: [{
|
|
170
|
+
type: "text",
|
|
171
|
+
text: `Status: ${response.status}\n\n${text.substring(0, 10000)}${text.length > 10000 ? '\n...(truncated)' : ''}`
|
|
172
|
+
}]
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
return {
|
|
177
|
+
content: [{ type: "text", text: `Fetch Error: ${error instanceof Error ? error.message : String(error)}` }],
|
|
178
|
+
isError: true
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
// 3. shell_execute
|
|
183
|
+
server.tool("shell_execute", "Execute a shell command. Use with caution.", {
|
|
184
|
+
command: z.string().describe("The bash command to execute")
|
|
185
|
+
}, async ({ command }) => {
|
|
186
|
+
try {
|
|
187
|
+
const { stdout, stderr } = await execAsync(command);
|
|
188
|
+
return {
|
|
189
|
+
content: [{
|
|
190
|
+
type: "text",
|
|
191
|
+
text: `STDOUT:\n${stdout}\n\nSTDERR:\n${stderr}`
|
|
192
|
+
}]
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
return {
|
|
197
|
+
content: [{ type: "text", text: `Shell Error: ${error instanceof Error ? error.message : String(error)}` }],
|
|
198
|
+
isError: true
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
// 4. read_file
|
|
203
|
+
server.tool("read_file", "Read the contents of a file.", {
|
|
204
|
+
path: z.string().describe("Path to the file")
|
|
205
|
+
}, async ({ path }) => {
|
|
206
|
+
try {
|
|
207
|
+
const content = await fs.readFile(path, 'utf-8');
|
|
208
|
+
return {
|
|
209
|
+
content: [{ type: "text", text: content }]
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
return {
|
|
214
|
+
content: [{ type: "text", text: `Read Error: ${error instanceof Error ? error.message : String(error)}` }],
|
|
215
|
+
isError: true
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
// 5. write_file
|
|
220
|
+
server.tool("write_file", "Write content to a file (overwrites existing).", {
|
|
221
|
+
path: z.string().describe("Path to the file"),
|
|
222
|
+
content: z.string().describe("Content to write")
|
|
223
|
+
}, async ({ path, content }) => {
|
|
224
|
+
try {
|
|
225
|
+
await fs.writeFile(path, content, 'utf-8');
|
|
226
|
+
return {
|
|
227
|
+
content: [{ type: "text", text: `Successfully wrote to ${path}` }]
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
catch (error) {
|
|
231
|
+
return {
|
|
232
|
+
content: [{ type: "text", text: `Write Error: ${error instanceof Error ? error.message : String(error)}` }],
|
|
233
|
+
isError: true
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
// --- Project Knowledge Graph Tools ---
|
|
238
|
+
// 6. build_project_graph
|
|
239
|
+
server.tool("build_project_graph", "Scan the directory and build a dependency graph of the project (Analyzing imports/exports).", {
|
|
240
|
+
path: z.string().optional().default('.').describe("Root directory path to scan (default: current dir)")
|
|
241
|
+
}, async ({ path }) => {
|
|
242
|
+
try {
|
|
243
|
+
const result = await knowledgeGraph.build(path || '.');
|
|
244
|
+
return {
|
|
245
|
+
content: [{ type: "text", text: `Graph built successfully.\nNodes: ${result.nodeCount}\nTotal Scanned Files: ${result.totalFiles}` }]
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
catch (error) {
|
|
249
|
+
return {
|
|
250
|
+
content: [{ type: "text", text: `Graph Build Error: ${error instanceof Error ? error.message : String(error)}` }],
|
|
251
|
+
isError: true
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
// 7. get_file_relationships
|
|
256
|
+
server.tool("get_file_relationships", "Get dependencies and references for a specific file from the built graph.", {
|
|
257
|
+
filePath: z.string().describe("Path to the file (e.g., 'src/index.ts')")
|
|
258
|
+
}, async ({ filePath }) => {
|
|
259
|
+
try {
|
|
260
|
+
const rel = knowledgeGraph.getRelationships(filePath);
|
|
261
|
+
if (!rel) {
|
|
262
|
+
return {
|
|
263
|
+
content: [{ type: "text", text: `File not found in graph: ${filePath}. (Did you run 'build_project_graph'?)` }],
|
|
264
|
+
isError: true
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
return {
|
|
268
|
+
content: [{ type: "text", text: JSON.stringify(rel, null, 2) }]
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
catch (error) {
|
|
272
|
+
return {
|
|
273
|
+
content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
|
|
274
|
+
isError: true
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
// 8. get_project_graph_summary
|
|
279
|
+
server.tool("get_project_graph_summary", "Get a summary of the project structure (most referenced files, total count).", {}, async () => {
|
|
280
|
+
try {
|
|
281
|
+
const summary = knowledgeGraph.getSummary();
|
|
282
|
+
return {
|
|
283
|
+
content: [{ type: "text", text: JSON.stringify(summary, null, 2) }]
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
catch (error) {
|
|
287
|
+
return {
|
|
288
|
+
content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
|
|
289
|
+
isError: true
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
async function runServer() {
|
|
294
|
+
const transport = new StdioServerTransport();
|
|
295
|
+
await server.connect(transport);
|
|
296
|
+
console.error("Sequential Thinking MCP Server (Extended) running on stdio");
|
|
297
|
+
}
|
|
298
|
+
runServer().catch((error) => {
|
|
299
|
+
console.error("Fatal error running server:", error);
|
|
300
|
+
process.exit(1);
|
|
301
|
+
});
|
package/dist/lib.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
export class SequentialThinkingServer {
|
|
3
|
+
thoughtHistory = [];
|
|
4
|
+
branches = {};
|
|
5
|
+
disableThoughtLogging;
|
|
6
|
+
constructor() {
|
|
7
|
+
this.disableThoughtLogging = (process.env.DISABLE_THOUGHT_LOGGING || "").toLowerCase() === "true";
|
|
8
|
+
}
|
|
9
|
+
formatThought(thoughtData) {
|
|
10
|
+
const { thoughtNumber, totalThoughts, thought, isRevision, revisesThought, branchFromThought, branchId, thoughtType, score, options, selectedOption } = thoughtData;
|
|
11
|
+
let prefix = '';
|
|
12
|
+
let context = '';
|
|
13
|
+
if (thoughtType === 'reflexion' || isRevision) {
|
|
14
|
+
prefix = chalk.yellow('🔄 Reflexion');
|
|
15
|
+
if (revisesThought)
|
|
16
|
+
context += ` (revising thought ${revisesThought})`;
|
|
17
|
+
}
|
|
18
|
+
else if (thoughtType === 'generation') {
|
|
19
|
+
prefix = chalk.magenta('💡 Generation');
|
|
20
|
+
}
|
|
21
|
+
else if (thoughtType === 'evaluation') {
|
|
22
|
+
prefix = chalk.cyan('⚖️ Evaluation');
|
|
23
|
+
if (score)
|
|
24
|
+
context += ` (Score: ${score})`;
|
|
25
|
+
}
|
|
26
|
+
else if (thoughtType === 'selection') {
|
|
27
|
+
prefix = chalk.green('✅ Selection');
|
|
28
|
+
if (selectedOption)
|
|
29
|
+
context += ` (Selected: ${selectedOption})`;
|
|
30
|
+
}
|
|
31
|
+
else if (branchFromThought) {
|
|
32
|
+
prefix = chalk.green('🌿 Branch');
|
|
33
|
+
context = ` (from thought ${branchFromThought}, ID: ${branchId})`;
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
prefix = chalk.blue('💭 Thought');
|
|
37
|
+
context = '';
|
|
38
|
+
}
|
|
39
|
+
const header = `${prefix} ${thoughtNumber}/${totalThoughts}${context}`;
|
|
40
|
+
const borderLength = Math.max(header.length, thought.length) + 4;
|
|
41
|
+
const border = '─'.repeat(borderLength);
|
|
42
|
+
let extraContent = '';
|
|
43
|
+
if (options && options.length > 0) {
|
|
44
|
+
extraContent += `
|
|
45
|
+
│ Options:
|
|
46
|
+
` + options.map(o => `│ - ${o}`).join('\n');
|
|
47
|
+
}
|
|
48
|
+
return `
|
|
49
|
+
┌${border}┐
|
|
50
|
+
│ ${header} │
|
|
51
|
+
├${border}┤
|
|
52
|
+
│ ${thought.padEnd(borderLength - 2)} │${extraContent}
|
|
53
|
+
└${border}┘`;
|
|
54
|
+
}
|
|
55
|
+
processThought(input) {
|
|
56
|
+
try {
|
|
57
|
+
if (input.thoughtNumber > input.totalThoughts) {
|
|
58
|
+
input.totalThoughts = input.thoughtNumber;
|
|
59
|
+
}
|
|
60
|
+
this.thoughtHistory.push(input);
|
|
61
|
+
if (input.branchFromThought && input.branchId) {
|
|
62
|
+
if (!this.branches[input.branchId]) {
|
|
63
|
+
this.branches[input.branchId] = [];
|
|
64
|
+
}
|
|
65
|
+
this.branches[input.branchId].push(input);
|
|
66
|
+
}
|
|
67
|
+
if (!this.disableThoughtLogging) {
|
|
68
|
+
const formattedThought = this.formatThought(input);
|
|
69
|
+
console.error(formattedThought);
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
content: [{
|
|
73
|
+
type: "text",
|
|
74
|
+
text: JSON.stringify({
|
|
75
|
+
thoughtNumber: input.thoughtNumber,
|
|
76
|
+
totalThoughts: input.totalThoughts,
|
|
77
|
+
nextThoughtNeeded: input.nextThoughtNeeded,
|
|
78
|
+
branches: Object.keys(this.branches),
|
|
79
|
+
thoughtHistoryLength: this.thoughtHistory.length
|
|
80
|
+
}, null, 2)
|
|
81
|
+
}]
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
return {
|
|
86
|
+
content: [{
|
|
87
|
+
type: "text",
|
|
88
|
+
text: JSON.stringify({
|
|
89
|
+
error: error instanceof Error ? error.message : String(error),
|
|
90
|
+
status: 'failed'
|
|
91
|
+
}, null, 2)
|
|
92
|
+
}],
|
|
93
|
+
isError: true
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import { SequentialThinkingServer } from './lib.js';
|
|
3
|
+
describe('SequentialThinkingServer', () => {
|
|
4
|
+
let server;
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
server = new SequentialThinkingServer();
|
|
7
|
+
});
|
|
8
|
+
it('should process a basic linear thought', () => {
|
|
9
|
+
const input = {
|
|
10
|
+
thought: "First step",
|
|
11
|
+
thoughtNumber: 1,
|
|
12
|
+
totalThoughts: 3,
|
|
13
|
+
nextThoughtNeeded: true,
|
|
14
|
+
thoughtType: 'analysis'
|
|
15
|
+
};
|
|
16
|
+
const result = server.processThought(input);
|
|
17
|
+
expect(result.isError).toBeUndefined();
|
|
18
|
+
const content = JSON.parse(result.content[0].text);
|
|
19
|
+
expect(content.thoughtNumber).toBe(1);
|
|
20
|
+
expect(content.thoughtHistoryLength).toBe(1);
|
|
21
|
+
});
|
|
22
|
+
it('should handle branching correctly', () => {
|
|
23
|
+
// Initial thought
|
|
24
|
+
server.processThought({
|
|
25
|
+
thought: "Root thought",
|
|
26
|
+
thoughtNumber: 1,
|
|
27
|
+
totalThoughts: 3,
|
|
28
|
+
nextThoughtNeeded: true
|
|
29
|
+
});
|
|
30
|
+
// Branch 1
|
|
31
|
+
const branch1Input = {
|
|
32
|
+
thought: "Alternative A",
|
|
33
|
+
thoughtNumber: 2,
|
|
34
|
+
totalThoughts: 3,
|
|
35
|
+
nextThoughtNeeded: true,
|
|
36
|
+
branchFromThought: 1,
|
|
37
|
+
branchId: "branch-A",
|
|
38
|
+
thoughtType: 'generation'
|
|
39
|
+
};
|
|
40
|
+
const result1 = server.processThought(branch1Input);
|
|
41
|
+
const content1 = JSON.parse(result1.content[0].text);
|
|
42
|
+
expect(content1.branches).toContain("branch-A");
|
|
43
|
+
// Branch 2
|
|
44
|
+
const branch2Input = {
|
|
45
|
+
thought: "Alternative B",
|
|
46
|
+
thoughtNumber: 2,
|
|
47
|
+
totalThoughts: 3,
|
|
48
|
+
nextThoughtNeeded: true,
|
|
49
|
+
branchFromThought: 1,
|
|
50
|
+
branchId: "branch-B",
|
|
51
|
+
thoughtType: 'generation'
|
|
52
|
+
};
|
|
53
|
+
const result2 = server.processThought(branch2Input);
|
|
54
|
+
const content2 = JSON.parse(result2.content[0].text);
|
|
55
|
+
expect(content2.branches).toContain("branch-B");
|
|
56
|
+
expect(content2.branches.length).toBe(2);
|
|
57
|
+
});
|
|
58
|
+
it('should handle evaluation with scores', () => {
|
|
59
|
+
const input = {
|
|
60
|
+
thought: "Evaluating option X",
|
|
61
|
+
thoughtNumber: 3,
|
|
62
|
+
totalThoughts: 5,
|
|
63
|
+
nextThoughtNeeded: true,
|
|
64
|
+
thoughtType: 'evaluation',
|
|
65
|
+
score: 8,
|
|
66
|
+
options: ['Option X', 'Option Y']
|
|
67
|
+
};
|
|
68
|
+
const result = server.processThought(input);
|
|
69
|
+
expect(result.isError).toBeUndefined();
|
|
70
|
+
// Since we don't return the score in the simple JSON response (only in logs or history),
|
|
71
|
+
// we mainly check that it doesn't crash and processes correctly.
|
|
72
|
+
// If we exposed history in the response, we could check that too.
|
|
73
|
+
});
|
|
74
|
+
it('should adjust totalThoughts if thoughtNumber exceeds it', () => {
|
|
75
|
+
const input = {
|
|
76
|
+
thought: "Unexpected long process",
|
|
77
|
+
thoughtNumber: 6,
|
|
78
|
+
totalThoughts: 5,
|
|
79
|
+
nextThoughtNeeded: true
|
|
80
|
+
};
|
|
81
|
+
const result = server.processThought(input);
|
|
82
|
+
const content = JSON.parse(result.content[0].text);
|
|
83
|
+
expect(content.totalThoughts).toBe(6);
|
|
84
|
+
});
|
|
85
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gotza02/sequential-thinking",
|
|
3
|
+
"version": "2026.01.16",
|
|
4
|
+
"publishConfig": {
|
|
5
|
+
"access": "public"
|
|
6
|
+
},
|
|
7
|
+
"description": "MCP server for sequential thinking and problem solving (Extended with Web Search & Graph)",
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"mcpName": "sequential-thinking-extended",
|
|
10
|
+
"author": "Anthropic, PBC (https://anthropic.com)",
|
|
11
|
+
"homepage": "https://modelcontextprotocol.io",
|
|
12
|
+
"bugs": "https://github.com/modelcontextprotocol/servers/issues",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "https://github.com/modelcontextprotocol/servers.git"
|
|
16
|
+
},
|
|
17
|
+
"type": "module",
|
|
18
|
+
"bin": {
|
|
19
|
+
"mcp-server-sequential-thinking": "dist/index.js"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsc && shx chmod +x dist/*.js",
|
|
26
|
+
"prepare": "npm run build",
|
|
27
|
+
"watch": "tsc --watch",
|
|
28
|
+
"test": "vitest run --coverage"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@modelcontextprotocol/sdk": "^1.24.0",
|
|
32
|
+
"chalk": "^5.3.0",
|
|
33
|
+
"yargs": "^17.7.2"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/node": "^22",
|
|
37
|
+
"@types/yargs": "^17.0.32",
|
|
38
|
+
"@vitest/coverage-v8": "^2.1.8",
|
|
39
|
+
"shx": "^0.3.4",
|
|
40
|
+
"typescript": "^5.3.3",
|
|
41
|
+
"vitest": "^2.1.8"
|
|
42
|
+
}
|
|
43
|
+
}
|