mermaid-mcp

mermaid-mcp

Fast Mermaid diagram validation and rendering (PNG/SVG) using jsdom and sharp, no Chromium needed.

Category
Visit Server

README

mermaid-mcp

Custom Mermaid MCP server — fast validation + PNG/SVG rendering via jsdom + sharp, no Chromium needed.

Replaces the broken @rtuin/mcp-mermaid-validator that consistently timed out (MCP error -32001) for every invocation, even on trivially small diagrams. The root cause was shell + npx overhead spawning Chromium/Puppeteer. This server eliminates Chromium entirely — smaller image, faster renders, no browser crash risk.

Why It Exists

The original @rtuin/mcp-mermaid-validator@0.7.0 used @mermaid-js/mermaid-cli under the hood, which spawns a headless Chromium browser for every render. This caused:

  • Timeouts — Chromium startup takes 2-5 seconds, exceeding MCP request timeouts
  • Large image — ~1 GB Docker image (Chromium + Node.js)
  • Crash risk — Headless browser crashes on certain diagram types

This server replaces Chromium with jsdom (fake DOM) + sharp (SVG → PNG rasterization), resulting in:

  • Instant validation — ~50 ms (pure Node.js, mermaid.parse())
  • Fast rendering — ~30-100 ms (jsdom + sharp, no browser)
  • Tiny image — ~150-300 MB (no Chromium)
  • Reliable — No browser crash risk

Features

Feature Description
validate Parse-only validation — instant, no rendering, no DOM needed
render Full rendering to PNG (default) or SVG via jsdom + sharp, no browser
Stateless Per-request McpServer + transport — no cross-request state
HTTP-native Node.js built-in http module — no Express dependency
Docker-ready Multi-arch (amd64 + arm64), small image, puma-net deployment
Structured logging JSON logs to stderr, debug level via LOG_LEVEL env var

Architecture

graph TD
    subgraph "opencode"
        A["agent<br/>calls tool via HTTP"]
    end

    subgraph "puma-net"
        subgraph "mermaid-mcp container"
            B["http.createServer"]
            C["POST /mcp"]
            D["McpServer (per request)"]
            E["validate tool"]
            F["render tool"]
            G["mermaid.parse()"]
            H["mermaid.render() + jsdom"]
            I["sharp → PNG"]
        end
    end

    A -->|"HTTP POST /mcp"| B
    B --> C --> D
    D --> E --> G
    D --> F --> G
    F -->|"if valid"| H --> I
    I -->|"PNG buffer"| D

    style G fill:#e8f5e9
    style H fill:#e3f2fd
    style I fill:#fff4e1

Key design decisions:

Concern Decision Rationale
Transport HTTP (StreamableHTTPServerTransport, port 3000) Remote opencode access, follows hugging-kreuzberg pattern
HTTP server Node.js built-in http No Express dependency, per user request
Validation mermaid.parse() Pure Node.js, instant, no DOM
Rendering jsdom + sharp (no Chromium) Mermaid v11+ render() works with jsdom; sharp rasterizes SVG→PNG
Docker base node:26.3.0-slim Latest stable, no Chromium — tiny image, fast pulls
Output PNG default, SVG optional PNG for inline chat; SVG for editable diagrams

Quick Start

Prerequisites

  • Node.js 26+ (see .nvmrc)
  • Docker Desktop with Docker Compose
  • puma-net Docker network (created automatically by start.sh)

Local Development

# Clone and install
cd ~/www/misc/mermaid-mcp
npm install

# Run locally (port 3000)
npm start

# Run tests (60 pass, 1 skip, 0 fail)
npm test

Docker Compose

# Build and start
./start.sh

# Stop
./stop.sh

# Smoke test (7 HTTP tests via curl)
./test.sh

Build and Push to Docker Hub

# Build + push (latest)
./build-and-push.sh

# Tag with version
./build-and-push.sh --tag v1.0.0

# Build only (skip push)
./build-and-push.sh --build-only

# ARM64 only
./build-and-push.sh --platform linux/arm64

# Dry run (show commands, don't execute)
./build-and-push.sh --dry-run

Tool Reference

validate

Validate a Mermaid diagram definition without rendering. Returns the detected diagram type on success or a parse error with line number on failure. Pure Node.js — instant, no browser needed.

Input:

Parameter Type Required Description
diagram string Yes Mermaid diagram definition text

Output (valid):

{
  "content": [
    {
      "type": "text",
      "text": "Valid: flowchart"
    }
  ]
}

Output (invalid):

{
  "content": [
    {
      "type": "text",
      "text": "Invalid: Parse error on line 3: ..."
    }
  ],
  "isError": true
}

Example (MCP JSON-RPC):

curl -X POST http://localhost:3000/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
      "name": "validate",
      "arguments": {
        "diagram": "graph TD\n    A[Start] --> B[End]"
      }
    }
  }'

render

Render a Mermaid diagram to PNG or SVG. Validates first, then renders via jsdom (no browser needed). PNG output uses 2x DPI (density 144) for crisp images.

Input:

Parameter Type Required Default Description
diagram string Yes Mermaid diagram definition text
format enum No png Output format — png (raster) or svg (vector)
backgroundColor string No transparent Background color for PNG (CSS color value, e.g. "white" or "transparent")

Output (PNG):

{
  "content": [
    {
      "type": "text",
      "text": "Rendered as PNG (12345 bytes)"
    },
    {
      "type": "image",
      "data": "<base64-encoded PNG>",
      "mimeType": "image/png"
    }
  ]
}

Output (SVG):

{
  "content": [
    {
      "type": "text",
      "text": "Rendered as SVG (5678 bytes)"
    },
    {
      "type": "image",
      "data": "<base64-encoded SVG>",
      "mimeType": "image/svg+xml"
    }
  ]
}

Output (error):

{
  "content": [
    {
      "type": "text",
      "text": "Error: Parse error on line 3: ..."
    }
  ],
  "isError": true
}

Example (MCP JSON-RPC):

curl -X POST http://localhost:3000/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{
    "jsonrpc": "2.0",
    "id": 2,
    "method": "tools/call",
    "params": {
      "name": "render",
      "arguments": {
        "diagram": "graph TD\n    A[Start] --> B[End]",
        "format": "png",
        "backgroundColor": "white"
      }
    }
  }'

Development

Running Locally

# Start the server (port 3000)
npm start

# Start with debug logging
LOG_LEVEL=debug npm start

# Start on a different port
MCP_PORT=8080 npm start

Running Tests

# All tests (60 pass, 1 skip, 0 fail)
npm test

# Specific test file
node --test src/renderer.test.js
node --test src/tools.test.js
node --test src/mcp-server.test.js

Test Coverage

Test File Tests Coverage
src/renderer.test.js 36 Validate (6 diagram types), render SVG (6 types), render PNG (5 types + 1 skipped), error paths
src/tools.test.js 4 Tool schema registration, validate handler, render handler (PNG + SVG)
src/mcp-server.test.js 21 MCP protocol (initialize, tools/list, tools/call), HTTP error codes (405, 415, 413, 400)
Total 60 60 pass, 1 skip, 0 fail

Smoke Tests (Docker)

# Start compose, wait for server, run 7 HTTP tests
./start.sh && ./test.sh

The smoke test suite (test.sh) tests via curl against the running container:

  1. MCP initialize handshake
  2. tools/list returns 2 tools (validate, render)
  3. validate — valid flowchart
  4. validate — invalid diagram
  5. render — PNG format
  6. render — SVG format
  7. render — invalid diagram

Build and Push

# Build + push to tuiteraz/mermaid-mcp:latest (multi-arch: amd64 + arm64)
./build-and-push.sh

# Build only (local image)
./build-and-push.sh --build-only

# Push with version tag
./build-and-push.sh --tag v1.0.0

# ARM64 only
./build-and-push.sh --platform linux/arm64

# Dry run (preview commands)
./build-and-push.sh --dry-run

Deployment

Local Compose

# Start (creates puma-net if missing, builds image, runs container)
./start.sh

# Stop
./stop.sh

Puma-LAN (puma-net)

The deployment lives at ~/www/olho/puma-lan/lite-llm/mcp/mermaid/ and uses the Infisical pattern for secret management.

# Deploy to puma-net
cd ~/www/olho/puma-lan/lite-llm/mcp/mermaid
./start.sh

# Stop from puma-net
./stop.sh

# Pin image digest (for reproducible deployments)
./pin-image-digest.sh

# Build and push (copies source from misc dir, builds, pushes)
./build-and-push.sh

opencode Integration

The server is configured in ~/.config/opencode/opencode.jsonc:

"mermaid": {
  "type": "remote",
  "url": "https://lite-llm.lan/mcp/mermaid",
  "enabled": true,
  "category": "validation",
  "enabledTools": ["validate", "render"]
}

This replaces the old mermaid-validator entry that used @rtuin/mcp-mermaid-validator via stdio transport.

Project Structure

mermaid-mcp/
├── Dockerfile                     # node:26.3.0-slim + fonts-dejavu + fonts-liberation
├── docker-compose.yml             # Local compose (puma-net, port 3000)
├── package.json                   # Pinned deps, ESM, npm scripts
├── package-lock.json              # Lockfile
├── .nvmrc                         # Node.js 26
├── .gitignore
├── .husky/                        # Git hooks
├── start.sh                       # Start local compose (creates puma-net if needed)
├── stop.sh                        # Stop local compose
├── build-and-push.sh              # Build + push to Docker Hub (multi-arch)
├── test.sh                        # Smoke tests (7 HTTP tests via curl)
└── src/
    ├── mcp-server.mjs             # Entry: http.createServer + StreamableHTTPServerTransport
    ├── tools.js                   # validate + render tool definitions (Zod schemas)
    ├── renderer.js                # Render pipeline: mermaid.parse → mermaid.render → sharp
    ├── polyfills.js               # jsdom + browser polyfills (rAF, ResizeObserver, CSSStyleSheet, SVG)
    ├── config.js                  # Frozen config object from env vars
    ├── logger.js                  # Structured JSON logging to stderr
    ├── mcp-server.test.js         # Server tests (21: MCP protocol + HTTP error codes)
    ├── tools.test.js              # Tool tests (4: schema + handler)
    └── renderer.test.js           # Renderer tests (36: validate + render, 1 skip)

Key Files

File Purpose
src/mcp-server.mjs HTTP server entry point. Uses Node.js built-in http.createServer (no Express). Each request gets its own stateless McpServer + StreamableHTTPServerTransport pair. Handles POST /mcp, validates Content-Type, enforces body size limit (10 MB), graceful shutdown on SIGINT.
src/tools.js MCP tool definitions. validate — parse-only validation via mermaid.parse(). render — validate + render pipeline. Input schemas use Zod. Returns MCP-formatted responses with text + image content.
src/renderer.js Core rendering pipeline. validate(diagram) — uses mermaid.parse() with suppressErrors: false, returns diagram type or parse error. render(diagram, format, backgroundColor) — validates first, then mermaid.render() for SVG, then sharp for PNG conversion (density 144, compression 9).
src/polyfills.js Browser environment for Mermaid in Node.js. Creates a single JSDOM instance, attaches window/document/Element to global, polyfills requestAnimationFrame, ResizeObserver, CSSStyleSheet, SVG getBBox/getCTM. Lazy-loads DOMPurify + Mermaid via dynamic import() after globals are set.
src/config.js Configuration — single frozen object loaded from environment variables at startup. No process.env access outside this module.
src/logger.js Structured JSON logging to stderr (stdout reserved for HTTP in Docker). logInfo always emits; logDebug only when LOG_LEVEL=debug.

Configuration

All configuration is loaded from environment variables at startup via src/config.js. No process.env access outside this module.

Variable Default Description
MCP_PORT 3000 HTTP server port
MERMAID_DEFAULT_FORMAT png Default output format (png or svg)
MERMAID_BACKGROUND_COLOR transparent Default background color for PNG rendering (CSS color value)
MERMAID_SCALE 1 Render scale factor
LOG_LEVEL info Log level (info or debug)
MCP_MAX_BODY_SIZE 10485760 Maximum request body size in bytes (10 MB)

Example:

# Development with debug logging
LOG_LEVEL=debug MCP_PORT=8080 npm start

# Production with white background
MERMAID_BACKGROUND_COLOR=white MCP_PORT=3000 npm start

Docker

Image

FROM node:26.3.0-slim
├── fonts-dejavu          (~20 MB, proper Mermaid text rendering)
├── fonts-liberation      (~20 MB, Arial compatibility)
├── npm ci --omit=dev     (production deps only)
└── node src/mcp-server.mjs

Image size: ~150-300 MB (vs ~1 GB for mermaid-cli with Chromium)

Registry

Images are pushed to docker.io/tuiteraz/mermaid-mcp via build-and-push.sh. Supports multi-arch builds (amd64 + arm64) via docker buildx.

Known Issues

Gantt PNG Rendering Fails

Symptom: Rendering a Gantt diagram to PNG fails with an image conversion error.

Root cause: Mermaid produces viewBox="0 0 0 124" (zero width) for Gantt diagrams when rendered via jsdom. This is a mermaid rendering bug, not an issue with this server. The SVG rendering path works fine for Gantt diagrams.

Workaround: Use format: "svg" for Gantt diagrams.

Test: The test render PNG — gantt diagram is skipped (test.skip) with this note. All other diagram types render to PNG successfully.

Affected diagram type: gantt only. Flowchart, sequence, class, state, ER, and pie diagrams all render to PNG correctly.

License

This project is part of the internal tooling for the Puma LAN infrastructure.

Recommended Servers

playwright-mcp

playwright-mcp

A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.

Official
Featured
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.

Official
Featured
Local
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
graphlit-mcp-server

graphlit-mcp-server

The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.

Official
Featured
TypeScript
Kagi MCP Server

Kagi MCP Server

An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.

Official
Featured
Python
E2B

E2B

Using MCP to run code via e2b.

Official
Featured
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured
Exa Search

Exa Search

A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.

Official
Featured