Skip to content

Commit 9f3f595

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

File tree

2 files changed

+75
-7
lines changed

2 files changed

+75
-7
lines changed

src/sequentialthinking/index.ts

Lines changed: 62 additions & 7 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,13 +103,13 @@ You should:
91103
},
92104
},
93105
async (args) => {
106+
const thinkingServer = getOrCreateThinkingServer();
94107
const result = thinkingServer.processThought(args);
95108

96109
if (result.isError) {
97-
return result;
110+
return { content: result.content };
98111
}
99112

100-
// Parse the JSON response to get structured content
101113
const parsedContent = JSON.parse(result.content[0].text);
102114

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

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

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

124175
let transport: StreamableHTTPServerTransport;
125176
if (sessionId && transports[sessionId]) {
126177
transport = transports[sessionId];
178+
resetSessionTimeout(sessionId);
127179
} else if (!sessionId) {
128180
transport = new StreamableHTTPServerTransport({
129181
sessionIdGenerator: () => crypto.randomUUID(),
130182
onsessioninitialized: (sid) => {
131183
transports[sid] = transport;
184+
resetSessionTimeout(sid);
132185
console.error('Session initialized:', sid);
133186
},
134187
onsessionclosed: (sid) => {
135-
delete transports[sid];
188+
cleanupSession(sid);
136189
console.error('Session closed:', sid);
137190
}
138191
});
@@ -151,6 +204,7 @@ async function runServer() {
151204
res.end('Invalid or missing session ID');
152205
return;
153206
}
207+
resetSessionTimeout(sessionId);
154208
await transports[sessionId].handleRequest(req, res);
155209
} else if (req.method === 'DELETE') {
156210
if (!sessionId || !transports[sessionId]) {
@@ -159,6 +213,7 @@ async function runServer() {
159213
return;
160214
}
161215
await transports[sessionId].handleRequest(req, res);
216+
cleanupSession(sessionId);
162217
} else {
163218
res.writeHead(405);
164219
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)