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

134 lines 4.52 kB
import { readFileSync, existsSync } from 'node:fs'; import { join } from 'node:path'; import { z } from 'zod'; /** * Auto-discovery: чтение конфигурации проекта (`els.config.json` или * `package.json` поле `inso.els`) из списка workdir'ов. * * Использование: * - stdio: передать `[process.cwd()]` или `[process.env.ELS_PROJECT_CONFIG_DIR]`. * - HTTP: передать массив путей из `roots/list` ответа клиента. * * Возвращает первый найденный валидный config или `null`. Никогда не * throw'ит — при невалидных данных просто пропускает файл (fail-silent с * предупреждением через `onWarn`). */ const ProjectConfigSchema = z.object({ $schema: z.string().optional(), appSlug: z.string().min(1).max(255), environments: z.record(z.string(), z.string()).optional(), defaultEnvironment: z.string().optional(), alerts: z .object({ criticalRateThreshold: z.number().nonnegative().optional(), }) .optional(), }); /** * Читает `els.config.json` (приоритет) или `package.json[inso.els]` из первого * каталога, где найден валидный файл. * * @param workdirs Список абсолютных путей к каталогам для поиска. * @returns Распарсенный config или `null` если нигде не нашли. */ export function readProjectConfig(workdirs, opts = {}) { const onWarn = opts.onWarn ?? (() => undefined); for (const dir of workdirs) { if (!dir || typeof dir !== 'string') continue; // Приоритет: els.config.json → package.json[inso.els] const elsPath = join(dir, 'els.config.json'); if (existsSync(elsPath)) { const parsed = parseElsConfigJson(elsPath, onWarn); if (parsed) return parsed; } const pkgPath = join(dir, 'package.json'); if (existsSync(pkgPath)) { const parsed = parsePackageJsonInsoEls(pkgPath, onWarn); if (parsed) return parsed; } } return null; } function parseElsConfigJson(path, onWarn) { let raw; try { raw = readFileSync(path, 'utf8'); } catch (err) { onWarn('failed to read els.config.json', { path, err: errMsg(err) }); return null; } let data; try { data = JSON.parse(raw); } catch (err) { onWarn('malformed JSON in els.config.json', { path, err: errMsg(err) }); return null; } const parsed = ProjectConfigSchema.safeParse(data); if (!parsed.success) { onWarn('invalid els.config.json shape', { path, issues: parsed.error.issues.map((i) => i.message), }); return null; } return toProjectConfig(parsed.data, path); } function parsePackageJsonInsoEls(path, onWarn) { let raw; try { raw = readFileSync(path, 'utf8'); } catch (err) { onWarn('failed to read package.json', { path, err: errMsg(err) }); return null; } let data; try { data = JSON.parse(raw); } catch (err) { onWarn('malformed JSON in package.json', { path, err: errMsg(err) }); return null; } if (!data || typeof data !== 'object') return null; const obj = data; const inso = obj.inso; if (!inso || typeof inso !== 'object') return null; const els = inso.els; if (!els || typeof els !== 'object') return null; const parsed = ProjectConfigSchema.safeParse(els); if (!parsed.success) { onWarn('invalid package.json[inso.els] shape', { path, issues: parsed.error.issues.map((i) => i.message), }); return null; } return toProjectConfig(parsed.data, path); } function toProjectConfig(data, sourcePath) { const cfg = { appSlug: data.appSlug, sourcePath, }; if (data.environments) cfg.environments = data.environments; if (data.defaultEnvironment) cfg.defaultEnvironment = data.defaultEnvironment; if (data.alerts) cfg.alerts = data.alerts; return cfg; } function errMsg(err) { return err instanceof Error ? err.message : String(err); } //# sourceMappingURL=projectConfig.js.map