feat(mcpedia): Phase 1 MVP — monorepo, Core, Web UI, MCP server, Postgres FTS

- bun workspaces + Turborepo monorepo (apps/web, apps/mcp; packages/*, scripts)
- @mcpedia/core single business-logic layer (Document/Content/Search services)
- @mcpedia/db Drizzle schema: documents + weighted tsvector (GIN) for FTS
- @mcpedia/parser frontmatter, @mcpedia/search Postgres FTS (ts_rank+ts_headline)
- Next.js 16 Web UI (home/doc SSG, search dynamic) + react-markdown render
- MCP server (stdio) with 4 tools + in-memory smoke test
- scripts/indexer walks content/ -> upserts into Postgres
- 4 seed docs; README + PHASES status
This commit is contained in:
asepharyana
2026-08-19 17:40:14 +07:00
commit ec3a2867d4
60 changed files with 3723 additions and 0 deletions
+13
View File
@@ -0,0 +1,13 @@
{
"name": "@mcpedia/parser",
"version": "0.1.0",
"private": true,
"type": "module",
"exports": {
".": "./src/index.ts"
},
"dependencies": {
"@mcpedia/types": "workspace:*",
"gray-matter": "^4.0.3"
}
}
+65
View File
@@ -0,0 +1,65 @@
import matter from "gray-matter";
import { readFileSync } from "node:fs";
import type {
DocSection,
DocStatus,
DocType,
DocumentMeta,
} from "@mcpedia/types";
const SECTIONS: DocSection[] = ["docs", "writeups", "research", "notes"];
const VALID_TYPES: DocType[] = [
"documentation",
"writeup",
"research",
"note",
];
export interface ParsedFile {
meta: DocumentMeta;
body: string;
}
/**
* Parse a content markdown file into metadata + body.
* @param absPath absolute path on disk
* @param relPath path relative to content root (e.g. "docs/websocket/contract.md")
*/
export function parseFile(absPath: string, relPath: string): ParsedFile {
const raw = readFileSync(absPath, "utf8");
const { data, content } = matter(raw);
const section: DocSection =
(SECTIONS.find((s) => relPath.startsWith(s + "/")) as DocSection | undefined) ??
"docs";
const slug = relPath.replace(/\.mdx?$/, "");
const type = (VALID_TYPES.includes(data.type) ? data.type : "documentation") as DocType;
const status = (data.status === "draft" ? "draft" : "published") as DocStatus;
const tags: string[] = Array.isArray(data.tags)
? data.tags.map((t: unknown) => String(t))
: [];
const nowIso = new Date().toISOString();
const createdAt = data.created_at ?? nowIso;
const updatedAt = data.updated_at ?? data.created_at ?? nowIso;
const meta: DocumentMeta = {
id: slug,
slug,
title: typeof data.title === "string" ? data.title : slug,
type,
section,
status,
author: typeof data.author === "string" ? data.author : "",
tags,
path: relPath,
createdAt: String(createdAt),
updatedAt: String(updatedAt),
};
return { meta, body: content };
}