UNPKG

@inso_web/els-mcp

Version:

MCP-сервер поверх INSO Error Logs Service. Read-only tools (search, analytics, fingerprinting, correlations) для подключения Claude Desktop/Code и ChatGPT к логам ошибок. Streamable HTTP transport + stdio для npx-запуска.

59 lines 1.99 kB
const DEFAULT_LIMIT = 10; const DEFAULT_WINDOW_SEC = 3600; const KEY_PREFIX = 'mcp:dcr:rl:'; export function createDcrRateLimit(opts = {}) { const redis = opts.redis; const limit = opts.limit ?? DEFAULT_LIMIT; const windowSec = opts.windowSec ?? DEFAULT_WINDOW_SEC; const log = opts.log; return async function dcrRateLimit(req, res, next) { const ip = pickIp(req); if (!ip) { // не определили IP — пропускаем next(); return; } if (!redis || redis.unavailable) { log?.warn?.({ ip }, 'dcrRateLimit: Redis unavailable, fail-open'); next(); return; } const key = `${KEY_PREFIX}${ip}`; try { const raw = redis.raw; if (!raw) { next(); return; } const current = await raw.incr(key); if (current === 1) { // первый INCR — выставляем TTL окна. await raw.expire(key, windowSec); } if (current > limit) { const ttl = await raw.ttl(key); res.setHeader('Retry-After', String(Math.max(1, ttl))); res.status(429).json({ error: 'rate_limited', error_description: `DCR rate limit exceeded (${limit}/${windowSec}s)`, }); return; } } catch (err) { // На ошибках Redis — fail-open. log?.warn?.({ err: err.message, ip }, 'dcrRateLimit: Redis error, fail-open'); } next(); }; } function pickIp(req) { const xff = req.headers['x-forwarded-for']; if (typeof xff === 'string') { const first = xff.split(',')[0]?.trim(); if (first) return first; } return req.socket?.remoteAddress ?? ''; } //# sourceMappingURL=dcrRateLimit.js.map