generate
Convert Mermaid markdown into PNG or SVG images with customizable themes, background colors, and output formats using the MCP server.
Instructions
Generate PNG image or SVG from mermaid markdown
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| backgroundColor | No | Background color for the diagram, e.g. 'white', 'transparent', '#F0F0F0' (optional) | |
| code | Yes | The mermaid markdown to generate an image from | |
| folder | No | Absolute path to save the image to (optional) | |
| name | No | Name of the diagram (optional) | |
| outputFormat | No | Output format for the diagram (optional, defaults to 'png') | |
| theme | No | Theme for the diagram (optional) |
Implementation Reference
- index.ts:509-642 (handler)Main execution logic for the 'generate' tool. Processes arguments, renders the Mermaid diagram using renderMermaid, handles PNG/SVG output, optional file saving, and constructs the response content.async function processGenerateRequest(args: { code: string; theme?: "default" | "forest" | "dark" | "neutral"; backgroundColor?: string; outputFormat?: "png" | "svg"; name?: string; folder?: string; }): Promise<{ content: Array< | { type: "text"; text: string } | { type: "image"; data: string; mimeType: string } >; isError: boolean; }> { try { const outputFormat = args.outputFormat || "png"; const result = await renderMermaid(args.code, { theme: args.theme, backgroundColor: args.backgroundColor, outputFormat: outputFormat, }); // Check if we need to save the file to a folder if (!CONTENT_IMAGE_SUPPORTED) { if (!args.folder) { throw new Error( "Folder parameter is required when CONTENT_IMAGE_SUPPORTED is false" ); } // Save the file based on format let fullPath: string; if (outputFormat === "svg") { if (!result.svg) { throw new Error("SVG content not available"); } fullPath = await saveMermaidSvgToFile( result.svg, args.name!, args.folder! ); } else { fullPath = await saveMermaidImageToFile( result.data, args.name!, args.folder! ); } return { content: [ { type: "text", text: `${outputFormat.toUpperCase()} saved to: ${fullPath}`, }, ], isError: false, }; } // If folder is provided and CONTENT_IMAGE_SUPPORTED is true, save the file to the folder // but also return the content in the response let savedMessage = ""; if (args.folder && args.name) { try { let fullPath: string; if (outputFormat === "svg") { if (!result.svg) { throw new Error("SVG content not available"); } fullPath = await saveMermaidSvgToFile( result.svg, args.name, args.folder ); } else { fullPath = await saveMermaidImageToFile( result.data, args.name, args.folder ); } savedMessage = `${outputFormat.toUpperCase()} also saved to: ${fullPath}`; log(LogLevel.INFO, savedMessage); } catch (saveError) { log( LogLevel.ERROR, `Failed to save ${outputFormat} to folder: ${(saveError as Error).message}` ); savedMessage = `Note: Failed to save ${outputFormat} to folder: ${ (saveError as Error).message }`; } } // Return the appropriate content based on format if (outputFormat === "svg") { if (!result.svg) { throw new Error("SVG content not available"); } return { content: [ { type: "text", text: savedMessage ? `Here is the generated SVG:\n\n${result.svg}\n\n${savedMessage}` : `Here is the generated SVG:\n\n${result.svg}`, }, ], isError: false, }; } else { // Return the PNG image in the response return { content: [ { type: "text", text: savedMessage ? `Here is the generated image. ${savedMessage}` : "Here is the generated image", }, { type: "image", data: result.data, mimeType: "image/png", }, ], isError: false, }; } } catch (error) { return handleMermaidError(error); } }
- index.ts:193-397 (helper)Core rendering function that uses Puppeteer and Mermaid.js to generate PNG base64 or SVG from Mermaid markdown code.async function renderMermaid( code: string, config: { theme?: "default" | "forest" | "dark" | "neutral"; backgroundColor?: string; outputFormat?: "png" | "svg"; } = {} ): Promise<{ data: string; svg?: string }> { log(LogLevel.INFO, "Launching Puppeteer"); log(LogLevel.DEBUG, `Rendering with config: ${JSON.stringify(config)}`); // Resolve the path to the local mermaid.js file const distPath = path.dirname( url.fileURLToPath(resolve("mermaid", import.meta.url)) ); const mermaidPath = path.resolve(distPath, "mermaid.min.js"); log(LogLevel.DEBUG, `Using Mermaid from: ${mermaidPath}`); const browser = await puppeteer.launch({ headless: true, // Use the bundled browser instead of looking for Chrome on the system executablePath: process.env.PUPPETEER_EXECUTABLE_PATH || undefined, args: ["--no-sandbox", "--disable-setuid-sandbox"], }); // Declare page outside try block so it's accessible in catch and finally let page: puppeteer.Page | null = null; // Store console messages for error reporting const consoleMessages: string[] = []; try { page = await browser.newPage(); log(LogLevel.DEBUG, "Browser page created"); // Capture browser console messages for better error reporting page.on("console", (msg) => { const text = msg.text(); consoleMessages.push(text); log(LogLevel.DEBUG, text); }); // Create a simple HTML template without the CDN reference const htmlContent = ` <!DOCTYPE html> <html> <head> <title>Mermaid Renderer</title> <style> body { background: ${config.backgroundColor || "white"}; margin: 0; padding: 0; } #container { padding: 0; margin: 0; } </style> </head> <body> <div id="container"></div> </body> </html> `; // Note: Must be set before page.goto() to ensure the page renders with the correct dimensions from the start await page.setViewport({ width: 1200, // Keep the same viewport size as before height: 800, deviceScaleFactor: 3, // 2~4 is fine, the larger the PNG, the clearer and larger it is }); // Write the HTML to a temporary file const tempHtmlPath = path.join(__dirname, "temp-mermaid.html"); fs.writeFileSync(tempHtmlPath, htmlContent); log(LogLevel.INFO, `Rendering mermaid code: ${code.substring(0, 50)}...`); log(LogLevel.DEBUG, `Full mermaid code: ${code}`); // Navigate to the HTML file await page.goto(`file://${tempHtmlPath}`); log(LogLevel.DEBUG, "Navigated to HTML template"); // Add the mermaid script to the page await page.addScriptTag({ path: mermaidPath }); log(LogLevel.DEBUG, "Added Mermaid script to page"); // Render the mermaid diagram using a more robust approach similar to the CLI log(LogLevel.DEBUG, "Starting Mermaid rendering in browser"); const screenshot = await page.$eval( "#container", async (container, mermaidCode, mermaidConfig) => { try { // @ts-ignore - mermaid is loaded by the script tag window.mermaid.initialize({ startOnLoad: false, theme: mermaidConfig.theme || "default", securityLevel: "loose", logLevel: 5, }); // This will throw an error if the mermaid syntax is invalid // @ts-ignore - mermaid is loaded by the script tag const { svg: svgText } = await window.mermaid.render( "mermaid-svg", mermaidCode, container ); container.innerHTML = svgText; const svg = container.querySelector("svg"); if (!svg) { throw new Error("SVG element not found after rendering"); } // Apply any necessary styling to the SVG svg.style.backgroundColor = mermaidConfig.backgroundColor || "white"; // Return the dimensions for screenshot const rect = svg.getBoundingClientRect(); return { width: Math.ceil(rect.width), height: Math.ceil(rect.height), success: true, }; } catch (error) { // Return the error to be handled outside return { success: false, error: error instanceof Error ? error.message : String(error), }; } }, code, { theme: config.theme, backgroundColor: config.backgroundColor } ); // Check if rendering was successful if (!screenshot.success) { log( LogLevel.ERROR, `Mermaid rendering failed in browser: ${screenshot.error}` ); throw new Error(`Mermaid rendering failed: ${screenshot.error}`); } log(LogLevel.DEBUG, "Mermaid rendered successfully in browser"); // Get the SVG content if needed let svgContent: string | undefined; if (config.outputFormat === "svg") { svgContent = await page.$eval("#container svg", (svg) => { return svg.outerHTML; }); log(LogLevel.DEBUG, "SVG content extracted"); } // Take a screenshot of the SVG for PNG output let base64Image = ""; if (config.outputFormat === "png" || config.outputFormat === undefined) { const svgElement = await page.$("#container svg"); if (!svgElement) { log(LogLevel.ERROR, "SVG element not found after successful rendering"); throw new Error("SVG element not found"); } log(LogLevel.DEBUG, "Taking screenshot of SVG"); // Take a screenshot with the correct dimensions base64Image = await svgElement.screenshot({ omitBackground: false, type: "png", encoding: "base64", }); } // Clean up the temporary file fs.unlinkSync(tempHtmlPath); log(LogLevel.DEBUG, "Temporary HTML file cleaned up"); log(LogLevel.INFO, "Mermaid rendered successfully"); return { data: base64Image, svg: svgContent }; } catch (error) { log( LogLevel.ERROR, `Error in renderMermaid: ${ error instanceof Error ? error.message : String(error) }` ); log( LogLevel.ERROR, `Error stack: ${error instanceof Error ? error.stack : "No stack trace"}` ); // Include console messages in the error for better debugging if (page && page.isClosed() === false) { log(LogLevel.ERROR, "Browser console messages:"); consoleMessages.forEach((msg) => log(LogLevel.ERROR, ` ${msg}`)); } throw error; } finally { await browser.close(); log(LogLevel.DEBUG, "Puppeteer browser closed"); } }
- index.ts:113-153 (schema)Tool definition including name, description, and detailed input schema with parameters for code, theme, colors, format, and optional file saving.const GENERATE_TOOL: Tool = { name: "generate", description: "Generate PNG image or SVG from mermaid markdown", inputSchema: { type: "object", properties: { code: { type: "string", description: "The mermaid markdown to generate an image from", }, theme: { type: "string", enum: ["default", "forest", "dark", "neutral"], description: "Theme for the diagram (optional)", }, backgroundColor: { type: "string", description: "Background color for the diagram, e.g. 'white', 'transparent', '#F0F0F0' (optional)", }, outputFormat: { type: "string", enum: ["png", "svg"], description: "Output format for the diagram (optional, defaults to 'png')", }, name: { type: "string", description: CONTENT_IMAGE_SUPPORTED ? "Name of the diagram (optional)" : "Name for the generated file (required)", }, folder: { type: "string", description: CONTENT_IMAGE_SUPPORTED ? "Absolute path to save the image to (optional)" : "Absolute path to save the image to (required)", }, }, required: CONTENT_IMAGE_SUPPORTED ? ["code"] : ["code", "name", "folder"], }, };
- index.ts:645-647 (registration)Registers the 'generate' tool in the MCP listTools handler by returning it in the tools array.server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [GENERATE_TOOL], }));
- index.ts:663-671 (handler)Dispatch logic in the MCP callTool handler that validates arguments and invokes processGenerateRequest for the 'generate' tool.if (name === "generate") { log(LogLevel.INFO, "Rendering Mermaid PNG"); if (!isGenerateArgs(args)) { throw new Error("Invalid arguments for generate"); } // Process the generate request return await processGenerateRequest(args); }