mermaid-mcp
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 |
| Parse-only validation — instant, no rendering, no DOM needed |
| Full rendering to PNG (default) or SVG via jsdom + sharp, no browser |
Stateless | Per-request |
HTTP-native | Node.js built-in |
Docker-ready | Multi-arch (amd64 + arm64), small image |
Structured logging | JSON logs to stderr, debug level via |
Architecture
graph TD
subgraph "opencode"
A["agent<br/>calls tool via HTTP"]
end
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
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:#fff4e1Key design decisions:
Concern | Decision | Rationale |
Transport | HTTP (StreamableHTTPServerTransport, port 3000) | Remote opencode access, follows hugging-kreuzberg pattern |
HTTP server | Node.js built-in | No Express dependency, per user request |
Validation |
| Pure Node.js, instant, no DOM |
Rendering | jsdom + sharp (no Chromium) | Mermaid v11+ |
Docker base |
| 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
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 testDocker Compose
# Build and start
./start.sh
# Stop
./stop.sh
# Smoke test (7 HTTP tests via curl)
./test.shBuild 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-runTool 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 |
|
| 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 |
|
| Yes | — | Mermaid diagram definition text |
|
| No |
| Output format — |
|
| No |
| Background color for PNG (CSS color value, e.g. |
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 startRunning 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.jsTest Coverage
Test File | Tests | Coverage |
| 36 | Validate (6 diagram types), render SVG (6 types), render PNG (5 types + 1 skipped), error paths |
| 4 | Tool schema registration, validate handler, render handler (PNG + SVG) |
| 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.shThe smoke test suite (test.sh) tests via curl against the running container:
MCP initialize handshake
tools/listreturns 2 tools (validate, render)validate— valid flowchartvalidate— invalid diagramrender— PNG formatrender— SVG formatrender— 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-runDeployment
Local Compose
# Start (builds image, runs container)
./start.sh
# Stop
./stop.shopencode Integration
To use this server with opencode, add the following to your MCP configuration:
"mermaid": {
"type": "remote",
"url": "http://localhost:3000/mcp",
"enabled": true,
"category": "validation",
"enabledTools": ["validate", "render"]
}For remote deployments, replace the URL with your server's address.
Project Structure
mermaid-mcp/
├── Dockerfile # node:26.3.0-slim + fonts-dejavu + fonts-liberation
├── docker-compose.yml # Local compose (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 Docker network 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 |
| HTTP server entry point. Uses Node.js built-in |
| MCP tool definitions. |
| Core rendering pipeline. |
| Browser environment for Mermaid in Node.js. Creates a single |
| Configuration — single frozen object loaded from environment variables at startup. No |
| Structured JSON logging to stderr (stdout reserved for HTTP in Docker). |
Configuration
All configuration is loaded from environment variables at startup via src/config.js. No process.env access outside this module.
Variable | Default | Description |
|
| HTTP server port |
|
| Default output format ( |
|
| Default background color for PNG rendering (CSS color value) |
|
| Render scale factor |
|
| Log level ( |
|
| 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 startDocker
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.mjsImage 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
MIT
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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