@gotza02/sequential-thinking 2026.2.22 β†’ 2026.2.23

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 CHANGED
@@ -1,161 +1,96 @@
1
- # Sequential Thinking MCP (Extended) 🧠
1
+ # @gotza02/sequential-thinking MCP Server
2
2
 
3
- Advanced MCP Server enabling AI to act as a Software Engineer with Deep Thinking, Codebase Graph Analysis, and Persistent Memory.
3
+ A comprehensive Model Context Protocol (MCP) server that empowers AI models with **Sequential Thinking**, **Web Research**, **Project Knowledge Graph**, and **Persistent Memory** capabilities.
4
4
 
5
- ## ✨ Key Features
6
- - **Deep Thinking:** Step-by-step reasoning with auto-correction (Loop Breaker).
7
- - **Code Intelligence:** Dependency graph mapping (`build_project_graph`) with **Incremental Caching** & surgical code editing.
8
- - **Memory:** Long-term project notes, reusable code database, and thought history.
9
- - **Research:** Integrated Web search (Brave/Exa) & webpage reading.
10
- - **REST API:** Built-in HTTP server wrapper for easy integration with web tools and external services.
5
+ ## Features
11
6
 
12
- ## πŸš€ Quick Setup
7
+ - **🧠 Sequential Thinking:** Structured reasoning engine with block-based context management (Analysis -> Planning -> Execution -> Observation).
8
+ - **🌐 Web Research:** Built-in web search (Brave, Exa, Google) and webpage reading with ad-blocking/markdown conversion.
9
+ - **πŸ•ΈοΈ Project Knowledge Graph:** Automatically scans and maps code dependencies (imports/exports) to understand project architecture.
10
+ - **πŸ“ Persistent Notes:** Save and retrieve long-term project notes.
11
+ - **πŸ’Ύ Code Database:** Store and search reusable code snippets.
12
+ - **πŸ› οΈ File & Code Tools:** Safe file operations and code analysis utilities.
13
13
 
14
- ### 1. Installation
15
- **Option A: Run Temporarily (Recommended for testing)**
16
- ```bash
17
- npx -y @gotza02/sequential-thinking
18
- ```
14
+ ## Usage
19
15
 
20
- **Option B: Install Globally**
21
- ```bash
22
- npm install -g @gotza02/sequential-thinking
23
- ```
16
+ ### Method 1: Using `npx` (Recommended)
24
17
 
25
- ### 2. Configuration (Gemini/Claude)
26
- Add this to your MCP settings (`~/.gemini/settings.json` or `claude_desktop_config.json`).
18
+ Run the server directly without global installation. This is best for Claude Desktop or other MCP clients.
27
19
 
28
- **For npx usage:**
29
- ```json
30
- {
31
- "mcpServers": {
32
- "smartagent": {
33
- "command": "npx",
34
- "args": ["-y", "@gotza02/sequential-thinking"],
35
- "env": { "THOUGHTS_STORAGE_PATH": "thoughts.json" }
36
- }
37
- }
38
- }
39
- ```
20
+ #### Configuration for Claude Desktop (`claude_desktop_config.json`)
21
+
22
+ To use the **Web Search** features, you must provide at least one API key.
40
23
 
41
- **For npm usage:**
42
- *(Find path with: `npm root -g`)*
43
24
  ```json
44
25
  {
45
26
  "mcpServers": {
46
- "smartagent": {
47
- "command": "node",
48
- "args": ["/PATH/TO/node_modules/@gotza02/sequential-thinking/dist/index.js"]
27
+ "sequential-thinking": {
28
+ "command": "npx",
29
+ "args": [
30
+ "-y",
31
+ "@gotza02/sequential-thinking"
32
+ ],
33
+ "env": {
34
+ "BRAVE_API_KEY": "your_brave_api_key_here",
35
+ "EXA_API_KEY": "your_exa_api_key_here",
36
+ "GOOGLE_SEARCH_API_KEY": "your_google_api_key",
37
+ "GOOGLE_SEARCH_CX": "your_google_cx_id"
38
+ }
49
39
  }
50
40
  }
51
41
  }
52
42
  ```
53
-
54
- ## πŸ”‘ Environment Variables
55
- | Variable | Description |
56
- |----------|-------------|
57
- | `THOUGHTS_STORAGE_PATH` | Path to save thought history (Required) |
58
- | `NOTES_STORAGE_PATH` | Path to save project notes |
59
- | `CODE_DB_PATH` | Path to save code snippets |
60
- | `BRAVE_API_KEY` | Optional: For Brave Search |
61
- | `EXA_API_KEY` | Optional: For Exa.ai Search |
62
-
63
- ## πŸ› οΈ Tools Summary
64
- - **Thinking:** `sequentialthinking`, `summarize_history`, `clear_thought_history`
65
- - **Code:** `build_project_graph`, `deep_code_analyze` (Context+Usages), `deep_code_edit`, `search_code` (Regex/Line Numbers)
66
- - **Memory:** `add_code_snippet`, `search_code_db`, `manage_notes`
67
- - **Web:** `web_search`, `read_webpage`, `fetch`
68
- - **System:** `read_file`, `write_file`, `edit_file`, `shell_execute`
69
-
70
- ---
71
-
72
- ## 🌐 HTTP REST API (Wrapper)
73
- In addition to the standard MCP protocol, this server includes an Express-based HTTP wrapper for RESTful access.
74
-
75
- ### Start the HTTP Server
76
- ```bash
77
- export PORT=3000
78
- npm run start:http
79
- ```
80
-
81
- ### Key Endpoints
82
- - `GET /health` - Service health check.
83
- - `POST /api/thinking/thought` - Process a new reasoning step.
84
- - `GET /api/thinking/history` - Retrieve reasoning history.
85
- - `POST /api/graph/build` - Scan codebase and build dependency graph.
86
- - `GET /api/notes` - List and manage persistent project notes.
87
-
88
- ---
89
-
90
- ## πŸ§ͺ Testing & Robustness
91
- This project is rigorously tested using **TestSprite** and **Vitest**.
92
- - **Current Status:** βœ… **100% Pass Rate** (10/10 core requirements).
93
- - **Test Isolation:** Graph tests now run in isolated temporary environments to prevent race conditions.
94
- - **Path Security:** Enhanced `validatePath` with symlink resolution (`realpathSync`) for stable operation in restricted environments like Termux/Android.
95
- - **Error Handling:** Improved HTTP status mapping and descriptive error reporting.
96
-
97
- ---
98
-
99
- ## πŸ€– Recommended System Instruction (The Ultimate Deep Engineer)
100
-
101
- Copy this into your AI's System Prompt to enable the Loop Breaker and Deep Thinking protocols:
102
-
103
- ```markdown
104
- # πŸ€– System Instruction: Ultimate Deep Engineer (Sequential Thinking Extended)
105
-
106
- You are a Senior AI Software Engineer equipped with the Sequential Thinking MCP Server (Extended Edition). Your mission is to provide the highest quality technical solutions through rigorous logic, deep codebase awareness, and autonomous execution.
107
-
108
- ## 🧠 CORE MANDATE: Deepest Thinking Protocol
109
- - **NEVER solve complex queries immediately.** You MUST use the `sequentialthinking` tool to structure your reasoning.
110
- - **Atomic Analysis:** Use `thoughtType: 'analysis'` to break every request into atomic requirements and constraints before proposing solutions.
111
- - **Mandatory Reflexion:** Use `thoughtType: 'reflexion'` frequently to critique your own logic, identify potential edge cases, and challenge your assumptions.
112
- - **Tree of Thoughts:** For critical architectural decisions, use branching to explore multiple paths (Conservative vs. Aggressive) and evaluate them using `thoughtType: 'evaluation'`.
113
- - **Loop Breaker (Auto-Correction):**
114
- - **Self-Monitoring:** If you detect yourself repeating a failed strategy or stuck in a loop -> **STOP**.
115
- - **Mandatory Branching:** You MUST create a new branch (`branchFromThought`) to explore a *radically different* approach.
116
- - **Explicit Statement:** State "Stuck detected. Branching to Strategy B..." before proceeding.
117
- - **Verified Completion:** Only set `nextThoughtNeeded: false` when the solution is definitive, verified, and follows project standards.
118
-
119
- ## πŸ› οΈ TOOL USAGE TRIGGERS (Context-Action Mapping)
120
- **You MUST follow these explicit triggers:**
121
-
122
- 1. **Unknown/New Project Context:**
123
- - **Trigger:** Start of session or new task.
124
- - **Action:** `build_project_graph` -> `read_file` (README/config).
125
- - **Goal:** Map the territory before moving.
126
-
127
- 2. **Coding/Refactoring Tasks:**
128
- - **Trigger:** Request to write or edit code.
129
- - **Action:** `deep_code_analyze` (on target files) -> `sequentialthinking` (Plan) -> `deep_code_edit` or `edit_file`.
130
- - **Constraint:** NEVER edit code without reading the file and its related context first.
131
-
132
- 3. **Debugging/Error Fixing:**
133
- - **Trigger:** User reports a bug or error.
134
- - **Action:** `read_file` (logs/code) OR `diagnose_error_screenshot` -> `sequentialthinking` (Hypothesis) -> `codebase_investigator` (Verification).
135
- - **Constraint:** Do not guess. Evidence first.
136
-
137
- 4. **Unknown Libraries/Docs:**
138
- - **Trigger:** Need to use a specific library/tool you are unsure about.
139
- - **Action:** `web_search` -> `read_webpage` or `get_code_context_exa`.
140
- - **Constraint:** Do not hallucinate APIs.
141
-
142
- ## πŸ’» DEEP CODING WORKFLOW
143
- 1. **Discovery:** `build_project_graph` -> Identify entry points and core logic.
144
- 2. **Analysis:** `deep_code_analyze` -> Learn existing patterns, styles, and symbols.
145
- 3. **Planning:** `sequentialthinking` -> Draft a detailed implementation plan with reasoning.
146
- 4. **Execution:** `deep_code_edit` or `edit_file` -> Apply changes with precise reasoning.
147
- 5. **Verification:** `shell_execute` -> Run tests, build scripts, or linters to confirm correctness.
148
-
149
- ## πŸ›‘ SAFETY & PRECISION
150
- - **Surgical Edits:** Prefer `edit_file` or `deep_code_edit` over `write_file` for existing files to minimize risk.
151
- - **Shell Responsibility:** Explain the purpose of any modification command before running `shell_execute`.
152
- - **No Assumptions:** If a library or configuration is unknown, use `web_search` or `read_webpage` to ingest documentation before implementation.
153
-
154
- ## πŸ“ PERSISTENT MEMORY
155
- - **Long-term Knowledge:** Use `manage_notes` to save architectural decisions, user preferences, project conventions, and "lessons learned" that must persist across sessions.
156
- - **Code Database:** Use `add_code_snippet` to store reusable patterns and `search_code_db` to retrieve them.
157
- - **Session Continuity:** Your thought history is saved. If you restart, review the history to maintain context.
158
- ```
43
+ *Note: You only need one of the search provider keys (Brave is recommended for general use).*
44
+
45
+ ### Method 2: Running Locally
46
+
47
+ 1. **Install dependencies:** `npm install`
48
+ 2. **Build:** `npm run build`
49
+ 3. **Config:** Point to the built `dist/index.js` file.
50
+
51
+ ## 🧰 Complete Tool List
52
+
53
+ ### 🧠 Core Thinking
54
+ * **`sequentialthinking`**: The main engine for problem-solving. Handles `analysis`, `planning`, `execution`, and `observation` steps.
55
+ * **`start_thinking_block`**: Start a new thinking context (e.g., switch from "Planning" to "Coding"). Helps prevent context pollution.
56
+ * **`get_thinking_blocks`**: View a summary of all discussion topics/blocks.
57
+ * **`summarize_history`**: Compress past thoughts to save token space.
58
+ * **`search_thoughts`**: Search through your thinking history.
59
+ * **`clear_thought_history`**: Reset the thinking state completely.
60
+
61
+ ### 🌐 Web & Research (Requires API Keys)
62
+ * **`web_search`**: Search the internet.
63
+ * *Requires:* `BRAVE_API_KEY` OR `EXA_API_KEY` OR (`GOOGLE_SEARCH_API_KEY` + `GOOGLE_SEARCH_CX`).
64
+ * **`fetch`**: Fetch raw content from a URL (GET/POST).
65
+ * **`read_webpage`**: Download a webpage and convert it to clean Markdown (removes ads/clutter).
66
+
67
+ ### πŸ•ΈοΈ Project Knowledge Graph
68
+ * **`build_project_graph`**: Scans the current directory to map file dependencies.
69
+ * **`force_rebuild_graph`**: Clears cache and rebuilds the graph from scratch.
70
+ * **`get_file_relationships`**: Shows what a specific file imports and what imports it.
71
+ * **`get_project_graph_summary`**: High-level stats of the project structure.
72
+ * **`get_project_graph_visualization`**: Generates a Mermaid diagram of the dependency graph.
73
+
74
+ ### πŸ“ Notes & Memory
75
+ * **`manage_notes`**: Create, read, update, or delete persistent notes.
76
+ * **`list_notes`**: List all saved notes.
77
+ * **`add_code_snippet`**: Save a useful piece of code to the database.
78
+ * **`search_code_db`**: Find saved code snippets by description or tags.
79
+
80
+ ### πŸ’» System & Coding
81
+ * (Include standard file system tools provided by the server context, typically `read_file`, `write_file`, etc., if exposed via this MCP, though often provided by the client itself. This server provides specialized graph/analysis tools.)
82
+
83
+ ## Configuration Variables
84
+
85
+ | Variable | Description | Required For |
86
+ | :--- | :--- | :--- |
87
+ | `BRAVE_API_KEY` | Brave Search API Token | `web_search` (Provider: Brave) |
88
+ | `EXA_API_KEY` | Exa.ai API Key | `web_search` (Provider: Exa) |
89
+ | `GOOGLE_SEARCH_API_KEY` | Google Custom Search API Key | `web_search` (Provider: Google) |
90
+ | `GOOGLE_SEARCH_CX` | Google Custom Search Engine ID | `web_search` (Provider: Google) |
91
+ | `THOUGHTS_STORAGE_PATH` | Path to save thinking history (default: `thoughts_history.json`) | Persistence |
92
+ | `NOTES_STORAGE_PATH` | Path to save notes (default: `project_notes.json`) | Persistence |
159
93
 
160
94
  ## License
161
- MIT - Developed by @gotza02/sequential-thinking
95
+
96
+ MIT
package/dist/graph.js CHANGED
@@ -252,8 +252,24 @@ export class ProjectKnowledgeGraph {
252
252
  }
253
253
  // Handle: from .module import func OR from package.module import func
254
254
  const fromImportMatches = content.matchAll(/^\s*from\s+([.a-zA-Z0-9_]+)\s+import/gm);
255
- for (const match of fromImportMatches)
256
- imports.push(match[1]);
255
+ for (const match of fromImportMatches) {
256
+ let imp = match[1];
257
+ // Convert Python relative import to path (e.g. .module -> ./module)
258
+ if (imp.startsWith('.')) {
259
+ const matchDots = imp.match(/^(\.+)(.*)/);
260
+ if (matchDots) {
261
+ const dots = matchDots[1].length;
262
+ const name = matchDots[2];
263
+ if (dots === 1) {
264
+ imp = `./${name}`;
265
+ }
266
+ else {
267
+ imp = `${'../'.repeat(dots - 1)}${name}`;
268
+ }
269
+ }
270
+ }
271
+ imports.push(imp);
272
+ }
257
273
  // 2. Python Symbols (Only top-level defs/classes to avoid nested methods)
258
274
  const topLevelFuncMatches = content.matchAll(/^def\s+([a-zA-Z0-9_]+)/gm);
259
275
  for (const match of topLevelFuncMatches)
package/dist/index.js CHANGED
@@ -1,4 +1,7 @@
1
1
  #!/usr/bin/env node
2
+ import { readFileSync } from 'fs';
3
+ import { fileURLToPath } from 'url';
4
+ import { dirname, join } from 'path';
2
5
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
6
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
7
  import { SequentialThinkingServer } from './lib.js';
@@ -13,9 +16,12 @@ import { registerNoteTools } from './tools/notes.js';
13
16
  import { registerCodingTools } from './tools/coding.js';
14
17
  import { registerCodeDbTools } from './tools/codestore.js';
15
18
  import { registerHumanTools } from './tools/human.js';
19
+ const __filename = fileURLToPath(import.meta.url);
20
+ const __dirname = dirname(__filename);
21
+ const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'));
16
22
  const server = new McpServer({
17
23
  name: "sequential-thinking-server",
18
- version: "2026.2.6",
24
+ version: pkg.version,
19
25
  });
20
26
  const thinkingServer = new SequentialThinkingServer(process.env.THOUGHTS_STORAGE_PATH || 'thoughts_history.json', parseInt(process.env.THOUGHT_DELAY_MS || '0', 10));
21
27
  const knowledgeGraph = new ProjectKnowledgeGraph();
@@ -39,3 +45,11 @@ runServer().catch((error) => {
39
45
  console.error("Fatal error running server:", error);
40
46
  process.exit(1);
41
47
  });
48
+ process.on('SIGINT', () => {
49
+ console.error("\nReceived SIGINT, shutting down...");
50
+ process.exit(0);
51
+ });
52
+ process.on('SIGTERM', () => {
53
+ console.error("\nReceived SIGTERM, shutting down...");
54
+ process.exit(0);
55
+ });
package/dist/lib.js CHANGED
@@ -81,16 +81,22 @@ export class SequentialThinkingServer {
81
81
  }
82
82
  }
83
83
  rebuildBlocks() {
84
+ const oldBlocksMap = new Map();
85
+ for (const b of this.blocks) {
86
+ oldBlocksMap.set(b.id, b);
87
+ }
88
+ this.blocks = [];
84
89
  const blockMap = new Map();
85
90
  for (const t of this.thoughtHistory) {
86
91
  const bid = t.blockId || 'default';
87
92
  if (!blockMap.has(bid)) {
93
+ const oldBlock = oldBlocksMap.get(bid);
88
94
  const newBlock = {
89
95
  id: bid,
90
- topic: t.thought.substring(0, 50),
91
- status: 'active',
96
+ topic: oldBlock ? oldBlock.topic : t.thought.substring(0, 50),
97
+ status: oldBlock ? oldBlock.status : 'active',
92
98
  thoughts: [],
93
- createdAt: new Date().toISOString(),
99
+ createdAt: oldBlock ? oldBlock.createdAt : new Date().toISOString(),
94
100
  updatedAt: new Date().toISOString()
95
101
  };
96
102
  blockMap.set(bid, newBlock);
@@ -99,7 +105,13 @@ export class SequentialThinkingServer {
99
105
  blockMap.get(bid).thoughts.push(t);
100
106
  }
101
107
  if (this.blocks.length > 0) {
102
- this.currentBlockId = this.blocks[this.blocks.length - 1].id;
108
+ // Ensure currentBlockId points to a valid block or the last one
109
+ if (!this.blocks.find(b => b.id === this.currentBlockId)) {
110
+ this.currentBlockId = this.blocks[this.blocks.length - 1].id;
111
+ }
112
+ }
113
+ else {
114
+ this.currentBlockId = null;
103
115
  }
104
116
  }
105
117
  rebuildBranches() {
@@ -8,44 +8,68 @@ export function registerCodeDbTools(server, db) {
8
8
  description: z.string().describe("What this code does and why it is useful"),
9
9
  tags: z.array(z.string()).optional().default([]).describe("Tags for categorization")
10
10
  }, async (args) => {
11
- const snippet = await db.addSnippet(args);
12
- return {
13
- content: [{ type: "text", text: `Snippet saved with ID: ${snippet.id}` }]
14
- };
11
+ try {
12
+ const snippet = await db.addSnippet(args);
13
+ return {
14
+ content: [{ type: "text", text: `Snippet saved with ID: ${snippet.id}` }]
15
+ };
16
+ }
17
+ catch (error) {
18
+ return {
19
+ content: [{ type: "text", text: `Error adding snippet: ${error instanceof Error ? error.message : String(error)}` }],
20
+ isError: true
21
+ };
22
+ }
15
23
  });
16
24
  // 17. search_code_db
17
25
  server.tool("search_code_db", "Search the Code Database for existing snippets, patterns, or architectural knowledge.", {
18
26
  query: z.string().describe("Search query")
19
27
  }, async ({ query }) => {
20
- const results = await db.searchSnippets(query);
21
- const patterns = await db.listAllPatterns();
22
- let output = `--- CODE DATABASE RESULTS ---\n\n`;
23
- if (results.length > 0) {
24
- output += `SNIPPETS FOUND:\n`;
25
- results.forEach(s => {
26
- output += `ID: ${s.id} | ${s.title} (${s.language})\nDesc: ${s.description}\nCode:\n\`\`\`\n${s.code}\n\`\`\`\n\n`;
27
- });
28
- }
29
- const matchedPatterns = Object.entries(patterns).filter(([k, v]) => k.toLowerCase().includes(query.toLowerCase()) || v.toLowerCase().includes(query.toLowerCase()));
30
- if (matchedPatterns.length > 0) {
31
- output += `PATTERNS FOUND:\n`;
32
- matchedPatterns.forEach(([name, desc]) => {
33
- output += `- ${name}: ${desc}\n`;
34
- });
28
+ try {
29
+ const results = await db.searchSnippets(query);
30
+ const patterns = await db.listAllPatterns();
31
+ let output = `--- CODE DATABASE RESULTS ---\n\n`;
32
+ if (results.length > 0) {
33
+ output += `SNIPPETS FOUND:\n`;
34
+ results.forEach(s => {
35
+ output += `ID: ${s.id} | ${s.title} (${s.language})\nDesc: ${s.description}\nCode:\n\`\`\`\n${s.code}\n\`\`\`\n\n`;
36
+ });
37
+ }
38
+ const matchedPatterns = Object.entries(patterns).filter(([k, v]) => k.toLowerCase().includes(query.toLowerCase()) || v.toLowerCase().includes(query.toLowerCase()));
39
+ if (matchedPatterns.length > 0) {
40
+ output += `PATTERNS FOUND:\n`;
41
+ matchedPatterns.forEach(([name, desc]) => {
42
+ output += `- ${name}: ${desc}\n`;
43
+ });
44
+ }
45
+ if (results.length === 0 && matchedPatterns.length === 0) {
46
+ output += "No results found in the Code Database.";
47
+ }
48
+ return { content: [{ type: "text", text: output }] };
35
49
  }
36
- if (results.length === 0 && matchedPatterns.length === 0) {
37
- output += "No results found in the Code Database.";
50
+ catch (error) {
51
+ return {
52
+ content: [{ type: "text", text: `Error searching code DB: ${error instanceof Error ? error.message : String(error)}` }],
53
+ isError: true
54
+ };
38
55
  }
39
- return { content: [{ type: "text", text: output }] };
40
56
  });
41
57
  // 18. learn_architecture_pattern
42
58
  server.tool("learn_architecture_pattern", "Store a high-level architectural or logic pattern in the Code DB (e.g., 'auth-flow', 'error-handling-strategy').", {
43
59
  name: z.string().describe("Name of the pattern"),
44
60
  description: z.string().describe("Detailed description of the pattern and how it applies to this project")
45
61
  }, async ({ name, description }) => {
46
- await db.learnPattern(name, description);
47
- return {
48
- content: [{ type: "text", text: `Pattern '${name}' learned and stored.` }]
49
- };
62
+ try {
63
+ await db.learnPattern(name, description);
64
+ return {
65
+ content: [{ type: "text", text: `Pattern '${name}' learned and stored.` }]
66
+ };
67
+ }
68
+ catch (error) {
69
+ return {
70
+ content: [{ type: "text", text: `Error learning pattern: ${error instanceof Error ? error.message : String(error)}` }],
71
+ isError: true
72
+ };
73
+ }
50
74
  });
51
75
  }
@@ -30,6 +30,34 @@ export class HumanInteractionManager {
30
30
  status: 'pending'
31
31
  };
32
32
  this.interactions.set(id, interaction);
33
+ // Handle timeout if specified
34
+ if (params.timeoutMs && params.timeoutMs > 0) {
35
+ setTimeout(() => {
36
+ const current = this.interactions.get(id);
37
+ if (current && current.status === 'pending') {
38
+ if (current.defaultOption) {
39
+ current.status = 'answered';
40
+ current.response = current.defaultOption;
41
+ current.respondedAt = new Date().toISOString();
42
+ // Also resolve pending callbacks if any (future proofing)
43
+ const callback = this.pendingCallbacks.get(id);
44
+ if (callback) {
45
+ callback.resolve(current.response);
46
+ this.pendingCallbacks.delete(id);
47
+ }
48
+ }
49
+ else {
50
+ current.status = 'timeout';
51
+ // Resolve pending callback with null or specific timeout error if supported
52
+ const callback = this.pendingCallbacks.get(id);
53
+ if (callback) {
54
+ callback.resolve(null); // Null indicates timeout/no answer
55
+ this.pendingCallbacks.delete(id);
56
+ }
57
+ }
58
+ }
59
+ }, params.timeoutMs);
60
+ }
33
61
  return interaction;
34
62
  }
35
63
  /**
@@ -173,133 +201,176 @@ Urgency Levels:
173
201
  options: z.array(z.string()).optional()
174
202
  .describe("Available options for 'choice' type questions"),
175
203
  defaultOption: z.string().optional()
176
- .describe("Default option if human doesn't respond (for non-critical)")
177
- }, async ({ question, questionType, urgency, context, options, defaultOption }) => {
178
- // Validate options for choice type
179
- if (questionType === 'choice' && (!options || options.length < 2)) {
204
+ .describe("Default option if human doesn't respond (for non-critical)"),
205
+ timeoutMs: z.number().int().min(1000).optional()
206
+ .describe("Timeout in milliseconds (default: no timeout)")
207
+ }, async ({ question, questionType, urgency, context, options, defaultOption, timeoutMs }) => {
208
+ try {
209
+ // Validate options for choice type
210
+ if (questionType === 'choice' && (!options || options.length < 2)) {
211
+ return {
212
+ content: [{
213
+ type: "text",
214
+ text: "Error: 'choice' question type requires at least 2 options"
215
+ }],
216
+ isError: true
217
+ };
218
+ }
219
+ const interaction = await manager.createInteraction({
220
+ questionType,
221
+ urgency,
222
+ question,
223
+ context,
224
+ options,
225
+ defaultOption,
226
+ timeoutMs
227
+ });
228
+ const formatted = manager.formatInteraction(interaction);
180
229
  return {
181
230
  content: [{
182
231
  type: "text",
183
- text: "Error: 'choice' question type requires at least 2 options"
184
- }],
232
+ text: formatted + `\n\n⏳ Status: WAITING FOR HUMAN RESPONSE\n\nThe AI should pause this line of action until a response is received via 'respond_to_human' or 'get_pending_questions'.`
233
+ }]
234
+ };
235
+ }
236
+ catch (error) {
237
+ return {
238
+ content: [{ type: "text", text: `Error asking human: ${error instanceof Error ? error.message : String(error)}` }],
185
239
  isError: true
186
240
  };
187
241
  }
188
- const interaction = await manager.createInteraction({
189
- questionType,
190
- urgency,
191
- question,
192
- context,
193
- options,
194
- defaultOption
195
- });
196
- const formatted = manager.formatInteraction(interaction);
197
- return {
198
- content: [{
199
- type: "text",
200
- text: formatted + `\n\n⏳ Status: WAITING FOR HUMAN RESPONSE\n\nThe AI should pause this line of action until a response is received via 'respond_to_human' or 'get_pending_questions'.`
201
- }]
202
- };
203
242
  });
204
243
  // --- respond_to_human Tool ---
205
244
  server.tool("respond_to_human", "Provide a response to a pending human-in-the-loop question. Use this after receiving human input.", {
206
245
  interactionId: z.string().describe("The interaction ID from ask_human"),
207
246
  response: z.string().describe("The human's response to the question")
208
247
  }, async ({ interactionId, response }) => {
209
- const interaction = manager.recordResponse(interactionId, response);
210
- if (!interaction) {
248
+ try {
249
+ const interaction = manager.recordResponse(interactionId, response);
250
+ if (!interaction) {
251
+ return {
252
+ content: [{
253
+ type: "text",
254
+ text: `Error: No pending interaction found with ID "${interactionId}"`
255
+ }],
256
+ isError: true
257
+ };
258
+ }
211
259
  return {
212
260
  content: [{
213
261
  type: "text",
214
- text: `Error: No pending interaction found with ID "${interactionId}"`
215
- }],
262
+ text: `βœ… Response recorded for interaction ${interactionId}\n\n` +
263
+ `Question: ${interaction.question}\n` +
264
+ `Response: ${response}\n` +
265
+ `Responded at: ${interaction.respondedAt}\n\n` +
266
+ `The AI can now proceed based on this human decision.`
267
+ }]
268
+ };
269
+ }
270
+ catch (error) {
271
+ return {
272
+ content: [{ type: "text", text: `Error responding to human: ${error instanceof Error ? error.message : String(error)}` }],
216
273
  isError: true
217
274
  };
218
275
  }
219
- return {
220
- content: [{
221
- type: "text",
222
- text: `βœ… Response recorded for interaction ${interactionId}\n\n` +
223
- `Question: ${interaction.question}\n` +
224
- `Response: ${response}\n` +
225
- `Responded at: ${interaction.respondedAt}\n\n` +
226
- `The AI can now proceed based on this human decision.`
227
- }]
228
- };
229
276
  });
230
277
  // --- get_pending_questions Tool ---
231
278
  server.tool("get_pending_questions", "Get all pending human-in-the-loop questions that need responses.", {}, async () => {
232
- const pending = manager.getPendingInteractions();
233
- if (pending.length === 0) {
279
+ try {
280
+ const pending = manager.getPendingInteractions();
281
+ if (pending.length === 0) {
282
+ return {
283
+ content: [{
284
+ type: "text",
285
+ text: "βœ… No pending questions. All human interactions have been addressed."
286
+ }]
287
+ };
288
+ }
289
+ let output = `πŸ“‹ PENDING HUMAN INTERACTIONS (${pending.length})\n`;
290
+ output += `${'═'.repeat(60)}\n\n`;
291
+ for (const interaction of pending) {
292
+ output += manager.formatInteraction(interaction);
293
+ output += '\n';
294
+ }
234
295
  return {
235
296
  content: [{
236
297
  type: "text",
237
- text: "βœ… No pending questions. All human interactions have been addressed."
298
+ text: output
238
299
  }]
239
300
  };
240
301
  }
241
- let output = `πŸ“‹ PENDING HUMAN INTERACTIONS (${pending.length})\n`;
242
- output += `${'═'.repeat(60)}\n\n`;
243
- for (const interaction of pending) {
244
- output += manager.formatInteraction(interaction);
245
- output += '\n';
302
+ catch (error) {
303
+ return {
304
+ content: [{ type: "text", text: `Error getting pending questions: ${error instanceof Error ? error.message : String(error)}` }],
305
+ isError: true
306
+ };
246
307
  }
247
- return {
248
- content: [{
249
- type: "text",
250
- text: output
251
- }]
252
- };
253
308
  });
254
309
  // --- get_interaction_history Tool ---
255
310
  server.tool("get_interaction_history", "Get the history of human-AI interactions for context and learning.", {
256
311
  limit: z.number().int().min(1).max(100).default(20)
257
312
  .describe("Maximum number of interactions to return")
258
313
  }, async ({ limit }) => {
259
- const history = manager.getHistory(limit);
260
- if (history.length === 0) {
314
+ try {
315
+ const history = manager.getHistory(limit);
316
+ if (history.length === 0) {
317
+ return {
318
+ content: [{
319
+ type: "text",
320
+ text: "No interaction history found."
321
+ }]
322
+ };
323
+ }
324
+ let output = `πŸ“œ INTERACTION HISTORY (${history.length} most recent)\n`;
325
+ output += `${'═'.repeat(60)}\n\n`;
326
+ for (const interaction of history) {
327
+ const statusEmoji = {
328
+ pending: '⏳',
329
+ answered: 'βœ…',
330
+ timeout: '⏱️',
331
+ skipped: '⏭️'
332
+ };
333
+ output += `${statusEmoji[interaction.status]} [${interaction.id}]\n`;
334
+ output += ` Type: ${interaction.questionType} | Urgency: ${interaction.urgency}\n`;
335
+ output += ` Q: ${interaction.question.substring(0, 80)}${interaction.question.length > 80 ? '...' : ''}\n`;
336
+ if (interaction.response) {
337
+ output += ` A: ${interaction.response.substring(0, 80)}${interaction.response.length > 80 ? '...' : ''}\n`;
338
+ }
339
+ output += ` Time: ${interaction.timestamp}\n\n`;
340
+ }
261
341
  return {
262
342
  content: [{
263
343
  type: "text",
264
- text: "No interaction history found."
344
+ text: output
265
345
  }]
266
346
  };
267
347
  }
268
- let output = `πŸ“œ INTERACTION HISTORY (${history.length} most recent)\n`;
269
- output += `${'═'.repeat(60)}\n\n`;
270
- for (const interaction of history) {
271
- const statusEmoji = {
272
- pending: '⏳',
273
- answered: 'βœ…',
274
- timeout: '⏱️',
275
- skipped: '⏭️'
348
+ catch (error) {
349
+ return {
350
+ content: [{ type: "text", text: `Error getting history: ${error instanceof Error ? error.message : String(error)}` }],
351
+ isError: true
276
352
  };
277
- output += `${statusEmoji[interaction.status]} [${interaction.id}]\n`;
278
- output += ` Type: ${interaction.questionType} | Urgency: ${interaction.urgency}\n`;
279
- output += ` Q: ${interaction.question.substring(0, 80)}${interaction.question.length > 80 ? '...' : ''}\n`;
280
- if (interaction.response) {
281
- output += ` A: ${interaction.response.substring(0, 80)}${interaction.response.length > 80 ? '...' : ''}\n`;
282
- }
283
- output += ` Time: ${interaction.timestamp}\n\n`;
284
353
  }
285
- return {
286
- content: [{
287
- type: "text",
288
- text: output
289
- }]
290
- };
291
354
  });
292
355
  // --- clear_old_interactions Tool ---
293
356
  server.tool("clear_old_interactions", "Clear old interaction history to free up memory.", {
294
357
  hoursOld: z.number().int().min(1).default(24)
295
358
  .describe("Clear interactions older than this many hours")
296
359
  }, async ({ hoursOld }) => {
297
- const cleared = manager.clearOldInteractions(hoursOld);
298
- return {
299
- content: [{
300
- type: "text",
301
- text: `πŸ—‘οΈ Cleared ${cleared} interactions older than ${hoursOld} hours.`
302
- }]
303
- };
360
+ try {
361
+ const cleared = manager.clearOldInteractions(hoursOld);
362
+ return {
363
+ content: [{
364
+ type: "text",
365
+ text: `πŸ—‘οΈ Cleared ${cleared} interactions older than ${hoursOld} hours.`
366
+ }]
367
+ };
368
+ }
369
+ catch (error) {
370
+ return {
371
+ content: [{ type: "text", text: `Error clearing interactions: ${error instanceof Error ? error.message : String(error)}` }],
372
+ isError: true
373
+ };
374
+ }
304
375
  });
305
376
  }
@@ -14,22 +14,25 @@ export function registerNoteTools(server, notesManager) {
14
14
  }, async ({ action, title, content, tags, searchQuery, noteId, priority, expiresAt, includeExpired }) => {
15
15
  try {
16
16
  switch (action) {
17
- case 'add':
17
+ case 'add': {
18
18
  if (!title || !content) {
19
19
  return { content: [{ type: "text", text: "Error: 'title' and 'content' are required for add action." }], isError: true };
20
20
  }
21
21
  const newNote = await notesManager.addNote(title, content, tags, priority, expiresAt);
22
22
  return { content: [{ type: "text", text: `Note added successfully.\nID: ${newNote.id} (Priority: ${newNote.priority})` }] };
23
- case 'list':
23
+ }
24
+ case 'list': {
24
25
  const notes = await notesManager.listNotes(undefined, includeExpired);
25
26
  return { content: [{ type: "text", text: JSON.stringify(notes, null, 2) }] };
26
- case 'search':
27
+ }
28
+ case 'search': {
27
29
  if (!searchQuery) {
28
30
  return { content: [{ type: "text", text: "Error: 'searchQuery' is required for search action." }], isError: true };
29
31
  }
30
32
  const searchResults = await notesManager.searchNotes(searchQuery);
31
33
  return { content: [{ type: "text", text: searchResults.length > 0 ? JSON.stringify(searchResults, null, 2) : "No matching notes found." }] };
32
- case 'update':
34
+ }
35
+ case 'update': {
33
36
  if (!noteId) {
34
37
  return { content: [{ type: "text", text: "Error: 'noteId' is required for update action." }], isError: true };
35
38
  }
@@ -38,12 +41,14 @@ export function registerNoteTools(server, notesManager) {
38
41
  return { content: [{ type: "text", text: `Error: Note with ID ${noteId} not found.` }], isError: true };
39
42
  }
40
43
  return { content: [{ type: "text", text: `Note updated successfully.` }] };
41
- case 'delete':
44
+ }
45
+ case 'delete': {
42
46
  if (!noteId) {
43
47
  return { content: [{ type: "text", text: "Error: 'noteId' is required for delete action." }], isError: true };
44
48
  }
45
49
  const deleted = await notesManager.deleteNote(noteId);
46
50
  return { content: [{ type: "text", text: deleted ? "Note deleted successfully." : `Error: Note with ID ${noteId} not found.` }], isError: !deleted };
51
+ }
47
52
  default:
48
53
  return { content: [{ type: "text", text: `Error: Unknown action '${action}'` }], isError: true };
49
54
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gotza02/sequential-thinking",
3
- "version": "2026.2.22",
3
+ "version": "2026.2.23",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -29,7 +29,9 @@
29
29
  "start:http": "node dist/http-server.js",
30
30
  "prepare": "npm run build",
31
31
  "watch": "tsc --watch",
32
- "test": "vitest run --coverage"
32
+ "test": "vitest run --coverage",
33
+ "lint": "eslint src/**/*.ts",
34
+ "format": "prettier --write \"src/**/*.{ts,json}\""
33
35
  },
34
36
  "dependencies": {
35
37
  "@modelcontextprotocol/sdk": "^1.25.2",
@@ -50,6 +52,10 @@
50
52
  "@types/turndown": "^5.0.6",
51
53
  "@types/yargs": "^17.0.35",
52
54
  "@vitest/coverage-v8": "^3.2.4",
55
+ "eslint": "^8.57.0",
56
+ "@typescript-eslint/parser": "^7.1.1",
57
+ "@typescript-eslint/eslint-plugin": "^7.1.1",
58
+ "prettier": "^3.2.5",
53
59
  "shx": "^0.4.0",
54
60
  "vitest": "^3.2.4"
55
61
  }
@@ -1 +0,0 @@
1
- export {};
@@ -1,46 +0,0 @@
1
- import { ProjectKnowledgeGraph } from './graph.js';
2
- import * as fs from 'fs/promises';
3
- async function test() {
4
- const tempFile = 'temp_req.ts';
5
- await fs.writeFile(tempFile, 'import fs = require("fs");\nexport = fs;');
6
- try {
7
- const graph = new ProjectKnowledgeGraph();
8
- await graph.build(process.cwd());
9
- const rel = graph.getRelationships(tempFile);
10
- console.log("Imports:", rel?.imports);
11
- // Note: 'fs' is a built-in, so it might not be in 'imports' array unless resolved to a file.
12
- // But ProjectKnowledgeGraph parser ADDS it to 'imports' list initially.
13
- // finalizeFileNodes only keeps it if it resolves to a node OR if we assume internal logic keeps it?
14
- // Wait, 'finalizeFileNodes' logic:
15
- // for (const importPath of imports) {
16
- // resolved = resolvePath(...)
17
- // if (resolved && nodes.has(resolved)) { imports.push(resolved) }
18
- // }
19
- // It FILTERS out imports that don't resolve to files in the project!
20
- // So 'fs' will be DROPPED.
21
- // This makes verifying the PARSER hard via 'getRelationships'.
22
- // However, 'symbols' are kept.
23
- // 'import fs = require...' creates a symbol 'fs'?
24
- // The parser only pushes to 'imports' or 'symbols'.
25
- // Let's create a local file 'my_dep.ts' and import THAT.
26
- await fs.writeFile('my_dep.ts', 'export const x = 1;');
27
- await fs.writeFile(tempFile, 'import dep = require("./my_dep");');
28
- await graph.build(process.cwd());
29
- const rel2 = graph.getRelationships(tempFile);
30
- console.log("Imports 2:", rel2?.imports);
31
- if (rel2?.imports.some(i => i.includes('my_dep'))) {
32
- console.log("PASS: Found 'my_dep' import");
33
- }
34
- else {
35
- console.log("FAIL: 'my_dep' import missing");
36
- }
37
- }
38
- finally {
39
- await fs.unlink(tempFile);
40
- try {
41
- await fs.unlink('my_dep.ts');
42
- }
43
- catch { }
44
- }
45
- }
46
- test();