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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/sequentialthinking/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,12 @@ Add this to your `claude_desktop_config.json`:
}
```

To disable logging of thought information set env var: `DISABLE_THOUGHT_LOGGING` to `true`.
### Environment Variables

| Variable | Description | Default |
|----------|-------------|---------|
| `DISABLE_THOUGHT_LOGGING` | Set to `true` to disable logging thought output to stderr | `false` |
| `SEQUENTIAL_THINKING_MAX_HISTORY` | Maximum number of thoughts retained in memory. Oldest thoughts are trimmed when exceeded. | `1000` |
Comment

### Usage with VS Code
Expand Down
75 changes: 75 additions & 0 deletions src/sequentialthinking/__tests__/lib.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,81 @@ describe('SequentialThinkingServer', () => {
});
});

describe('clearHistory', () => {
it('should clear all thoughts and branches', () => {
server.processThought({
thought: 'First', thoughtNumber: 1, totalThoughts: 3, nextThoughtNeeded: true
});
server.processThought({
thought: 'Branch', thoughtNumber: 2, totalThoughts: 3, nextThoughtNeeded: true,
branchFromThought: 1, branchId: 'b1'
});

const result = server.clearHistory();
const data = JSON.parse(result.content[0].text);

expect(data.cleared).toBe(true);
expect(data.previousThoughtCount).toBe(2);
expect(data.previousBranchCount).toBe(1);

// Verify history is actually empty
const nextResult = server.processThought({
thought: 'After clear', thoughtNumber: 1, totalThoughts: 1, nextThoughtNeeded: false
});
const nextData = JSON.parse(nextResult.content[0].text);
expect(nextData.thoughtHistoryLength).toBe(1);
expect(nextData.branches).toEqual([]);
});
});

describe('memory management', () => {
it('should trim history when exceeding max limit', () => {
// Default max is 1000, but we can test the trimming behavior
// by adding more thoughts than the limit
const maxHistory = 1000;
for (let i = 1; i <= maxHistory + 50; i++) {
server.processThought({
thought: `Thought ${i}`,
thoughtNumber: i,
totalThoughts: maxHistory + 50,
nextThoughtNeeded: i < maxHistory + 50
});
}

const lastResult = server.processThought({
thought: 'Final check',
thoughtNumber: maxHistory + 51,
totalThoughts: maxHistory + 51,
nextThoughtNeeded: false
});
const data = JSON.parse(lastResult.content[0].text);
// History should be capped at maxHistory
expect(data.thoughtHistoryLength).toBeLessThanOrEqual(maxHistory);
});

it('should respect SEQUENTIAL_THINKING_MAX_HISTORY env var', () => {
process.env.SEQUENTIAL_THINKING_MAX_HISTORY = '5';
const limitedServer = new SequentialThinkingServer();

for (let i = 1; i <= 10; i++) {
limitedServer.processThought({
thought: `Thought ${i}`,
thoughtNumber: i,
totalThoughts: 10,
nextThoughtNeeded: i < 10
});
}

const result = limitedServer.processThought({
thought: 'Check', thoughtNumber: 11, totalThoughts: 11, nextThoughtNeeded: false
});
const data = JSON.parse(result.content[0].text);
expect(data.thoughtHistoryLength).toBeLessThanOrEqual(5);

delete process.env.SEQUENTIAL_THINKING_MAX_HISTORY;
});
});

describe('processThought - with logging enabled', () => {
let serverWithLogging: SequentialThinkingServer;

Expand Down
15 changes: 15 additions & 0 deletions src/sequentialthinking/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,21 @@ You should:
}
);

server.registerTool(
"sequentialthinking_clear",
{
title: "Clear Thinking History",
description:
"Clears all stored thought history and branch data to free memory. " +
"Use this after completing a thinking session to prevent memory buildup " +
"in long-running server instances.",
inputSchema: {},
},
async () => {
return thinkingServer.clearHistory();
}
);

async function runServer() {
const transport = new StdioServerTransport();
await server.connect(transport);
Expand Down
35 changes: 35 additions & 0 deletions src/sequentialthinking/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,20 @@ export interface ThoughtData {
nextThoughtNeeded: boolean;
}

// Default maximum number of thoughts to retain in history.
// Can be overridden via the SEQUENTIAL_THINKING_MAX_HISTORY environment variable.
const DEFAULT_MAX_HISTORY = 1000;

export class SequentialThinkingServer {
private thoughtHistory: ThoughtData[] = [];
private branches: Record<string, ThoughtData[]> = {};
private disableThoughtLogging: boolean;
private maxHistory: number;

constructor() {
this.disableThoughtLogging = (process.env.DISABLE_THOUGHT_LOGGING || "").toLowerCase() === "true";
const envMax = parseInt(process.env.SEQUENTIAL_THINKING_MAX_HISTORY || "", 10);
this.maxHistory = Number.isFinite(envMax) && envMax > 0 ? envMax : DEFAULT_MAX_HISTORY;
}

private formatThought(thoughtData: ThoughtData): string {
Expand Down Expand Up @@ -49,6 +56,27 @@ export class SequentialThinkingServer {
└${border}┘`;
}

/**
* Clears all stored thought history and branch data.
* Useful for freeing memory in long-running sessions.
*/
public clearHistory(): { content: Array<{ type: "text"; text: string }> } {
const previousLength = this.thoughtHistory.length;
const previousBranches = Object.keys(this.branches).length;
this.thoughtHistory = [];
this.branches = {};
return {
content: [{
type: "text" as const,
text: JSON.stringify({
cleared: true,
previousThoughtCount: previousLength,
previousBranchCount: previousBranches
}, null, 2)
}]
};
}

public processThought(input: ThoughtData): { content: Array<{ type: "text"; text: string }>; isError?: boolean } {
try {
// Validation happens at the tool registration layer via Zod
Expand All @@ -59,6 +87,13 @@ export class SequentialThinkingServer {

this.thoughtHistory.push(input);

// Trim history when it exceeds the configured maximum to prevent
// unbounded memory growth during long-running sessions (see #2912).
if (this.thoughtHistory.length > this.maxHistory) {
const excess = this.thoughtHistory.length - this.maxHistory;
this.thoughtHistory.splice(0, excess);
}

if (input.branchFromThought && input.branchId) {
if (!this.branches[input.branchId]) {
this.branches[input.branchId] = [];
Expand Down