Skip to main content
Glama
peng-shawn

mermaid-mcp-server

by peng-shawn

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
NameRequiredDescriptionDefault
backgroundColorNoBackground color for the diagram, e.g. 'white', 'transparent', '#F0F0F0' (optional)
codeYesThe mermaid markdown to generate an image from
folderNoAbsolute path to save the image to (optional)
nameNoName of the diagram (optional)
outputFormatNoOutput format for the diagram (optional, defaults to 'png')
themeNoTheme for the diagram (optional)

Implementation Reference

  • 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);
      }
    }
  • 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");
      }
    }
  • 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],
    }));
  • 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);
    }
Install Server

Other Tools

Related Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/peng-shawn/mermaid-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server