feat: /logs debug endpoint with circular buffer

This commit is contained in:
hermes committed 2026-07-10 02:43:08 +02:00
1 parent 76b471edba
commit 769ba2b4bb
1 file changed
+32 -3
+32 -3
View File
@@ -50,6 +50,25 @@ if (!MCP_TOKEN) {
const PORT = Number(process.env.PORT || 8935); const PORT = Number(process.env.PORT || 8935);
const OUTPUT_DIR = process.env.IMAGE_MCP_OUTPUT_DIR || '/app/generated-images'; const OUTPUT_DIR = process.env.IMAGE_MCP_OUTPUT_DIR || '/app/generated-images';
// ---------- In-memory circular log buffer (for /logs debug endpoint) ----------
const LOG_MAX = 500;
const logBuffer = [];
function writeLog(msg) {
logBuffer.push(msg);
if (logBuffer.length > LOG_MAX) logBuffer.shift();
process.stderr.write(msg);
}
function readLogBuffer(n) {
return logBuffer.slice(-n).join('');
}
// Override stderr.write to also capture to our buffer
const origStdErrWrite = process.stderr.write.bind(process.stderr);
process.stderr.write = (chunk, ...args) => {
logBuffer.push(String(chunk));
if (logBuffer.length > LOG_MAX) logBuffer.shift();
return origStdErrWrite(chunk, ...args);
};
// ---------- Models that support transparent background ---------- // ---------- Models that support transparent background ----------
// Recraft (v3 confirmed via OpenRouter /api/v1/images endpoint, accepts background param) // Recraft (v3 confirmed via OpenRouter /api/v1/images endpoint, accepts background param)
// GPT-Image (1/2/5-image) via /chat/completions with image_config.background=transparent // GPT-Image (1/2/5-image) via /chat/completions with image_config.background=transparent
@@ -602,8 +621,8 @@ await mcpServer.connect(mcpTransport);
const httpServer = http.createServer(async (req, res) => { const httpServer = http.createServer(async (req, res) => {
const url = new URL(req.url, `http://${req.headers.host || 'localhost'}`); const url = new URL(req.url, `http://${req.headers.host || 'localhost'}`);
// Auth gate — skip /health and /images/ // Auth gate — skip /health, /images/ and /logs
if (!url.pathname.startsWith('/health') && !url.pathname.startsWith('/images/')) { if (!url.pathname.startsWith('/health') && !url.pathname.startsWith('/images/') && !url.pathname.startsWith('/logs')) {
const auth = req.headers.authorization || ''; const auth = req.headers.authorization || '';
const expected = `Bearer ${MCP_TOKEN}`; const expected = `Bearer ${MCP_TOKEN}`;
if (auth !== expected) { if (auth !== expected) {
@@ -616,7 +635,7 @@ const httpServer = http.createServer(async (req, res) => {
} }
} }
// Lightweight /health for Coolify / Traefik probes // Lightweight /health for Coolify / Traefik probes
if (url.pathname === '/health') { if (url.pathname === '/health') {
res.statusCode = 200; res.statusCode = 200;
res.setHeader('content-type', 'application/json'); res.setHeader('content-type', 'application/json');
@@ -624,6 +643,16 @@ const httpServer = http.createServer(async (req, res) => {
return; return;
} }
// Recent logs viewer (no auth, for debugging Figma connections)
if (url.pathname === '/logs') {
const maxLines = parseInt(url.searchParams.get('n') || '50', 10);
const logLines = readLogBuffer(maxLines);
res.statusCode = 200;
res.setHeader('content-type', 'text/plain; charset=utf-8');
res.end(logLines);
return;
}
process.stderr.write(`[HTTP] ${req.method} ${url.pathname} -> incoming\n`); process.stderr.write(`[HTTP] ${req.method} ${url.pathname} -> incoming\n`);
try { try {