Payload CMS MCP Server

MIT License
5
  • Linux
  • Apple
  • api
import { createClient } from "redis"; import type { VercelRequest, VercelResponse } from '@vercel/node'; // Global Redis client that persists between function invocations let redisClient: any = null; let lastPingTime = 0; let connectionAttempts = 0; const MAX_CONNECTION_ATTEMPTS = 10; // Clean the Redis URL if it contains variable name or quotes const cleanRedisUrl = (url: string | undefined): string | undefined => { if (!url) return undefined; // If the URL contains KV_URL= or REDIS_URL=, extract just the URL part if (url.includes('KV_URL=') || url.includes('REDIS_URL=')) { const match = url.match(/(?:KV_URL=|REDIS_URL=)["']?(rediss?:\/\/[^"']+)["']?/); return match ? match[1] : url; } // Remove any surrounding quotes return url.replace(/^["'](.+)["']$/, '$1'); }; // Initialize Redis client if not already initialized async function getRedisClient() { if (redisClient && await isRedisConnected()) { console.log("Using existing Redis client"); connectionAttempts = 0; return redisClient; } console.log("Creating new Redis client"); connectionAttempts++; if (connectionAttempts > MAX_CONNECTION_ATTEMPTS) { console.log(`Exceeded maximum connection attempts (${MAX_CONNECTION_ATTEMPTS}). Resetting counter.`); connectionAttempts = 1; } const redisUrl = cleanRedisUrl(process.env.REDIS_URL) || cleanRedisUrl(process.env.KV_URL); if (!redisUrl) { throw new Error("REDIS_URL or KV_URL environment variable is not set"); } // Create Redis client with maximum persistence settings redisClient = createClient({ url: redisUrl, socket: { reconnectStrategy: (retries) => { const delay = Math.min(Math.pow(1.5, retries) * 100, 5000); console.log(`Redis reconnecting in ${delay}ms (attempt ${retries})`); return delay; }, connectTimeout: 30000, keepAlive: 5000, noDelay: true, tls: redisUrl.startsWith('rediss://') ? { rejectUnauthorized: false } : undefined, }, pingInterval: 1000, disableOfflineQueue: false, commandTimeout: 5000, retryStrategy: () => 1000, // Retry every second autoResubscribe: true, autoResendUnfulfilledCommands: true, enableReadyCheck: true, enableOfflineQueue: true, maxRetriesPerRequest: 50, }); redisClient.on("error", (err: Error) => { console.error("Redis error", err); // Don't set redisClient to null here, let the reconnection strategy handle it }); redisClient.on("connect", () => { console.log("Redis connected"); }); redisClient.on("ready", () => { console.log("Redis ready"); }); redisClient.on("end", () => { console.log("Redis disconnected"); redisClient = null; // Reset client so we create a new one next time }); try { await redisClient.connect(); // Set up a ping interval to keep the connection alive setInterval(async () => { try { if (redisClient) { await redisClient.ping(); console.log("Internal ping successful"); } } catch (error) { console.error("Internal ping failed:", error); } }, 10000); // Ping every 10 seconds } catch (error) { console.error("Failed to connect to Redis:", error); redisClient = null; throw error; } return redisClient; } // Check if Redis is connected async function isRedisConnected() { if (!redisClient) return false; try { await redisClient.ping(); return true; } catch (error) { console.error("Redis connection check failed:", error); return false; } } // Perform Redis operations to keep the connection active async function performRedisOperations(redis: any) { const now = Date.now(); // Store the current time in Redis await redis.set('last_cron_keepalive', now.toString()); // Get the stored value const storedValue = await redis.get('last_cron_keepalive'); // Get some stats const info = await redis.info(); // Perform a simple list operation await redis.lPush('keepalive_list', now.toString()); await redis.lTrim('keepalive_list', 0, 9); // Keep only the last 10 entries // Get the list const list = await redis.lRange('keepalive_list', 0, -1); return { storedValue, info: info.substring(0, 500) + '...', // Truncate info to avoid large response list }; } // Handler for the cron endpoint export default async function handler(req: VercelRequest, res: VercelResponse) { try { // Check for authorization header if needed const authHeader = req.headers.authorization; if (process.env.CRON_SECRET && authHeader !== `Bearer ${process.env.CRON_SECRET}`) { return res.status(401).json({ error: 'Unauthorized' }); } const now = Date.now(); const timeSinceLastPing = now - lastPingTime; lastPingTime = now; console.log(`Cron keep-alive endpoint called. Time since last ping: ${timeSinceLastPing}ms`); const redis = await getRedisClient(); const pingResult = await redis.ping(); // Perform various Redis operations to ensure the connection stays active const operationResults = await performRedisOperations(redis); res.status(200).json({ status: 'ok', ping: pingResult, lastPingTime: lastPingTime, timeSinceLastPing: timeSinceLastPing, connectionAttempts, operations: operationResults, timestamp: now }); } catch (error) { console.error("Cron keep-alive error:", error); res.status(500).json({ status: 'error', message: error instanceof Error ? error.message : 'Unknown error', timestamp: Date.now() }); } }