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

150 lines 7.18 kB
import { ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js'; import { ToolError } from '../lib/errors.js'; import { handleListApps } from '../tools/listApps.js'; import { handleErrorStatsBreakdown } from '../tools/errorStatsBreakdown.js'; import { handleSearchLogs } from '../tools/searchLogs.js'; import { handleGetLogDetails } from '../tools/getLogDetails.js'; export const ALL_RESOURCE_URIS = [ 'els://apps', 'els://apps/{slug}/stats/24h', 'els://apps/{slug}/recent-critical', 'els://logs/{traceId}', 'els://apps/{slug}/saved-queries', ]; function buildJsonResource(uri, payload) { return { contents: [ { uri, mimeType: 'application/json', text: JSON.stringify(payload, null, 2), }, ], }; } function extractStructured(result) { if (result.isError) { return { error: result.structuredContent ?? { code: 'INTERNAL' } }; } return result.structuredContent ?? {}; } export function registerResources(server, opts) { const { client } = opts; const registered = []; // ─── els://apps ───────────────────────────────────────────────────────── server.registerResource('els-apps', 'els://apps', { title: 'List of apps accessible to current API key', description: 'Returns apps available to the current API key. For master keys — all apps; for regular keys — single current app.', mimeType: 'application/json', }, async (uri) => { const result = await handleListApps({}, client); return buildJsonResource(uri.toString(), extractStructured(result)); }); registered.push('els://apps'); // ─── els://apps/{slug}/stats/24h ───────────────────────────────────────── server.registerResource('els-app-stats-24h', new ResourceTemplate('els://apps/{slug}/stats/24h', { list: undefined }), { title: 'App stats for the last 24 hours', description: 'Aggregated stats for a single app over the last 24 hours: total, unique fingerprints, distinct users/services/urls, byLevel breakdown.', mimeType: 'application/json', }, async (uri, variables) => { const slug = String(variables.slug ?? ''); const fromIso = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(); const toIso = new Date().toISOString(); try { const result = await handleErrorStatsBreakdown({ from: fromIso, to: toIso, compareTo: 'none' }, client); const payload = { slug, window: '24h', from: fromIso, to: toIso, ...extractStructured(result) }; return buildJsonResource(uri.toString(), payload); } catch (err) { if (err instanceof ToolError) { return buildJsonResource(uri.toString(), { slug, error: { code: err.code, message: err.message }, }); } throw err; } }); registered.push('els://apps/{slug}/stats/24h'); // ─── els://apps/{slug}/recent-critical ─────────────────────────────────── server.registerResource('els-app-recent-critical', new ResourceTemplate('els://apps/{slug}/recent-critical', { list: undefined }), { title: 'Recent CRITICAL errors (last hour, top 50)', description: 'Up to 50 CRITICAL errors from the last hour, newest first. No subscriptions yet (each read = fresh upstream call).', mimeType: 'application/json', }, async (uri, variables) => { const slug = String(variables.slug ?? ''); const fromIso = new Date(Date.now() - 60 * 60 * 1000).toISOString(); try { const result = await handleSearchLogs({ limit: 50, sortBy: 'receivedAt', sortOrder: 'desc', from: fromIso, level: ['CRITICAL'], response_format: 'compact', }, client); const sc = extractStructured(result); const payload = { slug, items: sc.items ?? [], total: sc.total ?? 0, lastUpdated: new Date().toISOString(), _meta: sc._meta, }; return buildJsonResource(uri.toString(), payload); } catch (err) { if (err instanceof ToolError) { return buildJsonResource(uri.toString(), { slug, error: { code: err.code, message: err.message }, }); } throw err; } }); registered.push('els://apps/{slug}/recent-critical'); // ─── els://logs/{traceId} ──────────────────────────────────────────────── server.registerResource('els-log-details', new ResourceTemplate('els://logs/{traceId}', { list: undefined }), { title: 'Full error log details by traceId', description: 'Identical payload to get_log_details (response_format=full).', mimeType: 'application/json', }, async (uri, variables) => { const traceId = String(variables.traceId ?? ''); try { const result = await handleGetLogDetails({ traceId, response_format: 'full' }, client); return buildJsonResource(uri.toString(), extractStructured(result)); } catch (err) { if (err instanceof ToolError) { return buildJsonResource(uri.toString(), { traceId, error: { code: err.code, message: err.message }, }); } throw err; } }); registered.push('els://logs/{traceId}'); // ─── els://apps/{slug}/saved-queries (V2 placeholder) ──────────────────── server.registerResource('els-app-saved-queries', new ResourceTemplate('els://apps/{slug}/saved-queries', { list: undefined }), { title: 'Saved JQL queries (not implemented yet)', description: 'Placeholder for V2: saved JQL queries per app. Returns empty list with notImplemented marker until a future release.', mimeType: 'application/json', }, async (uri, variables) => { const slug = String(variables.slug ?? ''); return buildJsonResource(uri.toString(), { slug, items: [], _meta: { notImplemented: true, plannedFor: 'next-release (post-GA)', rationale: 'Saved queries are a UI feature in LK; MCP will deliver when the JQL editor is stable.', }, }); }); registered.push('els://apps/{slug}/saved-queries'); opts.log?.info?.({ resources: registered }, 'MCP resources registered'); return registered; } //# sourceMappingURL=index.js.map