convert_markdown_to_html
Convert Markdown files to styled HTML with theme support and table of contents generation for document processing workflows.
Instructions
Enhanced Markdown to HTML conversion with beautiful styling and theme support. Supports GitHub, Academic, Modern, and Default themes with complete style preservation. Output directory is controlled by OUTPUT_DIR environment variable. Files will be automatically saved to OUTPUT_DIR with auto-generated names.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| markdownPath | Yes | Markdown file path to convert | |
| theme | No | Theme to apply | github |
| includeTableOfContents | No | Generate table of contents | |
| customCSS | No | Additional custom CSS styles |
Implementation Reference
- The primary handler function that performs the Markdown to HTML conversion, including reading the input file, parsing Markdown using 'marked', applying CSS themes, generating complete HTML documents, and handling output saving and metadata generation.
async convertMarkdownToHtml( inputPath: string, options: MarkdownToHtmlOptions = {} ): Promise<MarkdownConversionResult> { try { this.options = { preserveStyles: true, theme: 'default', includeTableOfContents: false, enableSyntaxHighlighting: false, standalone: true, debug: false, ...options, }; if (this.options.debug) { console.log('🚀 开始 Markdown 到 HTML 转换...'); console.log('📄 输入文件:', inputPath); console.log('🎨 使用主题:', this.options.theme); } // 读取 Markdown 文件 const markdownContent = await fs.readFile(inputPath, 'utf-8'); // 配置 marked this.configureMarked(); // 转换为 HTML const htmlContent = marked.parse(markdownContent); // 分析内容统计 const stats = this.analyzeContent(htmlContent); // 生成完整的 HTML 文档 const completeHtml = this.generateCompleteHtml(htmlContent); // 保存文件(如果指定了输出路径) let htmlPath: string | undefined; if (this.options.outputPath) { const { validateAndSanitizePath } = require('../security/securityConfig'); // 移除路径限制,允许访问任意目录(与index.ts中的validatePath函数保持一致) htmlPath = validateAndSanitizePath(this.options.outputPath, []); await fs.writeFile(htmlPath, completeHtml, 'utf-8'); if (this.options.debug) { console.log('✅ HTML 文件已保存:', htmlPath); } } if (this.options.debug) { console.log('📊 转换统计:', stats); console.log('✅ Markdown 转换完成'); } return { success: true, content: completeHtml, htmlPath, metadata: { originalFormat: 'markdown', targetFormat: 'html', stylesPreserved: this.options.preserveStyles ?? false, theme: this.options.theme ?? 'default', converter: 'markdown-to-html-converter', contentLength: completeHtml.length, ...stats, }, }; } catch (error: any) { console.error('❌ Markdown 转换失败:', error.message); return { success: false, error: error.message, }; } } - TypeScript interface defining the input options/schema for the Markdown to HTML conversion tool, including theme selection, style preservation, TOC inclusion, and other configuration parameters.
interface MarkdownToHtmlOptions { preserveStyles?: boolean; theme?: 'default' | 'github' | 'academic' | 'modern'; includeTableOfContents?: boolean; enableSyntaxHighlighting?: boolean; customCSS?: string; outputPath?: string; standalone?: boolean; debug?: boolean; } - TypeScript interface defining the output schema/result structure for the conversion, including success status, generated HTML content, file paths, detailed metadata, and error handling.
interface MarkdownConversionResult { success: boolean; content?: string; htmlPath?: string; cssPath?: string; metadata?: { originalFormat: string; targetFormat: string; stylesPreserved: boolean; theme: string; converter: string; contentLength: number; headingsCount: number; linksCount: number; imagesCount: number; }; error?: string; } - Convenience export function that instantiates the converter class and delegates to the main handler method, serving as the primary entry point likely used by MCP tool registration.
export async function convertMarkdownToHtml( inputPath: string, options: MarkdownToHtmlOptions = {} ): Promise<MarkdownConversionResult> { const converter = new MarkdownToHtmlConverter(); return await converter.convertMarkdownToHtml(inputPath, options); } - Core converter class containing helper methods for theme management (initializeThemes), marked configuration, content analysis, complete HTML generation, and table of contents creation.
class MarkdownToHtmlConverter { private options: MarkdownToHtmlOptions = {}; private themes: Map<string, string>; constructor() { this.themes = new Map(); this.initializeThemes(); } /** * 初始化预设主题 */ private initializeThemes(): void { // GitHub 风格主题 this.themes.set( 'github', ` body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 1.5; color: #24292f; background-color: #ffffff; max-width: 980px; margin: 0 auto; padding: 45px; } h1, h2, h3, h4, h5, h6 { margin-top: 24px; margin-bottom: 16px; font-weight: 600; line-height: 1.25; } h1 { font-size: 2em; border-bottom: 1px solid #d0d7de; padding-bottom: 0.3em; } h2 { font-size: 1.5em; border-bottom: 1px solid #d0d7de; padding-bottom: 0.3em; } h3 { font-size: 1.25em; } h4 { font-size: 1em; } h5 { font-size: 0.875em; } h6 { font-size: 0.85em; color: #656d76; } p { margin-top: 0; margin-bottom: 16px; } blockquote { padding: 0 1em; color: #656d76; border-left: 0.25em solid #d0d7de; margin: 0 0 16px 0; } ul, ol { margin-top: 0; margin-bottom: 16px; padding-left: 2em; } li { margin-bottom: 0.25em; } code { padding: 0.2em 0.4em; margin: 0; font-size: 85%; background-color: rgba(175,184,193,0.2); border-radius: 6px; font-family: ui-monospace, SFMono-Regular, 'SF Mono', Consolas, 'Liberation Mono', Menlo, monospace; } pre { padding: 16px; overflow: auto; font-size: 85%; line-height: 1.45; background-color: #f6f8fa; border-radius: 6px; margin-bottom: 16px; } pre code { background-color: transparent; border: 0; padding: 0; margin: 0; font-size: 100%; } table { border-spacing: 0; border-collapse: collapse; margin-bottom: 16px; width: 100%; } table th, table td { padding: 6px 13px; border: 1px solid #d0d7de; } table th { font-weight: 600; background-color: #f6f8fa; } table tr:nth-child(2n) { background-color: #f6f8fa; } a { color: #0969da; text-decoration: none; } a:hover { text-decoration: underline; } img { max-width: 100%; height: auto; margin: 16px 0; } hr { height: 0.25em; padding: 0; margin: 24px 0; background-color: #d0d7de; border: 0; } ` ); // 学术风格主题 this.themes.set( 'academic', ` body { font-family: 'Times New Roman', Times, serif; font-size: 12pt; line-height: 1.6; color: #000000; background-color: #ffffff; max-width: 210mm; margin: 0 auto; padding: 25mm; } h1, h2, h3, h4, h5, h6 { font-family: 'Times New Roman', Times, serif; font-weight: bold; margin-top: 18pt; margin-bottom: 12pt; text-align: left; } h1 { font-size: 18pt; text-align: center; margin-bottom: 24pt; } h2 { font-size: 14pt; margin-top: 24pt; } h3 { font-size: 12pt; font-style: italic; } p { margin: 0 0 12pt 0; text-align: justify; text-indent: 2em; } blockquote { margin: 12pt 2em; padding: 0; font-style: italic; border-left: none; } ul, ol { margin: 12pt 0; padding-left: 2em; } li { margin-bottom: 6pt; } table { border-collapse: collapse; margin: 12pt auto; width: 100%; } table th, table td { border: 1pt solid #000; padding: 6pt; text-align: left; } table th { font-weight: bold; background-color: #f0f0f0; } code { font-family: 'Courier New', Courier, monospace; font-size: 10pt; } pre { font-family: 'Courier New', Courier, monospace; font-size: 10pt; margin: 12pt 0; padding: 12pt; border: 1pt solid #ccc; background-color: #f9f9f9; } a { color: #000; text-decoration: underline; } img { max-width: 100%; height: auto; display: block; margin: 12pt auto; } ` ); // 现代风格主题 this.themes.set( 'modern', ` body { font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 16px; line-height: 1.7; color: #2d3748; background-color: #ffffff; max-width: 768px; margin: 0 auto; padding: 2rem; } h1, h2, h3, h4, h5, h6 { font-weight: 700; margin-top: 2rem; margin-bottom: 1rem; color: #1a202c; } h1 { font-size: 2.5rem; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; margin-bottom: 2rem; } h2 { font-size: 2rem; border-left: 4px solid #667eea; padding-left: 1rem; } h3 { font-size: 1.5rem; color: #4a5568; } p { margin-bottom: 1.5rem; color: #4a5568; } blockquote { border-left: 4px solid #e2e8f0; padding: 1rem 1.5rem; margin: 1.5rem 0; background-color: #f7fafc; border-radius: 0 8px 8px 0; font-style: italic; } ul, ol { margin: 1.5rem 0; padding-left: 2rem; } li { margin-bottom: 0.5rem; color: #4a5568; } code { background-color: #edf2f7; color: #e53e3e; padding: 0.25rem 0.5rem; border-radius: 4px; font-family: 'Fira Code', 'Consolas', monospace; font-size: 0.875rem; } pre { background-color: #2d3748; color: #e2e8f0; padding: 1.5rem; border-radius: 8px; overflow-x: auto; margin: 1.5rem 0; } pre code { background-color: transparent; color: inherit; padding: 0; } table { width: 100%; border-collapse: collapse; margin: 1.5rem 0; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); border-radius: 8px; overflow: hidden; } table th, table td { padding: 1rem; text-align: left; border-bottom: 1px solid #e2e8f0; } table th { background-color: #f7fafc; font-weight: 600; color: #2d3748; } table tr:hover { background-color: #f7fafc; } a { color: #667eea; text-decoration: none; border-bottom: 1px solid transparent; transition: border-color 0.2s; } a:hover { border-bottom-color: #667eea; } img { max-width: 100%; height: auto; border-radius: 8px; margin: 1.5rem 0; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); } hr { border: none; height: 2px; background: linear-gradient(90deg, #667eea, #764ba2); margin: 2rem 0; border-radius: 1px; } ` ); // 默认主题 this.themes.set( 'default', ` body { font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 1.6; color: #333; background-color: #fff; max-width: 800px; margin: 0 auto; padding: 2rem; } h1, h2, h3, h4, h5, h6 { margin-top: 1.5rem; margin-bottom: 1rem; font-weight: bold; } h1 { font-size: 2.5rem; color: #2c3e50; } h2 { font-size: 2rem; color: #34495e; } h3 { font-size: 1.5rem; color: #34495e; } h4 { font-size: 1.25rem; } h5 { font-size: 1rem; } h6 { font-size: 0.875rem; } p { margin-bottom: 1rem; } blockquote { border-left: 4px solid #3498db; padding-left: 1rem; margin: 1rem 0; color: #7f8c8d; font-style: italic; } ul, ol { margin: 1rem 0; padding-left: 2rem; } code { background-color: #f8f9fa; padding: 0.25rem 0.5rem; border-radius: 3px; font-family: 'Consolas', 'Monaco', monospace; } pre { background-color: #f8f9fa; padding: 1rem; border-radius: 5px; overflow-x: auto; margin: 1rem 0; } table { border-collapse: collapse; width: 100%; margin: 1rem 0; } table th, table td { border: 1px solid #ddd; padding: 0.75rem; text-align: left; } table th { background-color: #f8f9fa; font-weight: bold; } a { color: #3498db; text-decoration: none; } a:hover { text-decoration: underline; } img { max-width: 100%; height: auto; margin: 1rem 0; } ` ); } /** * 主转换函数 */ async convertMarkdownToHtml( inputPath: string, options: MarkdownToHtmlOptions = {} ): Promise<MarkdownConversionResult> { try { this.options = { preserveStyles: true, theme: 'default', includeTableOfContents: false, enableSyntaxHighlighting: false, standalone: true, debug: false, ...options, }; if (this.options.debug) { console.log('🚀 开始 Markdown 到 HTML 转换...'); console.log('📄 输入文件:', inputPath); console.log('🎨 使用主题:', this.options.theme); } // 读取 Markdown 文件 const markdownContent = await fs.readFile(inputPath, 'utf-8'); // 配置 marked this.configureMarked(); // 转换为 HTML const htmlContent = marked.parse(markdownContent); // 分析内容统计 const stats = this.analyzeContent(htmlContent); // 生成完整的 HTML 文档 const completeHtml = this.generateCompleteHtml(htmlContent); // 保存文件(如果指定了输出路径) let htmlPath: string | undefined; if (this.options.outputPath) { const { validateAndSanitizePath } = require('../security/securityConfig'); // 移除路径限制,允许访问任意目录(与index.ts中的validatePath函数保持一致) htmlPath = validateAndSanitizePath(this.options.outputPath, []); await fs.writeFile(htmlPath, completeHtml, 'utf-8'); if (this.options.debug) { console.log('✅ HTML 文件已保存:', htmlPath); } } if (this.options.debug) { console.log('📊 转换统计:', stats); console.log('✅ Markdown 转换完成'); } return { success: true, content: completeHtml, htmlPath, metadata: { originalFormat: 'markdown', targetFormat: 'html', stylesPreserved: this.options.preserveStyles ?? false, theme: this.options.theme ?? 'default', converter: 'markdown-to-html-converter', contentLength: completeHtml.length, ...stats, }, }; } catch (error: any) { console.error('❌ Markdown 转换失败:', error.message); return { success: false, error: error.message, }; } } /** * 配置 marked 解析器 */ private configureMarked(): void { marked.setOptions({ gfm: true, // GitHub Flavored Markdown breaks: true, // 支持换行 pedantic: false, sanitize: false, smartLists: true, smartypants: true, }); // 使用更简单的配置,避免自定义渲染器的问题 // 暂时移除自定义渲染器,使用默认的marked渲染 } /** * 分析内容统计信息 */ private analyzeContent(htmlContent: string): { headingsCount: number; linksCount: number; imagesCount: number; } { const $ = cheerio.load(htmlContent); return { headingsCount: $('h1, h2, h3, h4, h5, h6').length, linksCount: $('a').length, imagesCount: $('img').length, }; } /** * 生成完整的 HTML 文档 */ private generateCompleteHtml(content: string): string { if (!this.options.standalone) { return content; } const theme = this.options.theme ?? 'default'; const themeCSS = this.themes.get(theme) ?? this.themes.get('default')!; const customCSS = this.options.customCSS ?? ''; // 生成目录(如果启用) let tocHtml = ''; if (this.options.includeTableOfContents) { tocHtml = this.generateTableOfContents(content); } return `<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Markdown Document</title> <style> ${themeCSS} ${customCSS} </style> </head> <body> ${tocHtml} ${content} </body> </html>`; } /** * 生成目录 */ private generateTableOfContents(content: string): string { const $ = cheerio.load(content); const headings = $('h1, h2, h3, h4, h5, h6'); if (headings.length === 0) { return ''; } let toc = '<div class="table-of-contents">\n<h2>目录</h2>\n<ul>\n'; headings.each((index, element) => { const $heading = $(element); const level = parseInt(element.tagName.substring(1)); const text = $heading.text(); const id = $heading.attr('id') ?? text.toLowerCase().replace(/[^\w]+/g, '-'); const indent = ' '.repeat(level - 1); toc += `${indent}<li><a href="#${id}">${text}</a></li>\n`; }); toc += '</ul>\n</div>\n\n'; return toc; } /** * 获取可用主题列表 */ getAvailableThemes(): string[] { return Array.from(this.themes.keys()); } /** * 添加自定义主题 */ addCustomTheme(name: string, css: string): void { this.themes.set(name, css); } }