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-запуска.

344 lines 19.8 kB
import { z } from 'zod'; export const ALL_PROMPT_NAMES = [ 'triage-recent-errors', 'find-regression-since-deploy', 'explain-error-cluster', 'weekly-error-report', 'post-feature-checkup', 'pre-deploy-baseline', 'post-deploy-verify', ]; function userMessage(text) { return { messages: [ { role: 'user', content: { type: 'text', text }, }, ], }; } export function registerPrompts(server, opts = {}) { const registered = []; // ─── triage-recent-errors ──────────────────────────────────────────────── server.registerPrompt('triage-recent-errors', { title: 'Triage recent errors', description: 'On-call инцидент-triage: какие ошибки случились за последние N часов, сгруппированные по severity и fingerprint.', argsSchema: { hours: z.string().optional().describe('Окно анализа в часах (1-24). Default: 1.'), level: z.string().optional().describe('Минимальный уровень severity. Default: CRITICAL.'), }, }, async (args) => { const hoursStr = args.hours ?? '1'; const hoursNum = Math.max(1, Math.min(24, Number(hoursStr) || 1)); const level = args.level ?? 'CRITICAL'; const minutes = hoursNum * 60; const text = [ 'Ты — опытный SRE, дежурный по платформе ELS. Сейчас инцидент-triage.', '', 'Контекст:', `- Окно анализа: последние ${hoursNum} ч.`, `- Severity: ${level} и выше.`, '', 'Сделай следующее, используя доступные tools:', '', `1. Вызови \`triage_recent_critical\` с minutesBack=${minutes}, sampleSize=10.`, '2. Если найдено ≥ 1 fingerprint, вызови `grouped_errors` за то же окно и покажи топ-5 по count.', '3. Для top-1 fingerprint вызови `find_similar_errors` (по traceId из samples) — ошибка новая или recurring?', '4. Сделай выводы в формате:', ' - **Заголовок:** одна строка — что сломалось', ` - **Severity:** ${level} × N`, ' - **Top causes (top-3):** fingerprint + краткое описание + count', ' - **Recurring vs new:** для top-1 — сколько раз за 7д', ' - **Recommended next step:** что проверять руками', '', 'Отвечай по-русски, без emoji, кратко.', ].join('\n'); return userMessage(text); }); registered.push('triage-recent-errors'); // ─── find-regression-since-deploy ──────────────────────────────────────── server.registerPrompt('find-regression-since-deploy', { title: 'Find regressions after deploy', description: 'Сравни ошибки между двумя версиями релиза и определи, что появилось нового в candidate.', argsSchema: { baselineVersion: z.string().min(1).describe('Стабильная предыдущая версия, напр. v2.3.0.'), candidateVersion: z.string().min(1).describe('Новая версия для анализа, напр. v2.4.0.'), }, }, async (args) => { const baseline = args.baselineVersion; const candidate = args.candidateVersion; const text = [ `Ты — release-engineer. Только что был развёрнут ${candidate}.`, `До этого работал ${baseline}. Нужно проверить, нет ли регрессий.`, '', 'Шаги:', '', `1. Вызови \`version_regression\` с baselineVersion=${baseline}, candidateVersion=${candidate}.`, '2. Если в `newFingerprints` есть записи — для каждого из топ-3 по `count`:', ' 2.1. Вызови `find_similar_errors` с traceId примера.', ' 2.2. Если ошибка появилась впервые за последние 24 часа — пометь как "REGRESSION SUSPECT".', '3. Если в `disappeared` есть записи — упомяни их кратко.', '', 'Финальный отчёт — markdown:', '', `# Regression check ${baseline}${candidate}`, '', '## Новые fingerprint\'ы (suspect regressions)', '| FP | Message | Count | First seen | Verdict |', '|----|---------|-------|------------|---------|', '', '## Исчезнувшие', '- ...', '', '## Рекомендация', '- (rollback / hotfix / safe-to-ignore)', '', 'Отвечай по-русски, без emoji.', ].join('\n'); return userMessage(text); }); registered.push('find-regression-since-deploy'); // ─── explain-error-cluster ─────────────────────────────────────────────── server.registerPrompt('explain-error-cluster', { title: 'Explain error cluster', description: 'Возьми traceId или fingerprint, собери контекст (similar, correlated) и объясни читабельно.', argsSchema: { traceId: z.string().optional().describe('TraceId конкретной ошибки.'), fingerprint: z.string().optional().describe('Альтернатива traceId — fingerprint кластера.'), locale: z.string().optional().describe('Язык объяснения: ru или en. Default: ru.'), }, }, async (args) => { const traceId = args.traceId; const fingerprint = args.fingerprint; const locale = args.locale ?? 'ru'; if (!traceId && !fingerprint) { return userMessage('Не задан ни traceId, ни fingerprint. Уточни у пользователя одно из значений и повтори вызов prompt.'); } const stepsTrace = traceId ? [ `Старт: traceId=${traceId}.`, '', 'Шаги:', `1. \`get_log_details\` с traceId=${traceId}.`, `2. \`find_similar_errors\` с этим же traceId.`, `3. \`find_correlated_errors\` (windowMinutes=15).`, `4. \`explain_error\` traceId=${traceId} — соберёт контекст; AI-summary пока не доступен, синтезируй сам.`, ].join('\n') : [ `Старт: fingerprint=${fingerprint}.`, '', 'Шаги:', `1. Вызови \`search_logs\` с filter \`fingerprint=${fingerprint}\`, limit=1 — получи свежий traceId.`, '2. Дальше как с traceId — пункты 2-4.', ].join('\n'); const text = [ 'Объясни этот кластер ошибок понятно, как для junior-разработчика.', '', stepsTrace, '', `Финальный ответ (${locale}):`, '- **Что произошло:** 1-2 предложения', '- **Почему скорее всего:** 2-3 гипотезы, ранжированные', '- **Какие ещё ошибки случались рядом:** перечисли correlated (если есть)', '- **Что проверить руками:** конкретные команды / dashboards', '', 'Без emoji. Без markdown-heavy форматирования вне нужного.', ].join('\n'); return userMessage(text); }); registered.push('explain-error-cluster'); // ─── weekly-error-report ───────────────────────────────────────────────── server.registerPrompt('weekly-error-report', { title: 'Weekly error report', description: 'Сгенерируй markdown-отчёт за последние 7 дней: total, growth vs prev week, top fingerprints, регрессии, impact-priority.', argsSchema: { appSlug: z.string().min(1).describe('Slug приложения для отчёта.'), }, }, async (args) => { const appSlug = args.appSlug; const locale = 'ru'; const text = [ `Подготовь еженедельный отчёт об ошибках для приложения ${appSlug}.`, '', 'Окно: последние 7 дней. Сравни с предыдущей неделей.', '', 'Шаги:', '1. `error_stats_breakdown` с compareTo=previous-week.', '2. `grouped_errors` take=10, sortBy=count.', '3. Определи две последние версии в выборке через `error_histogram` (или прямо в фильтрах). Вызови `version_regression` baseline=prev, candidate=latest.', '4. `impact_analysis` take=5 — приоритет на следующую неделю.', '', `Финальный отчёт (${locale}, markdown):`, '', `# Weekly Error Report — ${appSlug}`, '**Период:** ...', '**Тренд:** ↑/↓ NN%', '', '## Summary', '- Total errors: ... (vs prev week: ...)', '- Unique fingerprints: ...', '- Affected users: ...', '', '## Top 10 проблем недели', '| FP | Message | Count | trend |', '|----|---------|-------|-------|', '', '## Регрессии (после deploy последней версии)', '| FP | Message | First seen |', '|----|---------|------------|', '', '## Приоритет на след. неделю (impact-ranked top-5)', '1. ...', '', '## Что улучшилось', '- ...', '', 'Без emoji. Без «выводов» от себя — только данные.', ].join('\n'); return userMessage(text); }); registered.push('weekly-error-report'); // ─── post-feature-checkup ──────────────────────────────────────────────── server.registerPrompt('post-feature-checkup', { title: 'Post-feature local checkup', description: 'После того как фича готова локально и тесты прошли — проверь dev-логи, не породил ли ручной тест CRITICAL ошибки.', argsSchema: { featureBranch: z.string().optional().describe('Имя feature-ветки (для контекста, опционально).'), localTestTime: z.string().optional().describe('Сколько минут назад начался ручной тест (default: 20).'), }, }, async (args) => { const branch = args.featureBranch ?? '(unspecified)'; const minutes = Math.max(5, Math.min(180, Number(args.localTestTime) || 20)); const text = [ 'Ты только что закончил фичу и прогнал её локально. Время проверить dev-логи.', '', 'Контекст:', `- Ветка: ${branch}`, `- Окно анализа: последние ${minutes} минут в DEV.`, '', 'Шаги:', '', `1. \`search_logs\` с deploymentEnv=DEV, from=${minutes} минут назад, level=[ERROR, CRITICAL, FATAL], limit=20.`, '2. Если ноль ошибок — вердикт «dev чист, можно push».', '3. Если есть ошибки:', ' 3.1. Сгруппировать по fingerprint (вызови `grouped_errors`).', ' 3.2. Для top-1 fingerprint — `explain_error` или `get_log_details`.', ' 3.3. Решить: связано с фичей или фоновая регрессия.', '', 'Финальный ответ (русский, кратко):', '- **Вердикт:** clean / errors-detected', '- **Что нашёл:** список (если есть), 1 строка на fingerprint', '- **Рекомендация:** push / fix-first', '', 'Без emoji.', ].join('\n'); return userMessage(text); }); registered.push('post-feature-checkup'); // ─── pre-deploy-baseline ───────────────────────────────────────────────── server.registerPrompt('pre-deploy-baseline', { title: 'Pre-deploy baseline snapshot', description: 'Перед деплоем — снять snapshot текущего состояния ошибок: histogram, top messages, traffic. Для последующего сравнения после деплоя.', argsSchema: { appSlug: z.string().min(1).describe('Slug приложения, для которого снимаем baseline.'), }, }, async (args) => { const appSlug = args.appSlug; const text = [ `Перед деплоем приложения ${appSlug} зафиксируй baseline состояния production.`, '', 'Цель: иметь точку отсчёта, чтобы после деплоя сравнить.', '', 'Шаги:', '', `1. \`error_histogram\` deploymentEnv=PRODUCTION, from=1 час назад. Запомни общее число событий и форму кривой.`, `2. \`top_error_messages\` deploymentEnv=PRODUCTION, limit=10, from=1 час назад. Запомни топ-10 fingerprint'ов.`, `3. \`traffic_stats\` from=1 час назад. Запомни p95 / p99 latency и RPM.`, `4. \`triage_recent_critical\` minutesBack=60, deploymentEnv=PRODUCTION. Запомни число CRITICAL и top fingerprints.`, '', 'Финальный ответ (русский, краткий снапшот):', '', `# Baseline ${appSlug} (pre-deploy)`, '**Время фиксации:** <ISO timestamp>', '', '## Errors (1h)', '- Total: ...', '- CRITICAL: ...', '', '## Top 10 fingerprints (1h)', '1. ...', '', '## Traffic (1h)', '- RPM: ...', '- p95: ... ms / p99: ... ms', '', 'Сохрани эти цифры в памяти сессии — они понадобятся через 5-10 минут после деплоя', 'для сравнения через `post-deploy-verify`.', '', 'Без emoji.', ].join('\n'); return userMessage(text); }); registered.push('pre-deploy-baseline'); // ─── post-deploy-verify ────────────────────────────────────────────────── server.registerPrompt('post-deploy-verify', { title: 'Post-deploy verification', description: 'После деплоя (через 5-10 минут) — проверить production: regression vs prev version, CRITICAL spike, impact priority. Вердикт: safe / regression-detected.', argsSchema: { previousVersion: z.string().min(1).describe('Версия, которая работала до deploy (BUILD_VERSION).'), currentVersion: z.string().min(1).describe('Новая задеплоенная версия (BUILD_VERSION).'), appSlug: z.string().optional().describe('Slug приложения (если auto-discovery не сработал).'), }, }, async (args) => { const prev = args.previousVersion; const cur = args.currentVersion; const appSlug = args.appSlug ?? '(auto-detected)'; const text = [ `Только что задеплоен ${cur} (предыдущая версия: ${prev}, app: ${appSlug}).`, 'Прошло ~5 минут после deploy. Время проверить production.', '', 'Шаги:', '', `1. \`version_regression\` baselineVersion=${prev}, candidateVersion=${cur}.`, ' - Если `newFingerprints` пуст → переходи к шагу 2.', ' - Если в новых есть с count >= 5 → пометь как REGRESSION SUSPECT.', '', `2. \`triage_recent_critical\` minutesBack=15, deploymentEnv=PRODUCTION.`, ' - Если CRITICAL count > baseline (из `pre-deploy-baseline`, если был) — escalate.', '', `3. \`error_histogram\` deploymentEnv=PRODUCTION, from=30 минут назад. Сравни кривую "до" и "после" deploy.`, '', '4. Для каждого REGRESSION SUSPECT из шага 1:', ' 4.1. `find_similar_errors` с traceId примера — это новая или recurring?', ' 4.2. `impact_analysis` take=5 — попал ли suspect в топ-5 impact?', '', 'Финальный вердикт (русский, markdown):', '', `# Post-deploy verify: ${prev} -> ${cur}`, '', '## Вердикт', '- [ ] safe', '- [ ] regression-detected', '- [ ] needs-more-time (если прошло мало времени)', '', '## Новые fingerprint\'ы', '| FP | Message | Count (15m) | First seen | Verdict |', '|----|---------|-------------|------------|---------|', '', '## CRITICAL spike', '- До deploy: ...', '- После deploy: ...', '', '## Рекомендация', '- (continue / rollback / hotfix / monitor)', '', 'Без emoji.', ].join('\n'); return userMessage(text); }); registered.push('post-deploy-verify'); opts.log?.info?.({ prompts: registered }, 'MCP prompts registered'); return registered; } //# sourceMappingURL=index.js.map