Skip to main content
Glama

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.

F
license - not found
-
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

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/oleksii-honchar/mermaid-mcp'

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