fix: Docker container cleanup on session end (Issue #66)
- Added proper SIGTERM/SIGINT signal handlers to stdio-wrapper.ts - Removed problematic trap commands from docker-entrypoint.sh - Added STOPSIGNAL directive to Dockerfile for explicit signal handling - Implemented graceful shutdown in MCP server with database cleanup - Added stdin close detection for proper cleanup when Claude Desktop closes the pipe - Containers now properly exit with the --rm flag, preventing accumulation - Added --init flag to all Docker configuration examples - Updated documentation with container lifecycle management best practices - Bumped version to 2.7.20 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -2031,4 +2031,18 @@ Full documentation is being prepared. For now, use get_node_essentials for confi
|
||||
// Keep the process alive and listening
|
||||
process.stdin.resume();
|
||||
}
|
||||
|
||||
async shutdown(): Promise<void> {
|
||||
logger.info('Shutting down MCP server...');
|
||||
|
||||
// Close database connection if it exists
|
||||
if (this.db) {
|
||||
try {
|
||||
await this.db.close();
|
||||
logger.info('Database connection closed');
|
||||
} catch (error) {
|
||||
logger.error('Error closing database:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,9 +42,11 @@ console.countReset = () => {};
|
||||
// Import and run the server AFTER suppressing output
|
||||
import { N8NDocumentationMCPServer } from './server';
|
||||
|
||||
let server: N8NDocumentationMCPServer | null = null;
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
const server = new N8NDocumentationMCPServer();
|
||||
server = new N8NDocumentationMCPServer();
|
||||
await server.run();
|
||||
} catch (error) {
|
||||
// In case of fatal error, output to stderr only
|
||||
@@ -64,4 +66,47 @@ process.on('unhandledRejection', (reason) => {
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
// Handle termination signals for proper cleanup
|
||||
let isShuttingDown = false;
|
||||
|
||||
async function shutdown(signal: string) {
|
||||
if (isShuttingDown) return;
|
||||
isShuttingDown = true;
|
||||
|
||||
// Log to stderr only (not stdout which would corrupt JSON-RPC)
|
||||
originalConsoleError(`Received ${signal}, shutting down gracefully...`);
|
||||
|
||||
try {
|
||||
// Shutdown the server if it exists
|
||||
if (server) {
|
||||
await server.shutdown();
|
||||
}
|
||||
} catch (error) {
|
||||
originalConsoleError('Error during shutdown:', error);
|
||||
}
|
||||
|
||||
// Close stdin to signal we're done reading
|
||||
process.stdin.pause();
|
||||
process.stdin.destroy();
|
||||
|
||||
// Exit with timeout to ensure we don't hang
|
||||
setTimeout(() => {
|
||||
process.exit(0);
|
||||
}, 500).unref(); // unref() allows process to exit if this is the only thing keeping it alive
|
||||
|
||||
// But also exit immediately if nothing else is pending
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Register signal handlers
|
||||
process.on('SIGTERM', () => void shutdown('SIGTERM'));
|
||||
process.on('SIGINT', () => void shutdown('SIGINT'));
|
||||
process.on('SIGHUP', () => void shutdown('SIGHUP'));
|
||||
|
||||
// Also handle stdin close (when Claude Desktop closes the pipe)
|
||||
process.stdin.on('end', () => {
|
||||
originalConsoleError('stdin closed, shutting down...');
|
||||
void shutdown('STDIN_CLOSE');
|
||||
});
|
||||
|
||||
main();
|
||||
Reference in New Issue
Block a user