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