feat: add session expiration tracking and reconnection support#140
Merged
paulbreuler merged 2 commits intomainfrom Feb 11, 2026
Merged
feat: add session expiration tracking and reconnection support#140paulbreuler merged 2 commits intomainfrom
paulbreuler merged 2 commits intomainfrom
Conversation
- Track expired sessions with reason (timeout/closed/deleted) for 24h - Return specific error codes: SESSION_EXPIRED vs SESSION_NOT_FOUND - Add X-Session-Expired headers for MCP client reconnection - Document reconnection flow in README This allows MCP clients to detect expired sessions and automatically reconnect by creating a new session without the old session ID.
There was a problem hiding this comment.
Pull request overview
This PR enhances the HTTP MCP server with session expiration tracking and reconnection support to help clients automatically recover from idle timeouts. The implementation distinguishes between expired sessions (previously valid but timed out) and non-existent sessions (never existed or server restarted) through distinct error codes and HTTP headers.
Changes:
- Added expired session tracking with a 24-hour retention period to distinguish between
SESSION_EXPIREDandSESSION_NOT_FOUNDerrors - Enhanced error responses with specific error codes, descriptive messages, and custom headers (
X-Session-Expired,X-Session-Expired-Reason) to guide client reconnection logic - Updated documentation in README.md with session management guidance, reconnection flow, and configuration examples
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| packages/limps/src/server-http.ts | Added ExpiredSessionInfo interface and expiredSessions Map for tracking expired sessions; modified cleanupSession to accept a reason parameter and track expiration; enhanced error handling to distinguish SESSION_EXPIRED from SESSION_NOT_FOUND with appropriate headers; updated transport.onclose and stopHttpServer to use new cleanup mechanism |
| README.md | Added Session Management & Reconnection section documenting session expiration behavior, error responses, client reconnection flow, and configuration options |
Comments suppressed due to low confidence (3)
packages/limps/src/server-http.ts:255
- When a DELETE request is made with an unknown session ID (line 252-255), it returns a generic "Session not found" error without distinguishing between SESSION_EXPIRED and SESSION_NOT_FOUND. This is inconsistent with the enhanced error handling added for GET/POST requests. For consistency and to help clients understand the session state, DELETE should also check expiredSessions and return the appropriate error code and headers.
} else {
res.writeHead(404, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Session not found' }));
}
packages/limps/src/server-http.ts:505
- The stopHttpServer function does not clear the expiredSessions map. When the server stops and restarts, the expiredSessions map will still contain old session IDs, which could lead to incorrect SESSION_EXPIRED responses for sessions from a previous server instance. Consider adding expiredSessions.clear() after line 477 or at the end of the function to ensure a clean state on server shutdown.
export async function stopHttpServer(): Promise<void> {
// Stop session cleanup interval
if (cleanupInterval) {
clearInterval(cleanupInterval);
cleanupInterval = null;
}
// Close all active sessions
for (const [sessionId, session] of sessions) {
try {
if (session.server.loadedExtensions) {
await shutdownExtensions(session.server.loadedExtensions);
}
await session.transport.close();
// Track as closed for reconnection support
expiredSessions.set(sessionId, {
expiredAt: new Date(),
reason: 'closed',
});
} catch (error) {
logRedactedError(`Error closing session ${maskSessionId(sessionId)}`, error);
}
}
sessions.clear();
// Close HTTP server
if (httpServer) {
const server = httpServer;
await new Promise<void>((resolve, reject) => {
server.close((err) => {
if (err) reject(err);
else resolve();
});
});
httpServer = null;
console.error('HTTP server stopped');
}
// Clean up PID file
if (resources) {
const pidFilePath =
activePidFilePath ?? getPidFilePath(getHttpServerConfig(resources.config).port);
removePidFile(pidFilePath);
// Shut down shared resources
await shutdownServerResources(resources);
resources = null;
}
startTime = null;
activePidFilePath = null;
}
README.md:673
- The text "Use this endpoint for:" on line 670 appears to be misplaced. It refers to the /health endpoint but is located at the end of the Session Management & Reconnection section. This text should either be moved up to the Health Endpoint section (after line 618) or removed if it was intended to be replaced by the Session Management section.
Use this endpoint for:
- Monitoring daemon health in scripts or dashboards
- Verifying daemon is running before connecting MCP clients
- Automated health checks in orchestration tools (Kubernetes, Docker Compose)
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Allow sessionTimeoutMs: 0 to disable timeout (config validation) - Fix race condition in cleanupSession by removing from sessions first - Add comprehensive tests for SESSION_EXPIRED and SESSION_NOT_FOUND - Add test for sessionTimeoutMs: 0 boundary value Addresses review feedback on PR #140
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR adds session expiration tracking and reconnection support to help MCP clients automatically recover from idle timeouts.
Problem
The limps HTTP server has a default 30-minute session idle timeout. When a session expires, MCP clients receive a generic "Session not found" 404 error without any indication of whether the session expired or was invalid. This makes it difficult for clients to implement automatic reconnection logic.
Solution
1. Session Expiration Tracking
timeout,closed,deleted) for 24 hoursSESSION_EXPIRED(previously valid) andSESSION_NOT_FOUND(never existed)2. Enhanced Error Responses
Session Expired:
{ "error": "Session expired", "code": "SESSION_EXPIRED", "message": "Session expired due to timeout. Please reconnect without session ID.", "expiredAt": "2026-02-11T10:30:00.000Z" }Headers:
X-Session-Expired: trueX-Session-Expired-Reason: timeout(orclosed,deleted)Session Not Found (never existed):
{ "error": "Session not found", "code": "SESSION_NOT_FOUND", "message": "Session ID not recognized. It may have been invalid or the server was restarted." }3. Client Reconnection Flow
When receiving
SESSION_EXPIRED, clients should:mcp-session-id/mcpwithout the session ID header4. Documentation
Added comprehensive session management documentation to the README covering:
sessionTimeoutMs)Changes
packages/limps/src/server-http.ts: Add expired session tracking, enhanced error responsesREADME.md: Add Session Management & Reconnection sectionTesting
All 1714 tests pass:
packages/limpspackages/limps-headlessConfiguration
Users can adjust the session timeout in their config:
{ "server": { "sessionTimeoutMs": 3600000 // 1 hour (default: 30 min) } }Set to
0to disable timeout entirely (sessions persist until server restart).