/**
* Srcbook Parser for ClearThought MCP Server
*
* Parses .src.md files following Srcbook format to extract cells
* and make them available as MCP resources with embedded instructions.
*/
import { randomUUID } from "node:crypto";
import type { Token } from "marked";
import { marked } from "marked";
// Cell types matching Srcbook schema
export interface TitleCell {
id: string;
type: "title";
text: string;
}
export interface MarkdownCell {
id: string;
type: "markdown";
text: string;
}
export interface PackageJsonCell {
id: string;
type: "package.json";
source: string;
filename: "package.json";
}
export interface CodeCell {
id: string;
type: "code";
source: string;
language: "javascript" | "typescript";
filename: string;
}
export type Cell = TitleCell | MarkdownCell | PackageJsonCell | CodeCell;
export interface SrcbookMetadata {
language: "javascript" | "typescript";
"tsconfig.json"?: string;
}
export interface ParsedSrcbook {
metadata: SrcbookMetadata;
cells: Cell[];
title: string;
}
export interface SrcbookResource {
uri: string;
name: string;
title?: string;
description?: string;
mimeType: string;
annotations?: {
audience?: ("user" | "assistant")[];
priority?: number;
capabilities?: string[];
instructions?: any;
metadata?: any;
};
}
const SRCBOOK_METADATA_RE = /^<!--\s*srcbook:(.+)\s*-->$/;
/**
* Parse a .src.md file into a structured Srcbook format
*/
export function parseSrcbook(
contents: string,
filename: string,
): ParsedSrcbook {
// Parse markdown tokens
const tokens = marked.lexer(contents);
// Extract metadata
const metadata = extractMetadata(tokens);
// Extract cells
const cells = extractCells(tokens, metadata.language);
// Get title from first cell
const titleCell = cells.find((c) => c.type === "title") as TitleCell;
const title = titleCell?.text || filename.replace(".src.md", "");
return {
metadata,
cells,
title,
};
}
/**
* Extract Srcbook metadata from tokens
*/
function extractMetadata(tokens: Token[]): SrcbookMetadata {
for (const token of tokens) {
if (token.type !== "html") continue;
const match = token.raw.trim().match(SRCBOOK_METADATA_RE);
if (match) {
try {
return JSON.parse(match[1]);
} catch (e) {
console.error("Failed to parse srcbook metadata:", e);
}
}
}
// Default to JavaScript if no metadata found
return { language: "javascript" };
}
/**
* Extract cells from markdown tokens
*/
function extractCells(
tokens: Token[],
language: "javascript" | "typescript",
): Cell[] {
const cells: Cell[] = [];
let currentMarkdown = "";
for (const token of tokens) {
// Skip metadata comments
if (token.type === "html" && token.raw.match(SRCBOOK_METADATA_RE)) {
continue;
}
// Title cell (H1)
if (token.type === "heading" && token.depth === 1) {
if (currentMarkdown.trim()) {
cells.push({
id: randomUUID(),
type: "markdown",
text: currentMarkdown.trim(),
});
currentMarkdown = "";
}
cells.push({
id: randomUUID(),
type: "title",
text: token.text,
});
continue;
}
// Code cell with filename (H6 followed by code block)
if (token.type === "heading" && token.depth === 6) {
if (currentMarkdown.trim()) {
cells.push({
id: randomUUID(),
type: "markdown",
text: currentMarkdown.trim(),
});
currentMarkdown = "";
}
// Look for the next code block
const nextIndex = tokens.indexOf(token) + 1;
if (nextIndex < tokens.length) {
const nextToken = tokens[nextIndex];
if (nextToken && nextToken.type === "code") {
const filename = token.text;
if (filename === "package.json") {
cells.push({
id: randomUUID(),
type: "package.json",
source: nextToken.text,
filename: "package.json",
});
} else {
cells.push({
id: randomUUID(),
type: "code",
source: nextToken.text,
language: filename.endsWith(".ts") ? "typescript" : language,
filename,
});
}
// Skip the code block token since we've processed it
tokens.splice(nextIndex, 1);
}
}
continue;
}
// Regular code block without filename
if (token.type === "code" && !token.lang?.startsWith("```")) {
if (currentMarkdown.trim()) {
cells.push({
id: randomUUID(),
type: "markdown",
text: currentMarkdown.trim(),
});
currentMarkdown = "";
}
// Only create a code cell if it's JS/TS code
if (
!token.lang ||
["javascript", "typescript", "js", "ts"].includes(token.lang)
) {
cells.push({
id: randomUUID(),
type: "code",
source: token.text,
language:
token.lang === "typescript" || token.lang === "ts"
? "typescript"
: language,
filename: `cell-${cells.length}.${language === "typescript" ? "ts" : "js"}`,
});
} else {
// Non-JS/TS code blocks become part of markdown
currentMarkdown += token.raw;
}
continue;
}
// Accumulate markdown content
currentMarkdown += token.raw;
}
// Add any remaining markdown
if (currentMarkdown.trim()) {
cells.push({
id: randomUUID(),
type: "markdown",
text: currentMarkdown.trim(),
});
}
return cells;
}
/**
* Convert a parsed Srcbook into MCP resource format
*/
export function srcbookToResource(
srcbook: ParsedSrcbook,
notebookName: string,
): SrcbookResource {
const codeLanguage = srcbook.metadata.language;
const cellCount = srcbook.cells.length;
const codeCellCount = srcbook.cells.filter((c) => c.type === "code").length;
return {
uri: `notebook:///${notebookName}`,
name: srcbook.title,
title: `đź““ ${srcbook.title}`,
description: `Interactive ${codeLanguage === "typescript" ? "TypeScript" : "JavaScript"} notebook with ${codeCellCount} code cells`,
mimeType: "text/markdown",
annotations: {
audience: ["assistant"],
priority: 0.8,
capabilities: [
codeLanguage,
"interactive-execution",
"literate-programming",
],
instructions: {
execution: "Use mcp__ide__executeCode to run code cells",
navigation: "Process cells sequentially for learning flow",
cellExtraction:
"Code cells are marked with ###### filename or are standalone code blocks",
interaction: `Extract ${codeLanguage} code and execute with appropriate tool`,
},
metadata: {
language: codeLanguage,
cellCount,
codeCellCount,
hasPackageJson: srcbook.cells.some((c) => c.type === "package.json"),
hasTsConfig: !!srcbook.metadata["tsconfig.json"],
},
},
};
}
/**
* Generate embedded resource for a specific cell
*/
export function cellToEmbeddedResource(
cell: Cell,
notebookName: string,
cellIndex: number,
): any {
if (cell.type === "code") {
return {
type: "resource",
resource: {
uri: `notebook:///${notebookName}#cell-${cellIndex}`,
title: cell.filename,
mimeType: "text/plain",
text: cell.source,
annotations: {
audience: ["assistant"],
instructions: `Execute this ${cell.language} code using mcp__ide__executeCode`,
cellType: "code",
language: cell.language,
filename: cell.filename,
},
},
};
} else if (cell.type === "markdown") {
return {
type: "resource",
resource: {
uri: `notebook:///${notebookName}#cell-${cellIndex}`,
title: `Cell ${cellIndex}`,
mimeType: "text/markdown",
text: cell.text,
annotations: {
audience: ["assistant"],
cellType: "markdown",
},
},
};
}
return null;
}
/**
* Encode a Srcbook back to .src.md format
*/
export function encodeSrcbook(srcbook: ParsedSrcbook): string {
const parts: string[] = [];
// Add metadata
parts.push(`<!-- srcbook:${JSON.stringify(srcbook.metadata)} -->`);
// Add cells
for (const cell of srcbook.cells) {
switch (cell.type) {
case "title":
parts.push(`# ${cell.text}`);
break;
case "markdown":
parts.push(cell.text);
break;
case "package.json":
parts.push("###### package.json");
parts.push("```json");
parts.push(cell.source);
parts.push("```");
break;
case "code":
parts.push(`###### ${cell.filename}`);
parts.push(`\`\`\`${cell.language}`);
parts.push(cell.source);
parts.push("```");
break;
}
}
return `${parts.join("\n\n")}\n`;
}