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