ajustes
This commit is contained in:
151
mcp-server/tools/docs/_docsReader.js
Normal file
151
mcp-server/tools/docs/_docsReader.js
Normal file
@@ -0,0 +1,151 @@
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
|
||||
/**
|
||||
* Lectura directa de los markdown del knowledge base desde el filesystem.
|
||||
*
|
||||
* El MCP server corre dentro del container `agentic` junto al FastAPI, asi
|
||||
* que los .md viven en `/app/docs/` (la imagen los copia ahi).
|
||||
*
|
||||
* En caso de override por entorno, respeta `ACAI_DOCS_DIR`. En desarrollo
|
||||
* fuera del container, fallback a paths relativos al cwd.
|
||||
*/
|
||||
|
||||
function resolveDocsDir() {
|
||||
const override = process.env.ACAI_DOCS_DIR;
|
||||
if (override) return override;
|
||||
// Container path
|
||||
return "/app/docs";
|
||||
}
|
||||
|
||||
export async function listDocs() {
|
||||
const dir = resolveDocsDir();
|
||||
let files;
|
||||
try {
|
||||
files = await fs.readdir(dir);
|
||||
} catch (err) {
|
||||
const error = new Error(`No se pudo leer el directorio de docs (${dir}): ${err.message}`);
|
||||
error.code = "DOCS_DIR_NOT_FOUND";
|
||||
throw error;
|
||||
}
|
||||
|
||||
const mdFiles = files.filter((f) => f.endsWith(".md")).sort();
|
||||
const docs = [];
|
||||
|
||||
for (const file of mdFiles) {
|
||||
const id = file.replace(/\.md$/i, "");
|
||||
const filePath = path.join(dir, file);
|
||||
const content = await fs.readFile(filePath, "utf8");
|
||||
const lines = content.split("\n");
|
||||
const titleLine = lines.find((l) => l.trim().startsWith("# ")) || id;
|
||||
const title = titleLine.replace(/^#+/, "").trim();
|
||||
|
||||
// Summary: primeras 30 lineas no-heading, capeado a 500 chars
|
||||
const summaryLines = [];
|
||||
for (const line of lines.slice(0, 30)) {
|
||||
const t = line.trim();
|
||||
if (t && !t.startsWith("#")) summaryLines.push(t);
|
||||
if (summaryLines.join(" ").length > 500) break;
|
||||
}
|
||||
const summary = summaryLines.join(" ").slice(0, 500);
|
||||
|
||||
docs.push({
|
||||
id,
|
||||
title,
|
||||
summary,
|
||||
chars: content.length,
|
||||
});
|
||||
}
|
||||
return docs;
|
||||
}
|
||||
|
||||
export async function readDoc(name, section) {
|
||||
const dir = resolveDocsDir();
|
||||
const safeName = String(name).replace(/\.md$/i, "").replace(/[\/\\]/g, "");
|
||||
const filePath = path.join(dir, `${safeName}.md`);
|
||||
|
||||
let content;
|
||||
try {
|
||||
content = await fs.readFile(filePath, "utf8");
|
||||
} catch (err) {
|
||||
const error = new Error(`Doc '${safeName}' no encontrado en ${dir}`);
|
||||
error.code = "DOC_NOT_FOUND";
|
||||
throw error;
|
||||
}
|
||||
|
||||
const titleLine = content.split("\n").find((l) => l.trim().startsWith("# ")) || safeName;
|
||||
const title = titleLine.replace(/^#+/, "").trim();
|
||||
const availableSections = listSections(content);
|
||||
|
||||
if (section) {
|
||||
const sectionContent = extractSection(content, section);
|
||||
if (sectionContent === null) {
|
||||
return {
|
||||
id: safeName,
|
||||
title,
|
||||
section_requested: section,
|
||||
section_found: false,
|
||||
available_sections: availableSections,
|
||||
chars: 0,
|
||||
content: "",
|
||||
};
|
||||
}
|
||||
return {
|
||||
id: safeName,
|
||||
title,
|
||||
section,
|
||||
section_found: true,
|
||||
chars: sectionContent.length,
|
||||
content: sectionContent,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
id: safeName,
|
||||
title,
|
||||
section: null,
|
||||
section_found: true,
|
||||
chars: content.length,
|
||||
available_sections: availableSections,
|
||||
content,
|
||||
};
|
||||
}
|
||||
|
||||
function listSections(content) {
|
||||
const sections = [];
|
||||
for (const line of content.split("\n")) {
|
||||
const t = line.trimStart();
|
||||
if (t.startsWith("## ") && !t.startsWith("### ")) {
|
||||
sections.push(t.slice(3).trim());
|
||||
}
|
||||
}
|
||||
return sections;
|
||||
}
|
||||
|
||||
function extractSection(content, query) {
|
||||
const q = String(query).toLowerCase().trim();
|
||||
if (!q) return null;
|
||||
const lines = content.split("\n");
|
||||
const captured = [];
|
||||
let capturing = false;
|
||||
|
||||
for (const line of lines) {
|
||||
const t = line.trimStart();
|
||||
const isH2 = t.startsWith("## ") && !t.startsWith("### ");
|
||||
|
||||
if (isH2) {
|
||||
const heading = t.slice(3).trim();
|
||||
if (capturing) break;
|
||||
if (heading.toLowerCase().includes(q)) {
|
||||
capturing = true;
|
||||
captured.push(line);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (capturing) captured.push(line);
|
||||
}
|
||||
|
||||
if (captured.length === 0) return null;
|
||||
return captured.join("\n").trimEnd();
|
||||
}
|
||||
Reference in New Issue
Block a user