Repository: peng-shawn/mermaid-mcp-server
Branch: main
Commit: 81dc507991c6
Files: 10
Total size: 38.6 KB
Directory structure:
gitextract_pz7yyjpu/
├── .github/
│ └── workflows/
│ └── npm-publish.yml
├── .gitignore
├── Dockerfile
├── LICENSE
├── README.md
├── index.ts
├── package.json
├── scripts/
│ └── release.sh
├── smithery.yaml
└── tsconfig.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/npm-publish.yml
================================================
name: Publish to npm
on:
push:
tags:
- 'v*' # Run workflow on version tags, e.g. v1.0.0
jobs:
build-and-publish:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
registry-url: 'https://registry.npmjs.org'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Publish to npm
run: npm publish --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
================================================
FILE: .gitignore
================================================
node_modules
dist
.cursor/*
================================================
FILE: Dockerfile
================================================
# Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile
# Use node:lts-slim (Debian-based) instead of Alpine for better Chrome compatibility
FROM node:lts-slim
# Set working directory
WORKDIR /app
# Install Chromium with its dependencies
RUN apt-get update && apt-get install -y \
chromium \
--no-install-recommends \
&& rm -rf /var/lib/apt/lists/*
# Copy package files and install dependencies
# Use PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true to avoid downloading Chromium again
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
COPY package.json package-lock.json ./
RUN npm install
# Copy the rest of the application files
COPY . .
# Build the TypeScript code
RUN npm run build
# Command to run the MCP server
CMD [ "node", "dist/index.js" ]
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2025 Shawn Peng
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# Mermaid MCP Server
A Model Context Protocol (MCP) server that converts Mermaid diagrams to PNG images or SVG files. This server allows AI assistants and other applications to generate visual diagrams from textual descriptions using the Mermaid markdown syntax.
## Features
- Converts Mermaid diagram code to PNG images or SVG files
- Supports multiple diagram themes (default, forest, dark, neutral)
- Customizable background colors
- Uses Puppeteer for high-quality headless browser rendering
- Implements the MCP protocol for seamless integration with AI assistants
- Flexible output options: return images/SVG directly or save to disk
- Error handling with detailed error messages
## How It Works
The server uses Puppeteer to launch a headless browser, render the Mermaid diagram to SVG, and optionally capture a screenshot of the rendered diagram. The process involves:
1. Launching a headless browser instance
2. Creating an HTML template with the Mermaid code
3. Loading the Mermaid.js library
4. Rendering the diagram to SVG
5. Either saving the SVG directly or taking a screenshot as PNG
6. Either returning the image/SVG directly or saving it to disk
## Build
```bash
npx tsc
```
## Usage
### Use with Claude desktop
```json
{
"mcpServers": {
"mermaid": {
"command": "npx",
"args": ["-y", "@peng-shawn/mermaid-mcp-server"]
}
}
}
```
### Use with Cursor and Cline
```bash
env CONTENT_IMAGE_SUPPORTED=false npx -y @peng-shawn/mermaid-mcp-server
```
You can find a list of mermaid diagrams under `./diagrams`, they are created using Cursor agent with prompt: "generate mermaid diagrams and save them in a separate diagrams folder explaining how renderMermaidPng work"
### Run with inspector
Run the server with inspector for testing and debugging:
```bash
npx @modelcontextprotocol/inspector node dist/index.js
```
The server will start and listen on stdio for MCP protocol messages.
Learn more about inspector [here](https://modelcontextprotocol.io/docs/tools/inspector).
### Installing via Smithery
To install Mermaid Diagram Generator for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@peng-shawn/mermaid-mcp-server):
```bash
npx -y @smithery/cli install @peng-shawn/mermaid-mcp-server --client claude
```
### Docker and Smithery Environments
When running in Docker containers (including via Smithery), you may need to handle Chrome dependencies:
1. The server now attempts to use Puppeteer's bundled browser by default
2. If you encounter browser-related errors, you have two options:
**Option 1: During Docker image build:**
- Set `PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true` when installing Puppeteer
- Install Chrome/Chromium in your Docker container
- Set `PUPPETEER_EXECUTABLE_PATH` at runtime to point to the Chrome installation
**Option 2: Use Puppeteer's bundled Chrome:**
- Ensure your Docker container has the necessary dependencies for Chrome
- No need to set `PUPPETEER_SKIP_CHROMIUM_DOWNLOAD`
- The code will use the bundled browser automatically
For Smithery users, the latest version should work without additional configuration.
## API
The server exposes a single tool:
- `generate`: Converts Mermaid diagram code to a PNG image or SVG file
- Parameters:
- `code`: The Mermaid diagram code to render
- `theme`: (optional) Theme for the diagram. Options: "default", "forest", "dark", "neutral"
- `backgroundColor`: (optional) Background color for the diagram, e.g. 'white', 'transparent', '#F0F0F0'
- `outputFormat`: (optional) Output format for the diagram. Options: "png", "svg" (defaults to "png")
- `name`: Name for the generated file (required when CONTENT_IMAGE_SUPPORTED=false)
- `folder`: Absolute path to save the image/SVG to (required when CONTENT_IMAGE_SUPPORTED=false)
The behavior of the `generate` tool depends on the `CONTENT_IMAGE_SUPPORTED` environment variable:
- When `CONTENT_IMAGE_SUPPORTED=true` (default): The tool returns the image/SVG directly in the response
- When `CONTENT_IMAGE_SUPPORTED=false`: The tool saves the image/SVG to the specified folder and returns the file path
## Environment Variables
- `CONTENT_IMAGE_SUPPORTED`: Controls whether images are returned directly in the response or saved to disk
- `true` (default): Images are returned directly in the response
- `false`: Images are saved to disk, requiring `name` and `folder` parameters
## Examples
### Basic Usage
```javascript
// Generate a flowchart with default settings
{
"code": "flowchart TD\n A[Start] --> B{Is it?}\n B -->|Yes| C[OK]\n B -->|No| D[End]"
}
```
### With Theme and Background Color
```javascript
// Generate a sequence diagram with forest theme and light gray background
{
"code": "sequenceDiagram\n Alice->>John: Hello John, how are you?\n John-->>Alice: Great!",
"theme": "forest",
"backgroundColor": "#F0F0F0"
}
```
### Saving to Disk (when CONTENT_IMAGE_SUPPORTED=false)
```javascript
// Generate a class diagram and save it to disk as PNG
{
"code": "classDiagram\n Class01 <|-- AveryLongClass\n Class03 *-- Class04\n Class05 o-- Class06",
"theme": "dark",
"name": "class_diagram",
"folder": "/path/to/diagrams"
}
```
### Generating SVG Output
```javascript
// Generate a state diagram as SVG
{
"code": "stateDiagram-v2\n [*] --> Still\n Still --> [*]\n Still --> Moving\n Moving --> Still\n Moving --> Crash\n Crash --> [*]",
"outputFormat": "svg",
"name": "state_diagram",
"folder": "/path/to/diagrams"
}
```
## FAQ
### Doesn't Claude desktop already support mermaid via canvas?
Yes, but it doesn't support the `theme` and `backgroundColor` options. Plus, having a dedicated server makes it easier to create mermaid diagrams with different MCP clients.
### Why do I need to specify CONTENT_IMAGE_SUPPORTED=false when using with Cursor?
Cursor doesn't support inline images in responses yet.
## Publishing
This project uses GitHub Actions to automate the publishing process to npm.
### Method 1: Using the Release Script (Recommended)
1. Make sure all your changes are committed and pushed
2. Run the release script with either a specific version number or a semantic version increment:
```bash
# Using a specific version number
npm run release 0.1.4
# Using semantic version increments
npm run release patch # Increments the patch version (e.g., 0.1.3 → 0.1.4)
npm run release minor # Increments the minor version (e.g., 0.1.3 → 0.2.0)
npm run release major # Increments the major version (e.g., 0.1.3 → 1.0.0)
```
3. The script will:
- Validate the version format or semantic increment
- Check if you're on the main branch
- Detect and warn about version mismatches between files
- Update all version references consistently (package.json, package-lock.json, and index.ts)
- Create a single commit with all version changes
- Create and push a git tag
- The GitHub workflow will then automatically build and publish to npm
### Method 2: Manual Process
1. Update your code and commit the changes
2. Create and push a new tag with the version number:
```bash
git tag v0.1.4 # Use the appropriate version number
git push origin v0.1.4
```
3. The GitHub workflow will automatically:
- Build the project
- Publish to npm with the version from the tag
Note: You need to set up the `NPM_TOKEN` secret in your GitHub repository settings. To do this:
1. Generate an npm access token with publish permissions
2. Go to your GitHub repository → Settings → Secrets and variables → Actions
3. Create a new repository secret named `NPM_TOKEN` with your npm token as the value
## Badges
[](https://smithery.ai/server/@peng-shawn/mermaid-mcp-server)
## License
MIT
================================================
FILE: index.ts
================================================
#!/usr/bin/env node
import puppeteer from "puppeteer";
import path from "path";
import url from "url";
import fs from "fs";
import { resolve } from "import-meta-resolve";
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
Tool,
} from "@modelcontextprotocol/sdk/types.js";
/**
* Mermaid MCP Server
*
* This server provides a tool to render Mermaid diagrams as PNG images or SVG files.
*
* Environment Variables:
* - MERMAID_LOG_VERBOSITY: Controls the verbosity of logging (default: 2)
* 0 = EMERGENCY - Only the most critical errors
* 1 = CRITICAL - Critical errors that require immediate attention
* 2 = ERROR - Error conditions (default)
* 3 = WARNING - Warning conditions
* 4 = INFO - Informational messages
* 5 = DEBUG - Debug-level messages
* - CONTENT_IMAGE_SUPPORTED: Controls whether images can be returned directly in the response (default: true)
* When set to 'false', the 'name' and 'folder' parameters become mandatory, and all images must be saved to disk.
*
* Example:
* MERMAID_LOG_VERBOSITY=2 node index.js # Only show ERROR and more severe logs (default)
* MERMAID_LOG_VERBOSITY=4 node index.js # Show INFO and more severe logs
* MERMAID_LOG_VERBOSITY=5 node index.js # Show DEBUG and more severe logs
* CONTENT_IMAGE_SUPPORTED=false node index.js # Require all images to be saved to disk
*
* Tool Parameters:
* - code: The mermaid markdown to generate an image from (required)
* - theme: Theme for the diagram (optional, one of: "default", "forest", "dark", "neutral")
* - backgroundColor: Background color for the diagram (optional, e.g., "white", "transparent", "#F0F0F0")
* - outputFormat: Output format for the diagram (optional, "png" or "svg", defaults to "png")
* - name: Name for the generated file (required when saving to folder or when CONTENT_IMAGE_SUPPORTED=false)
* - folder: Folder path to save the image to (optional, but required when CONTENT_IMAGE_SUPPORTED=false)
*
* File Saving Behavior:
* - When 'folder' is specified, the image will be saved to disk instead of returned in the response
* - The 'name' parameter is required when 'folder' is specified
* - If a file with the same name already exists, a timestamp will be appended to the filename
* - When CONTENT_IMAGE_SUPPORTED=false, all images must be saved to disk, and 'name' and 'folder' are required
* - SVG files are saved as .svg text files, PNG files are saved as .png binary files
*/
// __dirname is not available in ESM modules by default
const __dirname = url.fileURLToPath(new URL(".", import.meta.url));
// Define log levels with numeric values for comparison
enum LogLevel {
EMERGENCY = 0,
CRITICAL = 1,
ERROR = 2,
WARNING = 3,
INFO = 4,
DEBUG = 5,
}
// Get verbosity level from environment variable, default to INFO (4)
const LOG_VERBOSITY = process.env.MERMAID_LOG_VERBOSITY
? parseInt(process.env.MERMAID_LOG_VERBOSITY, 10)
: LogLevel.ERROR;
// Check if content images are supported (default: true)
const CONTENT_IMAGE_SUPPORTED = process.env.CONTENT_IMAGE_SUPPORTED !== "false";
// Convert LogLevel to MCP log level string
function getMcpLogLevel(
level: LogLevel
): "error" | "info" | "debug" | "warning" | "critical" | "emergency" {
switch (level) {
case LogLevel.EMERGENCY:
return "emergency";
case LogLevel.CRITICAL:
return "critical";
case LogLevel.ERROR:
return "error";
case LogLevel.WARNING:
return "warning";
case LogLevel.DEBUG:
return "debug";
case LogLevel.INFO:
default:
return "info";
}
}
function log(level: LogLevel, message: string) {
// Only log if the current level is less than or equal to the verbosity setting
if (level <= LOG_VERBOSITY) {
// Get the appropriate MCP log level
const mcpLevel = getMcpLogLevel(level);
server.sendLoggingMessage({
level: mcpLevel,
data: message,
});
// Only console.error is consumed by MCP inspector
console.error(`${LogLevel[level]} - ${message}`);
}
}
// Define tools
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"],
},
};
// Server implementation
const server = new Server(
{
name: "mermaid-mcp-server",
version: "0.2.0",
},
{
capabilities: {
tools: {},
logging: {},
},
}
);
function isGenerateArgs(args: unknown): args is {
code: string;
theme?: "default" | "forest" | "dark" | "neutral";
backgroundColor?: string;
outputFormat?: "png" | "svg";
name?: string;
folder?: string;
} {
return (
typeof args === "object" &&
args !== null &&
"code" in args &&
typeof (args as any).code === "string" &&
(!(args as any).theme ||
["default", "forest", "dark", "neutral"].includes((args as any).theme)) &&
(!(args as any).backgroundColor ||
typeof (args as any).backgroundColor === "string") &&
(!(args as any).outputFormat ||
["png", "svg"].includes((args as any).outputFormat)) &&
(!(args as any).name || typeof (args as any).name === "string") &&
(!(args as any).folder || typeof (args as any).folder === "string")
);
}
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 = `