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:
czlonkowski
2025-07-18 18:51:24 +02:00
parent f76e2247f9
commit 6e52afd5af
7 changed files with 98 additions and 32 deletions

View File

@@ -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);
}
}
}
}

View File

@@ -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();