@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
JavaScript
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