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

174 lines (168 loc) 7.88 kB
import { z } from 'zod'; import { ToolError } from '../lib/errors.js'; import { mistralChatJson, getMistralModel } from '../ai/mistralClient.js'; /** * Tool: explain_error * * Composite: get_log_details + (optional) find_similar_errors + find_correlated_errors. * Если `MISTRAL_API_KEY` задан — добавляем AI summary / likelyCauses / nextSteps. * Иначе возвращаем raw context с `aiAvailable: false` — LLM-клиент сам формулирует. */ export const explainErrorInputShape = { traceId: z.string().min(1).max(128), locale: z.enum(['ru', 'en']).default('ru'), includeRelated: z.boolean().default(true), }; export const explainErrorToolDef = { name: 'explain_error', title: 'AI-assisted explanation of an error', description: [ 'Fetch error details + related context (similar + correlated) and, if a Mistral key is', 'configured, return a structured explanation: summary, likelyCauses, nextSteps.', 'Falls back to raw context when AI provider is offline.', '', 'WHEN TO USE:', ' - User asks "what is this error?" with a traceId - one call to get full picture.', ' - Onboarding new engineer - friendly explanation of unfamiliar errors.', ' - Drafting an incident note - get summary + likely causes for write-up.', ].join('\n'), inputShape: explainErrorInputShape, }; const SYSTEM_PROMPT_RU = `Ты — SRE-инженер с опытом анализа production-инцидентов в Node.js / TypeScript / Express / Postgres / Redis. Тебе передадут JSON с одним trace-логом ошибки и контекстом: количество подобных ошибок за сутки и список коррелированных trace'ов в том же временном окне. Верни СТРОГО валидный JSON со следующей схемой (никакого markdown / лишнего текста): { "summary": "1-2 предложения на русском: что именно случилось простыми словами", "likelyCauses": ["причина 1", "причина 2", "причина 3"], "nextSteps": ["шаг 1", "шаг 2", "шаг 3"] } Правила: 2-4 элемента в каждом массиве; конкретика выше шаблонов; не выдумывай факты которых нет в данных; если данных мало — говори об этом в summary.`; const SYSTEM_PROMPT_EN = `You are an SRE engineer specialised in Node.js / TypeScript / Express / Postgres / Redis production incidents. You will receive a JSON payload with a single error trace and context: similar-error count over 24h and a list of correlated traceIds in the same time window. Return STRICT valid JSON only (no markdown, no extra text): { "summary": "1-2 sentences in English: what happened in plain words", "likelyCauses": ["cause 1", "cause 2", "cause 3"], "nextSteps": ["step 1", "step 2", "step 3"] } Rules: 2-4 items per array; concrete over generic; don't invent facts not in the data; if data is sparse, say so in the summary.`; function parseAiResponse(raw) { if (!raw) return null; try { const parsed = JSON.parse(raw); const summary = typeof parsed.summary === 'string' ? parsed.summary.trim() : ''; const likelyCauses = Array.isArray(parsed.likelyCauses) ? parsed.likelyCauses.filter((x) => typeof x === 'string').slice(0, 6) : []; const nextSteps = Array.isArray(parsed.nextSteps) ? parsed.nextSteps.filter((x) => typeof x === 'string').slice(0, 6) : []; if (!summary) return null; return { summary, likelyCauses, nextSteps }; } catch { return null; } } export async function handleExplainError(args, client) { try { const { data: details, elsRequestId } = await client.getLogDetails(args.traceId); let similarCount24h; let correlatedTraces; const warnings = []; if (args.includeRelated) { try { const { data: similar } = await client.findSimilarErrors(args.traceId, {}); const sim = (similar ?? {}); if (typeof sim.last24h === 'number') similarCount24h = sim.last24h; } catch (err) { warnings.push(`find_similar_errors failed: ${err instanceof ToolError ? err.code : 'INTERNAL'}`); } try { const { data: correlated } = await client.findCorrelatedErrors(args.traceId, {}); const corr = (correlated ?? {}); const list = Array.isArray(corr.correlated) ? corr.correlated : []; correlatedTraces = list .map((c) => (typeof c.traceId === 'string' ? c.traceId : null)) .filter((t) => t !== null) .slice(0, 10); } catch (err) { warnings.push(`find_correlated_errors failed: ${err instanceof ToolError ? err.code : 'INTERNAL'}`); } } const aiKey = process.env.MISTRAL_API_KEY; let ai = null; let model = null; if (aiKey && aiKey.length >= 20 && !aiKey.startsWith('your_')) { const systemPrompt = args.locale === 'en' ? SYSTEM_PROMPT_EN : SYSTEM_PROMPT_RU; const userPayload = { details, relatedContext: { similarCount24h: similarCount24h ?? null, correlatedTraces: correlatedTraces ?? [], }, }; const raw = await mistralChatJson(aiKey, { systemPrompt, userContent: JSON.stringify(userPayload), }); ai = parseAiResponse(raw); if (ai) { model = getMistralModel(); } else if (raw === null) { warnings.push('mistral_unavailable: upstream timeout or non-2xx response'); } else { warnings.push('mistral_parse_failed: response not in expected JSON shape'); } } else { warnings.push('mistral_not_configured: MISTRAL_API_KEY missing — AI summary unavailable'); } const aiAvailable = ai !== null; const meta = { elsRequestId, cached: false, ttlSec: 60, redactionApplied: false, degraded: !aiAvailable, ...(warnings.length > 0 ? { warnings } : {}), }; const textSummary = aiAvailable ? `[explain_error] traceId=${args.traceId}: ${ai.summary}` : `[explain_error] traceId=${args.traceId}: details fetched, AI summary unavailable. The LLM should synthesize an explanation from the returned context.`; return { structuredContent: { traceId: args.traceId, summary: ai?.summary ?? null, likelyCauses: ai?.likelyCauses ?? [], nextSteps: ai?.nextSteps ?? [], details, related: { ...(similarCount24h !== undefined ? { similarCount24h } : {}), ...(correlatedTraces !== undefined ? { correlatedTraces } : {}), }, meta: { model, aiAvailable }, _meta: meta, }, content: [ { type: 'text', text: textSummary, }, ], }; } catch (err) { if (err instanceof ToolError) return err.toToolResult(); throw err; } } //# sourceMappingURL=explainError.js.map