@gotza02/sequential-thinking 1.0.0

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 ADDED
@@ -0,0 +1,262 @@
1
+ # Sequential Thinking MCP Server
2
+
3
+ MCP Server นี้ถูกออกแบบมาเพื่อช่วยให้ AI สามารถคิดวิเคราะห์แก้ปัญหาได้อย่างเป็นระบบ (Sequential Thinking) ผ่านกระบวนการคิดแบบทีละขั้นตอน (Step-by-step), การแตกแขนงความคิด (Tree of Thoughts) และการทบทวนตัวเอง (Self-Reflexion)
4
+
5
+ ## ฟีเจอร์หลัก (Features)
6
+
7
+ - **การวิเคราะห์ตามลำดับ (Sequential Analysis)**: ย่อยปัญหาที่ซับซ้อนให้เป็นขั้นตอนย่อยๆ ที่จัดการได้ง่าย
8
+ - **การให้เหตุผลแบบวนซ้ำ (Iterative Reasoning)**: คิดทีละขั้นตอนอย่างมีโครงสร้าง และปรับปรุงผลลัพธ์ผ่านการวนรอบ
9
+ - **แตกแขนงความคิด (Tree of Thoughts)**: สร้างและประเมินทางเลือกหลายๆ ทาง (เช่น กลยุทธ์แบบระมัดระวัง, แบบสมดุล, หรือแบบกล้าเสี่ยง)
10
+ - **การวิจารณ์ตัวเอง (Self-Critique)**: ตรวจสอบความเสี่ยง ข้อผิดพลาด และอคติในกระบวนการคิดอย่างสม่ำเสมอ
11
+ - **รวมความคิด (Branch Merging)**: สังเคราะห์และรวบรวมข้อมูลเชิงลึกจากเส้นทางการคิดที่แตกต่างกัน
12
+ - **การทดสอบสมมติฐาน (Hypothesis Testing)**: ตั้งสมมติฐานและตรวจสอบความถูกต้องด้วยหลักฐานหรือตรรกะ
13
+ - **ประเมินทางเลือก (Option Evaluation)**: ให้คะแนนและชั่งน้ำหนักทางเลือกต่างๆ (`evaluation`) เพื่อช่วยตัดสินใจ
14
+ - **ทบทวนตัวเอง (Self-Reflexion)**: ย้อนกลับไปแก้ไขความคิดก่อนหน้า (`reflexion`) เพื่อปรับปรุงความแม่นยำ
15
+ - **ปรับเปลี่ยนแบบไดนามิก (Dynamic Adjustment)**: ปรับจำนวนขั้นตอนการคิดเพิ่มหรือลดได้ตามความเหมาะสมของสถานการณ์
16
+
17
+ ## คำแนะนำระบบสำหรับ AI (System Instruction)
18
+
19
+ เมื่อเชื่อมต่อกับ Server นี้ AI ควรปฏิบัติตามแนวทางต่อไปนี้เพื่อให้แก้ปัญหาได้ดีที่สุด:
20
+
21
+ 1. **การคิดแบบวนซ้ำ (Iterative Thinking)**: ใช้เครื่องมือ `sequentialthinking` สำหรับงานที่ซับซ้อน **อย่า** พยายามตอบทันที ให้แตกปัญหาเป็นขั้นตอน ตั้งสมมติฐาน และประเมินผล
22
+ 2. **สำรวจบริบท (Context Discovery)**: ก่อนแก้ไขโค้ด ให้ใช้ `build_project_graph` ตามด้วย `get_project_graph_summary` เพื่อเข้าใจโครงสร้างโปรเจกต์เสมอ และใช้ `get_file_relationships` เพื่อดูความสัมพันธ์ของไฟล์
23
+ 3. **ตรวจสอบข้อเท็จจริง (Fact Verification)**: ใช้ `web_search` เพื่อตรวจสอบข้อมูลล่าสุด, เวอร์ชั่นของ Library หรือเอกสาร และใช้ `fetch` เพื่อดึงข้อมูลดิบเมื่อจำเป็น
24
+ 4. **รันคำสั่ง (Local Execution)**: ใช้ `shell_execute` เพื่อรัน Test, Linter หรือ Build เพื่อตรวจสอบความถูกต้องของการแก้ไข อ่านไฟล์ด้วย `read_file` ก่อนเขียนทับเสมอ
25
+ 5. **ความต่อเนื่อง (Persistence)**: กระบวนการคิดจะถูกบันทึกอัตโนมัติ หากทำงานค้างไว้ สามารถย้อนกลับมาดูไฟล์ `thoughts_history.json` ได้
26
+
27
+ ## คู่มือการใช้งานเครื่องมือ (Detailed Tool Guide)
28
+
29
+ ### 🧠 เครื่องมือช่วยคิด (Cognitive Tools)
30
+
31
+ #### `sequentialthinking`
32
+ หัวใจหลักสำหรับการแก้ปัญหาอย่างมีโครงสร้าง บังคับให้คิดวิเคราะห์ทีละขั้นตอนก่อนจะสรุปผล
33
+
34
+ **Inputs:**
35
+ - `thought` (string, required): เนื้อหาของความคิดในขั้นตอนนี้
36
+ - `thoughtNumber` (integer, required): ลำดับขั้นตอนปัจจุบัน (เริ่มที่ 1)
37
+ - `totalThoughts` (integer, required): จำนวนขั้นตอนที่คาดว่าจะใช้ (ปรับเปลี่ยนได้ตลอด)
38
+ - `nextThoughtNeeded` (boolean, required): `true` ถ้าต้องคิดต่อ, `false` เมื่อได้คำตอบสุดท้ายแล้ว
39
+ - `thoughtType` (enum): ประเภทของความคิด:
40
+ - `analysis`: วิเคราะห์แยกแยะปัญหา
41
+ - `generation`: ระดมสมองหาทางออก
42
+ - `evaluation`: ประเมินทางเลือก (ใช้คู่กับ `score`)
43
+ - `reflexion`: ทบทวนความคิดก่อนหน้า (ใช้คู่กับ `isRevision`)
44
+ - `selection`: เลือกเส้นทางที่จะไปต่อ
45
+ - `isRevision` (boolean): `true` ถ้ากำลังแก้ไขข้อผิดพลาดก่อนหน้า
46
+ - `revisesThought` (integer): ระบุหมายเลขขั้นตอนที่กำลังแก้ไข
47
+ - `branchFromThought` (integer): หมายเลขขั้นตอนแม่ ถ้าต้องการแตกกิ่งความคิดใหม่
48
+ - `branchId` (string): ชื่อระบุของกิ่งความคิดนั้น (Branch ID)
49
+
50
+ **ข้อแนะนำ:** ใช้เครื่องมือนี้กับงานทุกอย่างที่ไม่ใช่คำถามพื้นฐาน อย่าเพิ่งรีบตอบ ให้คิดก่อน
51
+
52
+ #### `clear_thought_history`
53
+ ล้างประวัติการคิดทั้งหมด ใช้เมื่อต้องการเริ่มงานใหม่หรือเคลียร์ Context
54
+
55
+ #### `summarize_history`
56
+ ย่อรวมความคิดหลายๆ ขั้นตอนให้เป็นสรุปเดียว จำเป็นมากสำหรับการคิดที่ยาวนาน เพื่อประหยัด Token
57
+
58
+ **Inputs:**
59
+ - `startIndex` (integer): จุดเริ่มต้นที่จะสรุป
60
+ - `endIndex` (integer): จุดสิ้นสุดที่จะสรุป
61
+ - `summary` (string): ข้อความสรุป
62
+
63
+ ### 🌐 ความรู้ภายนอก (External Knowledge)
64
+
65
+ #### `web_search`
66
+ ค้นหาข้อมูลแบบ Real-time จากอินเทอร์เน็ต
67
+
68
+ **Inputs:**
69
+ - `query` (string, required): คำค้นหา
70
+ - `provider` (enum, optional):
71
+ - `brave`: ค้นหาเว็บทั่วไป (ต้องมี `BRAVE_API_KEY`)
72
+ - `exa`: ค้นหาเชิงลึกด้วย AI (ต้องมี `EXA_API_KEY`)
73
+ - `google`: Google Custom Search (ต้องมี `GOOGLE_SEARCH_API_KEY` & `GOOGLE_SEARCH_CX`)
74
+
75
+ #### `fetch`
76
+ ดึงข้อมูล HTTP Request จาก URL โดยตรง มีประโยชน์มากสำหรับดึง HTML, JSON หรือ Text จากแหล่งข้อมูลที่เจอผ่าน Search
77
+
78
+ **Inputs:**
79
+ - `url` (string, required): URL เป้าหมาย
80
+ - `method`: `GET` (default), `POST`, `PUT`, `DELETE`
81
+ - `headers`: JSON Object สำหรับ Headers
82
+ - `body`: Request Body สำหรับ POST/PUT
83
+
84
+ #### `read_webpage`
85
+ อ่านหน้าเว็บและแปลงเป็น Markdown ที่สะอาด (ตัดโฆษณาและเมนูออก) เหมาะสำหรับอ่านบทความหรือ Document เพื่อประหยัด Token
86
+
87
+ **Inputs:**
88
+ - `url` (string, required): URL ที่จะอ่าน
89
+
90
+ ### 🏗 ความฉลาดด้านโค้ด (Codebase Intelligence)
91
+
92
+ #### `build_project_graph`
93
+ **รันคำสั่งนี้เป็นอันดับแรก** เมื่อเริ่มโปรเจกต์ใหม่ มันจะสแกนโฟลเดอร์และสร้างแผนผังความสัมพันธ์ (Dependency Graph) โดยใช้ TypeScript AST Analysis รวมถึงดึงข้อมูล **Exported Symbols** (Functions, Classes, Variables) ออกมาด้วย
94
+
95
+ **Inputs:**
96
+ - `path` (string, optional): โฟลเดอร์ราก (Default คือ `.`)
97
+
98
+ #### `get_project_graph_summary`
99
+ ดูภาพรวมโครงสร้างโปรเจกต์และไฟล์ที่ถูกอ้างอิงถึงบ่อยที่สุด
100
+
101
+ #### `get_project_graph_visualization`
102
+ สร้าง Mermaid Diagram String แสดงความสัมพันธ์ของไฟล์ในโปรเจกต์ สามารถนำไปแสดงผลใน Markdown Viewer ได้
103
+
104
+ ### 🛠 การจัดการระบบ (System Operations)
105
+
106
+ #### `read_file`
107
+ อ่านเนื้อหาไฟล์ **ควรอ่านไฟล์ก่อนแก้ไขเสมอ**
108
+
109
+ **Inputs:**
110
+ - `path` (string, required): Path ของไฟล์
111
+
112
+ #### `write_file`
113
+ สร้างหรือเขียนทับไฟล์
114
+
115
+ **Inputs:**
116
+ - `path` (string, required): Path ของไฟล์
117
+ - `content` (string, required): เนื้อหาที่จะเขียน
118
+
119
+ #### `edit_file`
120
+ แก้ไขข้อความบางส่วนในไฟล์ (Search & Replace) เหมาะสำหรับการแก้ไขจุดเล็กๆ โดยไม่ต้องเขียนทับทั้งไฟล์
121
+
122
+ **Inputs:**
123
+ - `path` (string, required): Path ของไฟล์
124
+ - `oldText` (string, required): ข้อความเดิมที่ต้องการเปลี่ยน (ต้องตรงเป๊ะ)
125
+ - `newText` (string, required): ข้อความใหม่
126
+ - `allowMultiple` (boolean, optional): อนุญาตให้เปลี่ยนหลายจุดพร้อมกัน (Default: false)
127
+
128
+ #### `manage_notes`
129
+ จัดการความจำระยะยาว (Long-term Memory) / บันทึกช่วยจำ ใช้เก็บข้อมูลสำคัญ กฎ หรือสิ่งที่เรียนรู้เพื่อให้จำได้ข้าม Session
130
+
131
+ **Inputs:**
132
+ - `action` (enum, required): 'add', 'list', 'search', 'update', 'delete'
133
+ - `title` (string): หัวข้อ
134
+ - `content` (string): เนื้อหา
135
+ - `tags` (array): แท็กจัดหมวดหมู่
136
+ - `searchQuery` (string): คำค้นหา (สำหรับ action: search)
137
+ - `noteId` (string): ID ของโน้ต (สำหรับ update/delete)
138
+
139
+ #### `shell_execute`
140
+ รันคำสั่ง Shell (เช่น `npm test`, `npm run build`, `ls`)
141
+ **คำเตือน:** ใช้ด้วยความระมัดระวัง หลีกเลี่ยงคำสั่งที่ลบข้อมูลหรือเปิดเผยความลับ
142
+
143
+ ## การตั้งค่าและการใช้งาน (Installation & Usage)
144
+
145
+ ### 1. การใช้งานกับ Gemini CLI (Usage with Gemini CLI)
146
+
147
+ แก้ไขไฟล์ `~/.gemini/settings.json` (หรือ `config.json`):
148
+
149
+ #### แบบใช้ npx (ไม่ต้องติดตั้ง)
150
+
151
+ ```json
152
+ {
153
+ "mcpServers": {
154
+ "smartagent": {
155
+ "command": "npx",
156
+ "args": [
157
+ "-y",
158
+ "@gotza02/sequential-thinking"
159
+ ],
160
+ "env": {
161
+ "BRAVE_API_KEY": "YOUR_BRAVE_API_KEY",
162
+ "EXA_API_KEY": "YOUR_EXA_API_KEY"
163
+ }
164
+ }
165
+ }
166
+ }
167
+ ```
168
+
169
+ #### แบบติดตั้ง Local (Node.js)
170
+
171
+ 1. Clone Repository นี้มา
172
+ 2. รัน `npm install` และ `npm run build`
173
+ 3. ตั้งค่าใน `settings.json`:
174
+
175
+ ```json
176
+ {
177
+ "mcpServers": {
178
+ "smartagent": {
179
+ "command": "node",
180
+ "args": [
181
+ "/path/to/your/MCP3/dist/index.js"
182
+ ],
183
+ "env": {
184
+ "BRAVE_API_KEY": "YOUR_BRAVE_API_KEY",
185
+ "THOUGHTS_STORAGE_PATH": "thoughts_history.json",
186
+ "NOTES_STORAGE_PATH": "project_notes.json"
187
+ }
188
+ }
189
+ }
190
+ }
191
+ ```
192
+
193
+ ### 2. การใช้งานกับ Claude Desktop (Usage with Claude Desktop)
194
+
195
+ แก้ไขไฟล์ `claude_desktop_config.json`:
196
+
197
+ #### แบบใช้ npx (ไม่ต้องติดตั้ง)
198
+
199
+ ```json
200
+ {
201
+ "mcpServers": {
202
+ "sequential-thinking": {
203
+ "command": "npx",
204
+ "args": [
205
+ "-y",
206
+ "@gotza02/sequential-thinking"
207
+ ],
208
+ "env": {
209
+ "BRAVE_API_KEY": "YOUR_BRAVE_API_KEY"
210
+ }
211
+ }
212
+ }
213
+ }
214
+ ```
215
+
216
+ #### แบบติดตั้ง Local (Node.js)
217
+
218
+ ```json
219
+ {
220
+ "mcpServers": {
221
+ "sequential-thinking": {
222
+ "command": "node",
223
+ "args": [
224
+ "/absolute/path/to/MCP3/dist/index.js"
225
+ ],
226
+ "env": {
227
+ "BRAVE_API_KEY": "YOUR_BRAVE_API_KEY",
228
+ "THOUGHTS_STORAGE_PATH": "/absolute/path/to/thoughts_history.json",
229
+ "NOTES_STORAGE_PATH": "/absolute/path/to/project_notes.json"
230
+ }
231
+ }
232
+ }
233
+ }
234
+ ```
235
+
236
+ ## การ Build และ Test
237
+
238
+ ```bash
239
+ npm install
240
+ npm run build
241
+ ```
242
+
243
+ ```bash
244
+ npm test
245
+ ```
246
+
247
+ ## ประวัติการอัปเดต (Recent Updates)
248
+
249
+ ### v1.0.0 (Major Release)
250
+ - **First Stable Release**: เปิดตัวเวอร์ชั่นเสถียร 1.0.0 พร้อมเอกสารภาษาไทยสมบูรณ์
251
+ - **Features Complete**: รวมฟีเจอร์ Sequential Thinking, Web Search, File System, Graph Analysis และ Memory ไว้ครบถ้วน
252
+
253
+ ### v2026.2.0 (Refactoring)
254
+ - **Refactoring**: ปรับโครงสร้างโค้ดครั้งใหญ่ แยกการทำงานเป็นโมดูล (`src/tools/`) เพื่อให้อ่านง่ายและรองรับการขยายตัวในอนาคต
255
+
256
+ ### v2026.1.31
257
+ - **New Tools**:
258
+ - `manage_notes`: ระบบความจำระยะยาวสำหรับเก็บข้อมูลข้าม Session
259
+
260
+ ## License
261
+
262
+ MIT License
package/dist/graph.js ADDED
@@ -0,0 +1,214 @@
1
+ import * as fs from 'fs/promises';
2
+ import * as path from 'path';
3
+ import ts from 'typescript';
4
+ export class ProjectKnowledgeGraph {
5
+ nodes = new Map();
6
+ rootDir = '';
7
+ constructor() { }
8
+ async build(rootDir) {
9
+ this.rootDir = path.resolve(rootDir);
10
+ this.nodes.clear();
11
+ const files = await this.getAllFiles(this.rootDir);
12
+ // Step 1: Initialize nodes
13
+ for (const file of files) {
14
+ this.nodes.set(file, {
15
+ path: file,
16
+ imports: [],
17
+ importedBy: [],
18
+ symbols: []
19
+ });
20
+ }
21
+ // Step 2: Parse imports and build edges
22
+ for (const file of files) {
23
+ await this.parseFile(file);
24
+ }
25
+ return {
26
+ nodeCount: this.nodes.size,
27
+ totalFiles: files.length
28
+ };
29
+ }
30
+ async getAllFiles(dir) {
31
+ const entries = await fs.readdir(dir, { withFileTypes: true });
32
+ const files = [];
33
+ for (const entry of entries) {
34
+ const res = path.resolve(dir, entry.name);
35
+ if (entry.isDirectory()) {
36
+ if (entry.name === 'node_modules' || entry.name === '.git' || entry.name === 'dist')
37
+ continue;
38
+ files.push(...await this.getAllFiles(res));
39
+ }
40
+ else {
41
+ if (/\.(ts|js|tsx|jsx|json)$/.test(entry.name)) {
42
+ files.push(res);
43
+ }
44
+ }
45
+ }
46
+ return files;
47
+ }
48
+ async parseFile(filePath) {
49
+ try {
50
+ const content = await fs.readFile(filePath, 'utf-8');
51
+ const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
52
+ const imports = [];
53
+ const symbols = [];
54
+ const visit = (node) => {
55
+ // --- Symbols (Exports) ---
56
+ if (ts.isFunctionDeclaration(node) && node.name) {
57
+ const isExported = node.modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword);
58
+ if (isExported)
59
+ symbols.push(`function:${node.name.text}`);
60
+ }
61
+ else if (ts.isClassDeclaration(node) && node.name) {
62
+ const isExported = node.modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword);
63
+ if (isExported)
64
+ symbols.push(`class:${node.name.text}`);
65
+ }
66
+ else if (ts.isVariableStatement(node)) {
67
+ const isExported = node.modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword);
68
+ if (isExported) {
69
+ node.declarationList.declarations.forEach(d => {
70
+ if (ts.isIdentifier(d.name))
71
+ symbols.push(`var:${d.name.text}`);
72
+ });
73
+ }
74
+ }
75
+ // --- Imports ---
76
+ if (ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) {
77
+ if (node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) {
78
+ imports.push(node.moduleSpecifier.text);
79
+ }
80
+ }
81
+ // 2. Dynamic imports: import('...')
82
+ else if (ts.isCallExpression(node)) {
83
+ if (node.expression.kind === ts.SyntaxKind.ImportKeyword && node.arguments.length > 0) {
84
+ const arg = node.arguments[0];
85
+ if (ts.isStringLiteral(arg)) {
86
+ imports.push(arg.text);
87
+ }
88
+ }
89
+ // 3. CommonJS: require('...')
90
+ else if (ts.isIdentifier(node.expression) && node.expression.text === 'require' && node.arguments.length > 0) {
91
+ const arg = node.arguments[0];
92
+ if (ts.isStringLiteral(arg)) {
93
+ imports.push(arg.text);
94
+ }
95
+ }
96
+ }
97
+ ts.forEachChild(node, visit);
98
+ };
99
+ visit(sourceFile);
100
+ const currentNode = this.nodes.get(filePath);
101
+ if (!currentNode)
102
+ return;
103
+ currentNode.symbols = symbols;
104
+ for (const importPath of imports) {
105
+ let resolvedPath = null;
106
+ if (importPath.startsWith('.')) {
107
+ resolvedPath = await this.resolvePath(path.dirname(filePath), importPath);
108
+ }
109
+ else {
110
+ resolvedPath = await this.resolvePath(this.rootDir, importPath);
111
+ }
112
+ if (resolvedPath && this.nodes.has(resolvedPath)) {
113
+ if (!currentNode.imports.includes(resolvedPath)) {
114
+ currentNode.imports.push(resolvedPath);
115
+ }
116
+ if (!this.nodes.get(resolvedPath)?.importedBy.includes(filePath)) {
117
+ this.nodes.get(resolvedPath)?.importedBy.push(filePath);
118
+ }
119
+ }
120
+ }
121
+ }
122
+ catch (error) {
123
+ console.error(`Error parsing file ${filePath}:`, error);
124
+ }
125
+ }
126
+ async resolvePath(dir, relativePath) {
127
+ const absolutePath = path.resolve(dir, relativePath);
128
+ // 1. Try exact match
129
+ if (this.nodes.has(absolutePath)) {
130
+ return absolutePath;
131
+ }
132
+ // 2. Try appending extensions
133
+ const extensions = ['.ts', '.js', '.tsx', '.jsx', '.json', '/index.ts', '/index.js'];
134
+ for (const ext of extensions) {
135
+ const p = absolutePath + ext;
136
+ if (this.nodes.has(p)) {
137
+ return p;
138
+ }
139
+ }
140
+ // 3. Try handling .js -> .ts mapping (ESM style imports)
141
+ if (absolutePath.endsWith('.js')) {
142
+ const tsPath = absolutePath.replace(/\.js$/, '.ts');
143
+ if (this.nodes.has(tsPath))
144
+ return tsPath;
145
+ const tsxPath = absolutePath.replace(/\.js$/, '.tsx');
146
+ if (this.nodes.has(tsxPath))
147
+ return tsxPath;
148
+ const jsxPath = absolutePath.replace(/\.js$/, '.jsx');
149
+ if (this.nodes.has(jsxPath))
150
+ return jsxPath;
151
+ }
152
+ return null;
153
+ }
154
+ getRelationships(filePath) {
155
+ const absolutePath = path.resolve(this.rootDir, filePath);
156
+ // Try to match exact or with extensions
157
+ let node = this.nodes.get(absolutePath);
158
+ if (!node) {
159
+ // Fallback search
160
+ for (const [key, value] of this.nodes.entries()) {
161
+ if (key.endsWith(filePath)) {
162
+ node = value;
163
+ break;
164
+ }
165
+ }
166
+ }
167
+ if (!node)
168
+ return null;
169
+ return {
170
+ path: node.path,
171
+ imports: node.imports.map(p => path.relative(this.rootDir, p)),
172
+ importedBy: node.importedBy.map(p => path.relative(this.rootDir, p)),
173
+ symbols: node.symbols
174
+ };
175
+ }
176
+ getSummary() {
177
+ return {
178
+ root: this.rootDir,
179
+ fileCount: this.nodes.size,
180
+ mostReferencedFiles: [...this.nodes.values()]
181
+ .sort((a, b) => b.importedBy.length - a.importedBy.length)
182
+ .slice(0, 5)
183
+ .map(n => ({
184
+ file: path.relative(this.rootDir, n.path),
185
+ referencedBy: n.importedBy.length
186
+ }))
187
+ };
188
+ }
189
+ toMermaid() {
190
+ const lines = ['graph TD'];
191
+ const fileToId = new Map();
192
+ let idCounter = 0;
193
+ // Assign IDs
194
+ for (const [filePath, _] of this.nodes) {
195
+ const relative = path.relative(this.rootDir, filePath);
196
+ const id = `N${idCounter++}`;
197
+ fileToId.set(filePath, id);
198
+ // Escape quotes in label
199
+ const label = relative.replace(/"/g, "'");
200
+ lines.push(` ${id}["${label}"]`);
201
+ }
202
+ // Add Edges
203
+ for (const [filePath, node] of this.nodes) {
204
+ const sourceId = fileToId.get(filePath);
205
+ for (const importPath of node.imports) {
206
+ const targetId = fileToId.get(importPath);
207
+ if (sourceId && targetId) {
208
+ lines.push(` ${sourceId} --> ${targetId}`);
209
+ }
210
+ }
211
+ }
212
+ return lines.join('\\n');
213
+ }
214
+ }
@@ -0,0 +1,79 @@
1
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
2
+ import { ProjectKnowledgeGraph } from './graph.js';
3
+ import * as fs from 'fs/promises';
4
+ vi.mock('fs/promises');
5
+ describe('ProjectKnowledgeGraph', () => {
6
+ let graph;
7
+ beforeEach(() => {
8
+ graph = new ProjectKnowledgeGraph();
9
+ vi.resetAllMocks();
10
+ });
11
+ it('should ignore imports in comments', async () => {
12
+ const mockFiles = ['/app/index.ts', '/app/utils.ts', '/app/oldUtils.ts'];
13
+ const mockContentIndex = `
14
+ import { something } from './utils';
15
+ // import { oldThing } from './oldUtils';
16
+ /* import { other } from './other' */
17
+ `;
18
+ const mockContentUtils = 'export const something = 1;';
19
+ fs.readdir.mockResolvedValue([
20
+ { name: 'index.ts', isDirectory: () => false },
21
+ { name: 'utils.ts', isDirectory: () => false },
22
+ { name: 'oldUtils.ts', isDirectory: () => false }
23
+ ]);
24
+ fs.readFile.mockImplementation(async (path) => {
25
+ if (path.includes('index.ts'))
26
+ return mockContentIndex;
27
+ return '';
28
+ });
29
+ // Mock resolvePath behavior indirectly by mocking existing files check in graph.build logic
30
+ // But since graph.ts uses fs.readdir recursively, we need to mock that structure.
31
+ // For simplicity in this unit test, we'll mock 'getAllFiles' if it were public,
32
+ // but since it's private, we have to mock fs structure carefully or rely on the implementation.
33
+ // Let's rely on the fact that build calls getAllFiles which calls readdir.
34
+ // We need to ensure 'utils.ts' and 'oldUtils.ts' resolution is tested.
35
+ // Actually, since we mock readFile, the file existence check in resolvePath uses "this.nodes.has".
36
+ // "this.nodes" is populated by getAllFiles.
37
+ // So we need getAllFiles to return both index.ts and utils.ts.
38
+ // And NOT oldUtils.ts so we can see if it tries to resolve it?
39
+ // Actually, if it tries to resolve 'oldUtils', it might fail if not in nodes.
40
+ // But the bug is that it SHOULD NOT even try to resolve 'oldUtils' because it's commented out.
41
+ await graph.build('/app');
42
+ const relationships = graph.getRelationships('/app/index.ts');
43
+ expect(relationships?.imports).toContain('utils.ts');
44
+ expect(relationships?.imports).not.toContain('oldUtils.ts');
45
+ });
46
+ it('should resolve .js imports to .ts files', async () => {
47
+ const mockContentIndex = `import { something } from './lib.js';`;
48
+ fs.readdir.mockResolvedValue([
49
+ { name: 'index.ts', isDirectory: () => false },
50
+ { name: 'lib.ts', isDirectory: () => false }
51
+ ]);
52
+ fs.readFile.mockImplementation(async (filePath) => {
53
+ if (filePath.endsWith('index.ts'))
54
+ return mockContentIndex;
55
+ return '';
56
+ });
57
+ await graph.build('/app');
58
+ const relationships = graph.getRelationships('/app/index.ts');
59
+ // The output of getRelationships.imports is relative paths.
60
+ // If imports ./lib.js, and we have lib.ts, it should resolve to /app/lib.ts
61
+ // And path.relative('/app', '/app/lib.ts') is 'lib.ts'
62
+ expect(relationships?.imports).toContain('lib.ts');
63
+ });
64
+ it('should resolve .js imports to .jsx files', async () => {
65
+ const mockContentIndex = `import { Button } from './Button.js';`;
66
+ fs.readdir.mockResolvedValue([
67
+ { name: 'index.js', isDirectory: () => false },
68
+ { name: 'Button.jsx', isDirectory: () => false }
69
+ ]);
70
+ fs.readFile.mockImplementation(async (filePath) => {
71
+ if (filePath.endsWith('index.js'))
72
+ return mockContentIndex;
73
+ return '';
74
+ });
75
+ await graph.build('/app');
76
+ const relationships = graph.getRelationships('/app/index.js');
77
+ expect(relationships?.imports).toContain('Button.jsx');
78
+ });
79
+ });
@@ -0,0 +1,63 @@
1
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
2
+ import { ProjectKnowledgeGraph } from './graph.js';
3
+ import * as fs from 'fs/promises';
4
+ vi.mock('fs/promises');
5
+ describe('ProjectKnowledgeGraph Reproduction', () => {
6
+ let graph;
7
+ beforeEach(() => {
8
+ graph = new ProjectKnowledgeGraph();
9
+ vi.resetAllMocks();
10
+ });
11
+ it('should resolve absolute imports from project root', async () => {
12
+ // Scenario: /app/src/feature/a.ts imports 'src/shared/b.ts'
13
+ const mockContentA = `import { b } from 'src/shared/b';`;
14
+ fs.readdir.mockResolvedValue([
15
+ { name: 'src', isDirectory: () => true },
16
+ { name: 'feature', isDirectory: () => true },
17
+ { name: 'shared', isDirectory: () => true },
18
+ { name: 'a.ts', isDirectory: () => false },
19
+ { name: 'b.ts', isDirectory: () => false }
20
+ ]);
21
+ // Mock getAllFiles behavior by manually setting up the file system structure conceptually
22
+ // Since getAllFiles is recursive and hard to mock perfectly with just readdir,
23
+ // we'll focus on the result of getAllFiles which populates the graph.
24
+ // Wait, the test uses the REAL getAllFiles, so we must mock readdir correctly.
25
+ // Let's simplify: Flat structure for test.
26
+ // Root: /app
27
+ // Files: /app/a.ts, /app/b.ts
28
+ // a.ts content: "import ... from 'b'" (no dot)
29
+ // OR
30
+ // a.ts content: "import ... from 'app/b'" (if root is /)
31
+ // Let's try the 'src/...' pattern which is common.
32
+ // Root: /root
33
+ // File: /root/src/index.ts -> imports 'src/utils'
34
+ // File: /root/src/utils.ts
35
+ const rootDir = '/root';
36
+ const indexFile = '/root/src/index.ts';
37
+ const utilsFile = '/root/src/utils.ts';
38
+ // Mock readdir to simulate:
39
+ // /root -> [src]
40
+ // /root/src -> [index.ts, utils.ts]
41
+ fs.readdir.mockImplementation(async (dir) => {
42
+ if (dir === '/root')
43
+ return [{ name: 'src', isDirectory: () => true }];
44
+ if (dir === '/root/src')
45
+ return [
46
+ { name: 'index.ts', isDirectory: () => false },
47
+ { name: 'utils.ts', isDirectory: () => false }
48
+ ];
49
+ return [];
50
+ });
51
+ fs.readFile.mockImplementation(async (filePath) => {
52
+ if (filePath === indexFile)
53
+ return `import { u } from 'src/utils';`;
54
+ return '';
55
+ });
56
+ await graph.build(rootDir);
57
+ const relationships = graph.getRelationships(indexFile);
58
+ // We expect 'src/utils.ts' to be in imports.
59
+ // Note: graph.getRelationships returns relative paths.
60
+ // relative('/root', '/root/src/utils.ts') -> 'src/utils.ts'
61
+ expect(relationships?.imports).toContain('src/utils.ts');
62
+ });
63
+ });
package/dist/index.js ADDED
@@ -0,0 +1,33 @@
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 { SequentialThinkingServer } from './lib.js';
5
+ import { ProjectKnowledgeGraph } from './graph.js';
6
+ import { NotesManager } from './notes.js';
7
+ import { registerThinkingTools } from './tools/thinking.js';
8
+ import { registerWebTools } from './tools/web.js';
9
+ import { registerFileSystemTools } from './tools/filesystem.js';
10
+ import { registerGraphTools } from './tools/graph.js';
11
+ import { registerNoteTools } from './tools/notes.js';
12
+ const server = new McpServer({
13
+ name: "sequential-thinking-server",
14
+ version: "2026.1.18",
15
+ });
16
+ const thinkingServer = new SequentialThinkingServer(process.env.THOUGHTS_STORAGE_PATH || 'thoughts_history.json', parseInt(process.env.THOUGHT_DELAY_MS || '0', 10));
17
+ const knowledgeGraph = new ProjectKnowledgeGraph();
18
+ const notesManager = new NotesManager(process.env.NOTES_STORAGE_PATH || 'project_notes.json');
19
+ // Register tools
20
+ registerThinkingTools(server, thinkingServer);
21
+ registerWebTools(server);
22
+ registerFileSystemTools(server);
23
+ registerGraphTools(server, knowledgeGraph);
24
+ registerNoteTools(server, notesManager);
25
+ async function runServer() {
26
+ const transport = new StdioServerTransport();
27
+ await server.connect(transport);
28
+ console.error("Sequential Thinking MCP Server (Extended) running on stdio");
29
+ }
30
+ runServer().catch((error) => {
31
+ console.error("Fatal error running server:", error);
32
+ process.exit(1);
33
+ });