UNPKG

@just-every/mcp-screenshot-website-fast

Version:

Fast screenshot capture tool for web pages - optimized for Claude Vision API

110 lines (109 loc) 3.82 kB
#!/usr/bin/env node import { spawn } from 'child_process'; import { dirname, join } from 'path'; import { fileURLToPath } from 'url'; const __dirname = dirname(fileURLToPath(import.meta.url)); const MAX_RESTART_ATTEMPTS = 10; const RESTART_WINDOW_MS = 60000; const INITIAL_BACKOFF_MS = 1000; const MAX_BACKOFF_MS = 30000; let restartAttempts = []; let currentBackoff = INITIAL_BACKOFF_MS; const log = (level, message, ...args) => { const logLevel = process.env.LOG_LEVEL?.toUpperCase(); if (!logLevel || logLevel === 'OFF') { return; } const timestamp = new Date().toISOString(); console.error(`[${timestamp}] [${level}] [restart-wrapper]`, message, ...args); }; const cleanupRestartAttempts = () => { const now = Date.now(); restartAttempts = restartAttempts.filter(timestamp => now - timestamp < RESTART_WINDOW_MS); }; const shouldRestart = () => { cleanupRestartAttempts(); if (restartAttempts.length >= MAX_RESTART_ATTEMPTS) { log('ERROR', `Reached maximum restart attempts (${MAX_RESTART_ATTEMPTS}) within ${RESTART_WINDOW_MS}ms`); return false; } return true; }; const getBackoffDelay = () => { const delay = Math.min(currentBackoff, MAX_BACKOFF_MS); currentBackoff = Math.min(currentBackoff * 2, MAX_BACKOFF_MS); return delay; }; const resetBackoff = () => { currentBackoff = INITIAL_BACKOFF_MS; }; const startServer = () => { log('INFO', 'Starting MCP server...'); const serverPath = join(__dirname, 'serve.js'); const child = spawn(process.execPath, [serverPath], { stdio: 'inherit', env: process.env, }); let shuttingDown = false; let restartTimer = null; const startupTimer = setTimeout(() => { log('INFO', 'Server started successfully'); resetBackoff(); }, 5000); child.on('exit', (code, signal) => { clearTimeout(startupTimer); if (shuttingDown) { log('INFO', 'Server stopped gracefully'); process.exit(0); return; } if (code === 0) { log('INFO', 'Server exited cleanly'); process.exit(0); return; } log('WARN', `Server exited with code ${code}, signal ${signal}`); if (!shouldRestart()) { log('ERROR', 'Too many restart attempts, giving up'); process.exit(1); return; } const backoffDelay = getBackoffDelay(); restartAttempts.push(Date.now()); log('INFO', `Restarting server in ${backoffDelay}ms (attempt ${restartAttempts.length}/${MAX_RESTART_ATTEMPTS})...`); restartTimer = setTimeout(() => { startServer(); }, backoffDelay); }); child.on('error', error => { log('ERROR', 'Failed to start server:', error); process.exit(1); }); const shutdown = (signal) => { if (shuttingDown) return; shuttingDown = true; log('INFO', `Received ${signal}, shutting down...`); if (restartTimer) { clearTimeout(restartTimer); } child.kill(signal); setTimeout(() => { log('WARN', 'Force killing child process'); child.kill('SIGKILL'); }, 5000); }; process.on('SIGINT', () => shutdown('SIGINT')); process.on('SIGTERM', () => shutdown('SIGTERM')); }; process.on('uncaughtException', error => { log('ERROR', 'Uncaught exception in restart wrapper:', error); process.exit(1); }); process.on('unhandledRejection', reason => { log('ERROR', 'Unhandled rejection in restart wrapper:', reason); process.exit(1); }); log('INFO', 'MCP server restart wrapper starting...'); log('INFO', `Configuration: max attempts=${MAX_RESTART_ATTEMPTS}, window=${RESTART_WINDOW_MS}ms`); startServer();