Skip to content

Commit b277fb1

Browse files
nickytonlineclaude
andcommitted
feat(mcp): implement comprehensive session cleanup mechanism
- Add session timestamp tracking for MCP transport connections - Implement automatic cleanup of stale sessions (30+ minutes inactive) - Properly close transport connections to prevent resource leaks - Schedule cleanup every 10 minutes with structured logging - Track both session creation and access times for accurate timeout detection - Log cleanup statistics including cleaned and active session counts 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent cf39df2 commit b277fb1

File tree

1 file changed

+46
-0
lines changed

1 file changed

+46
-0
lines changed

src/index.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ const app = express();
5050
app.use(express.json());
5151

5252
const transports: { [sessionId: string]: StreamableHTTPServerTransport } = {};
53+
const sessionTimestamps: { [sessionId: string]: Date } = {};
5354

5455
const mcpHandler = async (req: express.Request, res: express.Response) => {
5556
const sessionId = req.headers["mcp-session-id"] as string | undefined;
@@ -63,6 +64,7 @@ const mcpHandler = async (req: express.Request, res: express.Response) => {
6364
sessionIdGenerator: () => randomUUID(),
6465
onsessioninitialized: (sessionId) => {
6566
transports[sessionId] = transport;
67+
sessionTimestamps[sessionId] = new Date();
6668
logger.info("MCP session initialized", { sessionId });
6769
},
6870
});
@@ -75,6 +77,8 @@ const mcpHandler = async (req: express.Request, res: express.Response) => {
7577

7678
// Handle existing session requests
7779
if (sessionId && transports[sessionId]) {
80+
// Update session timestamp
81+
sessionTimestamps[sessionId] = new Date();
7882
const transport = transports[sessionId];
7983
await transport.handleRequest(req, res, req.body);
8084
return;
@@ -149,6 +153,48 @@ const mcpHandler = async (req: express.Request, res: express.Response) => {
149153
}
150154
};
151155

156+
/**
157+
* Clean up stale MCP sessions
158+
*/
159+
function cleanupStaleSessions(): void {
160+
const now = new Date();
161+
const SESSION_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes
162+
let cleanedCount = 0;
163+
164+
for (const [sessionId, timestamp] of Object.entries(sessionTimestamps)) {
165+
if (now.getTime() - timestamp.getTime() > SESSION_TIMEOUT_MS) {
166+
// Close transport if it exists
167+
const transport = transports[sessionId];
168+
if (transport) {
169+
try {
170+
transport.close?.();
171+
} catch (error) {
172+
logger.warn("Error closing stale transport", {
173+
sessionId,
174+
error: error instanceof Error ? error.message : error
175+
});
176+
}
177+
delete transports[sessionId];
178+
}
179+
180+
delete sessionTimestamps[sessionId];
181+
cleanedCount++;
182+
183+
logger.debug("Cleaned up stale MCP session", { sessionId });
184+
}
185+
}
186+
187+
if (cleanedCount > 0) {
188+
logger.info("MCP session cleanup completed", {
189+
cleanedSessions: cleanedCount,
190+
activeSessions: Object.keys(transports).length
191+
});
192+
}
193+
}
194+
195+
// Schedule MCP session cleanup every 10 minutes
196+
setInterval(cleanupStaleSessions, 10 * 60 * 1000);
197+
152198
const config = getConfig();
153199
let oauthProvider: OAuthProvider | null = null;
154200

0 commit comments

Comments
 (0)