From ca2bd19c4851a8f9aaa241f083315fb4d0e1add2 Mon Sep 17 00:00:00 2001 From: asepharyana Date: Thu, 20 Aug 2026 11:45:32 +0700 Subject: [PATCH] fix(core): strip YAML frontmatter in getDocument before web/MCP render getDocument preferred the on-disk file via readFileSync (raw), returning the full frontmatter block as visible text on doc pages and MCP resources. ReactMarkdown rendered the '---' delimiters as
, exposing YAML keys (id/title/type/etc.) as plain paragraphs. Root cause: getDocument bypassed parseFile's gray-matter stripping. The indexer correctly stripped frontmatter (DB body was clean), but getDocument read raw from disk for the 'source of truth' path. Fix: use parseFile(abs, relPath).body (same as the indexer) so the on-disk path returns frontmatter-free markdown. DB fallback unchanged. Affects web doc pages + MCP mcpedia://docs/{+slug} and /chunks resources. Verified: frontmatter leakage 0/9 doc pages, headings render as

, browser snapshot clean. --- packages/core/src/document.service.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/core/src/document.service.ts b/packages/core/src/document.service.ts index f102e24..f66f750 100644 --- a/packages/core/src/document.service.ts +++ b/packages/core/src/document.service.ts @@ -2,7 +2,8 @@ import { and, eq, sql } from "drizzle-orm"; import { db } from "@mcpedia/db"; import { documentChunks, documents } from "@mcpedia/db/schema"; import { CONTENT_ROOT } from "@mcpedia/config"; -import { existsSync, readFileSync } from "node:fs"; +import { parseFile } from "@mcpedia/parser"; +import { existsSync } from "node:fs"; import { join } from "node:path"; import type { Document, @@ -33,8 +34,16 @@ export async function getDocument(slug: string): Promise { const [row] = await db.select().from(documents).where(eq(documents.slug, slug)); if (!row) return null; // Prefer the on-disk file (source of truth); fall back to stored body. + // Use parseFile (gray-matter) so the frontmatter is stripped — matches what + // the indexer stores and what ReactMarkdown expects. const abs = join(CONTENT_ROOT, row.path); - const body = existsSync(abs) ? readFileSync(abs, "utf8") : row.body; + let body: string; + if (existsSync(abs)) { + const { body: parsedBody } = parseFile(abs, row.path); + body = parsedBody; + } else { + body = row.body; + } return { ...toMeta(row), body }; }