Skip to content

Commit e50b66f

Browse files
author
robertlestak
committed
make sequentialthinking server more resillient
1 parent 5e7cf25 commit e50b66f

File tree

2 files changed

+74
-5
lines changed

2 files changed

+74
-5
lines changed

src/sequentialthinking/index.ts

Lines changed: 61 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,19 @@ const server = new McpServer({
1111
version: "0.2.0",
1212
});
1313

14-
const thinkingServer = new SequentialThinkingServer();
14+
const thinkingSessions = new Map<string, SequentialThinkingServer>();
15+
16+
function getOrCreateThinkingServer(sessionId?: string): SequentialThinkingServer {
17+
if (!sessionId) {
18+
return new SequentialThinkingServer();
19+
}
20+
21+
if (!thinkingSessions.has(sessionId)) {
22+
thinkingSessions.set(sessionId, new SequentialThinkingServer());
23+
}
24+
25+
return thinkingSessions.get(sessionId)!;
26+
}
1527

1628
server.registerTool(
1729
"sequentialthinking",
@@ -91,6 +103,7 @@ You should:
91103
},
92104
},
93105
async (args) => {
106+
const thinkingServer = getOrCreateThinkingServer();
94107
const result = thinkingServer.processThought(args);
95108

96109
if (result.isError) {
@@ -111,28 +124,69 @@ async function runServer() {
111124
if (process.env.MCP_TRANSPORT === 'http') {
112125
const { createServer } = await import('http');
113126
const transports: Record<string, StreamableHTTPServerTransport> = {};
127+
const sessionTimeouts: Record<string, NodeJS.Timeout> = {};
128+
const SESSION_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes
129+
const MAX_BODY_SIZE = 10 * 1024 * 1024; // 10MB
130+
131+
function cleanupSession(sid: string) {
132+
delete transports[sid];
133+
thinkingSessions.delete(sid);
134+
if (sessionTimeouts[sid]) {
135+
clearTimeout(sessionTimeouts[sid]);
136+
delete sessionTimeouts[sid];
137+
}
138+
}
139+
140+
function resetSessionTimeout(sid: string) {
141+
if (sessionTimeouts[sid]) {
142+
clearTimeout(sessionTimeouts[sid]);
143+
}
144+
sessionTimeouts[sid] = setTimeout(() => cleanupSession(sid), SESSION_TIMEOUT_MS);
145+
}
114146

115147
const httpServer = createServer(async (req, res) => {
116148
const sessionId = req.headers['mcp-session-id'] as string | undefined;
117149

118150
if (req.method === 'POST') {
119-
let body = '';
120-
req.on('data', chunk => body += chunk);
151+
const chunks: Buffer[] = [];
152+
let totalSize = 0;
153+
154+
req.on('data', chunk => {
155+
totalSize += chunk.length;
156+
if (totalSize > MAX_BODY_SIZE) {
157+
req.destroy();
158+
res.writeHead(413);
159+
res.end('Request body too large');
160+
return;
161+
}
162+
chunks.push(chunk);
163+
});
164+
121165
req.on('end', async () => {
122-
const parsedBody = body.trim() ? JSON.parse(body) : undefined;
166+
let parsedBody;
167+
try {
168+
const body = Buffer.concat(chunks).toString();
169+
parsedBody = body.trim() ? JSON.parse(body) : undefined;
170+
} catch (error) {
171+
res.writeHead(400);
172+
res.end('Invalid JSON');
173+
return;
174+
}
123175

124176
let transport: StreamableHTTPServerTransport;
125177
if (sessionId && transports[sessionId]) {
126178
transport = transports[sessionId];
179+
resetSessionTimeout(sessionId);
127180
} else if (!sessionId) {
128181
transport = new StreamableHTTPServerTransport({
129182
sessionIdGenerator: () => crypto.randomUUID(),
130183
onsessioninitialized: (sid) => {
131184
transports[sid] = transport;
185+
resetSessionTimeout(sid);
132186
console.error('Session initialized:', sid);
133187
},
134188
onsessionclosed: (sid) => {
135-
delete transports[sid];
189+
cleanupSession(sid);
136190
console.error('Session closed:', sid);
137191
}
138192
});
@@ -151,6 +205,7 @@ async function runServer() {
151205
res.end('Invalid or missing session ID');
152206
return;
153207
}
208+
resetSessionTimeout(sessionId);
154209
await transports[sessionId].handleRequest(req, res);
155210
} else if (req.method === 'DELETE') {
156211
if (!sessionId || !transports[sessionId]) {
@@ -159,6 +214,7 @@ async function runServer() {
159214
return;
160215
}
161216
await transports[sessionId].handleRequest(req, res);
217+
cleanupSession(sessionId);
162218
} else {
163219
res.writeHead(405);
164220
res.end('Method not allowed');

src/sequentialthinking/lib.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,18 @@ export class SequentialThinkingServer {
1616
private thoughtHistory: ThoughtData[] = [];
1717
private branches: Record<string, ThoughtData[]> = {};
1818
private disableThoughtLogging: boolean;
19+
private readonly maxHistorySize: number = 1000;
1920

2021
constructor() {
2122
this.disableThoughtLogging = (process.env.DISABLE_THOUGHT_LOGGING || "").toLowerCase() === "true";
2223
}
2324

25+
private pruneHistory(): void {
26+
if (this.thoughtHistory.length > this.maxHistorySize) {
27+
this.thoughtHistory = this.thoughtHistory.slice(-this.maxHistorySize);
28+
}
29+
}
30+
2431
private formatThought(thoughtData: ThoughtData): string {
2532
const { thoughtNumber, totalThoughts, thought, isRevision, revisesThought, branchFromThought, branchId } = thoughtData;
2633

@@ -58,12 +65,18 @@ export class SequentialThinkingServer {
5865
}
5966

6067
this.thoughtHistory.push(input);
68+
this.pruneHistory();
6169

6270
if (input.branchFromThought && input.branchId) {
6371
if (!this.branches[input.branchId]) {
6472
this.branches[input.branchId] = [];
6573
}
6674
this.branches[input.branchId].push(input);
75+
76+
// Prune branches too
77+
if (this.branches[input.branchId].length > this.maxHistorySize) {
78+
this.branches[input.branchId] = this.branches[input.branchId].slice(-this.maxHistorySize);
79+
}
6780
}
6881

6982
if (!this.disableThoughtLogging) {

0 commit comments

Comments
 (0)