@volare-consulting/mermaid-mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +104 -0
- package/dist/index.js +76 -0
- package/dist/render.js +56 -0
- package/package.json +51 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Volare Consulting
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# mermaid-mcp
|
|
2
|
+
|
|
3
|
+
An [MCP](https://modelcontextprotocol.io) server that renders [Mermaid](https://mermaid.js.org)
|
|
4
|
+
diagram markup to **PNG** images, using [`@mermaid-js/mermaid-cli`](https://github.com/mermaid-js/mermaid-cli)
|
|
5
|
+
(Puppeteer/Chromium) under the hood.
|
|
6
|
+
|
|
7
|
+
Give it AI-generated Mermaid — flowcharts, sequence, class, ER, gantt, state diagrams — and get
|
|
8
|
+
back a rendered image, returned inline so the host can preview it and/or written to a file on disk.
|
|
9
|
+
|
|
10
|
+
## Tool
|
|
11
|
+
|
|
12
|
+
### `render_mermaid`
|
|
13
|
+
|
|
14
|
+
| Parameter | Type | Required | Description |
|
|
15
|
+
| ----------------- | -------- | -------- | ----------- |
|
|
16
|
+
| `diagram` | string | yes | Mermaid diagram source, e.g. `graph TD; A-->B;` |
|
|
17
|
+
| `outputPath` | string | no | Absolute path ending in `.png` to also save the image to. Omit to only return inline. |
|
|
18
|
+
| `theme` | enum | no | `default` \| `dark` \| `forest` \| `neutral` (default `default`) |
|
|
19
|
+
| `backgroundColor` | string | no | e.g. `white`, `transparent`, `#ffffff` (default `white`) |
|
|
20
|
+
| `width` | number | no | Output width in pixels |
|
|
21
|
+
| `height` | number | no | Output height in pixels |
|
|
22
|
+
| `scale` | number | no | Device scale factor; higher = sharper/larger PNG (default `1`) |
|
|
23
|
+
|
|
24
|
+
Returns a short text summary plus the PNG as inline MCP `image` content. When `outputPath` is
|
|
25
|
+
supplied, the file is written there and the path is included in the summary.
|
|
26
|
+
|
|
27
|
+
## Install & build
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install
|
|
31
|
+
npx puppeteer browsers install chrome # download the headless Chromium used for rendering
|
|
32
|
+
npm run build
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Rendering runs a headless Chromium via Puppeteer. If `npm install` doesn't fetch it automatically,
|
|
36
|
+
run `npx puppeteer browsers install chrome` (as above). It lands in Puppeteer's cache
|
|
37
|
+
(`~/.cache/puppeteer` / `%USERPROFILE%\.cache\puppeteer`). On Windows, if extraction stalls, delete
|
|
38
|
+
the partial `chrome/win64-*` folder and re-run the install.
|
|
39
|
+
|
|
40
|
+
## Test
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npm test
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Integration tests using Node's built-in test runner (`node:test`). They exercise the renderer
|
|
47
|
+
(input validation, inline render, render-to-file) and a full MCP stdio round-trip (spawn the server,
|
|
48
|
+
list tools, call `render_mermaid`). The render tests launch headless Chromium, so they need the
|
|
49
|
+
Chromium install above and take a few seconds each.
|
|
50
|
+
|
|
51
|
+
## Configure in an MCP client
|
|
52
|
+
|
|
53
|
+
After `npm run build`, point your MCP client at the built entry over stdio.
|
|
54
|
+
|
|
55
|
+
### Claude Code
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# From a local build:
|
|
59
|
+
claude mcp add mermaid -- node /absolute/path/to/mermaid-mcp/dist/index.js
|
|
60
|
+
|
|
61
|
+
# Or from the published package:
|
|
62
|
+
claude mcp add mermaid -- npx -y @volare-consulting/mermaid-mcp
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Claude Desktop / generic `mcpServers` config
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"mcpServers": {
|
|
70
|
+
"mermaid": {
|
|
71
|
+
"command": "node",
|
|
72
|
+
"args": ["/absolute/path/to/mermaid-mcp/dist/index.js"]
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Development
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
npm run dev # run the server from TypeScript source via tsx
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Releasing
|
|
85
|
+
|
|
86
|
+
Published to the public npm registry as
|
|
87
|
+
[`@volare-consulting/mermaid-mcp`](https://www.npmjs.com/package/@volare-consulting/mermaid-mcp)
|
|
88
|
+
via a tag-driven GitHub Actions release (`.github/workflows/publish.yml`), which calls the org's
|
|
89
|
+
shared `publish-npm-public` reusable workflow.
|
|
90
|
+
|
|
91
|
+
1. Bump `version` in `package.json` on a PR and merge to `main`.
|
|
92
|
+
2. Tag the merge commit and push the tag:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
git tag v0.1.0 && git push origin v0.1.0
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The tag **must** equal the `package.json` version or the job fails. Pushing a `v*` tag builds and
|
|
99
|
+
publishes the package (tests are skipped — they need headless Chromium the publish runner doesn't
|
|
100
|
+
provide). Authentication uses the org-level `NPM_TOKEN` secret.
|
|
101
|
+
|
|
102
|
+
## License
|
|
103
|
+
|
|
104
|
+
MIT
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { renderMermaidToPng } from "./render.js";
|
|
6
|
+
const server = new McpServer({
|
|
7
|
+
name: "mermaid-mcp",
|
|
8
|
+
version: "0.1.0",
|
|
9
|
+
});
|
|
10
|
+
server.registerTool("render_mermaid", {
|
|
11
|
+
title: "Render Mermaid diagram to PNG",
|
|
12
|
+
description: "Render Mermaid diagram markup to a PNG image. Returns the image inline so it " +
|
|
13
|
+
"can be previewed. If `outputPath` (an absolute .png path) is provided, the PNG " +
|
|
14
|
+
"is also saved there and the path is returned. Useful for turning AI-generated " +
|
|
15
|
+
"Mermaid (flowcharts, sequence, class, ER, gantt, state diagrams, etc.) into images.",
|
|
16
|
+
inputSchema: {
|
|
17
|
+
diagram: z
|
|
18
|
+
.string()
|
|
19
|
+
.min(1)
|
|
20
|
+
.describe("Mermaid diagram source, e.g. `graph TD; A-->B;`"),
|
|
21
|
+
outputPath: z
|
|
22
|
+
.string()
|
|
23
|
+
.optional()
|
|
24
|
+
.describe("Absolute path ending in .png to save the image to. Omit to only return the image inline."),
|
|
25
|
+
theme: z
|
|
26
|
+
.enum(["default", "dark", "forest", "neutral"])
|
|
27
|
+
.optional()
|
|
28
|
+
.describe("Mermaid theme. Defaults to \"default\"."),
|
|
29
|
+
backgroundColor: z
|
|
30
|
+
.string()
|
|
31
|
+
.optional()
|
|
32
|
+
.describe('Background color, e.g. "white", "transparent", "#ffffff". Defaults to "white".'),
|
|
33
|
+
width: z.number().int().positive().optional().describe("Output width in pixels."),
|
|
34
|
+
height: z.number().int().positive().optional().describe("Output height in pixels."),
|
|
35
|
+
scale: z
|
|
36
|
+
.number()
|
|
37
|
+
.positive()
|
|
38
|
+
.optional()
|
|
39
|
+
.describe("Device scale factor; higher = sharper/larger PNG. Defaults to 1."),
|
|
40
|
+
},
|
|
41
|
+
}, async (args) => {
|
|
42
|
+
try {
|
|
43
|
+
const result = await renderMermaidToPng(args);
|
|
44
|
+
const base64 = result.bytes.toString("base64");
|
|
45
|
+
const summary = result.isTemp
|
|
46
|
+
? `Rendered Mermaid diagram inline (PNG, ${result.bytes.length} bytes).`
|
|
47
|
+
: `Rendered Mermaid diagram to ${result.path} (PNG, ${result.bytes.length} bytes).`;
|
|
48
|
+
return {
|
|
49
|
+
content: [
|
|
50
|
+
{ type: "text", text: summary },
|
|
51
|
+
{ type: "image", data: base64, mimeType: "image/png" },
|
|
52
|
+
],
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
57
|
+
return {
|
|
58
|
+
isError: true,
|
|
59
|
+
content: [
|
|
60
|
+
{
|
|
61
|
+
type: "text",
|
|
62
|
+
text: `Failed to render Mermaid diagram: ${message}`,
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
async function main() {
|
|
69
|
+
const transport = new StdioServerTransport();
|
|
70
|
+
await server.connect(transport);
|
|
71
|
+
console.error("mermaid-mcp server running on stdio");
|
|
72
|
+
}
|
|
73
|
+
main().catch((error) => {
|
|
74
|
+
console.error("Fatal error in mermaid-mcp:", error);
|
|
75
|
+
process.exit(1);
|
|
76
|
+
});
|
package/dist/render.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { mkdtemp, writeFile, readFile, rm } from "node:fs/promises";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join, isAbsolute } from "node:path";
|
|
4
|
+
import { run as mmdcRun } from "@mermaid-js/mermaid-cli";
|
|
5
|
+
const PNG_MAGIC = Buffer.from([0x89, 0x50, 0x4e, 0x47]);
|
|
6
|
+
/**
|
|
7
|
+
* Render a Mermaid diagram to a PNG using @mermaid-js/mermaid-cli (Puppeteer/Chromium).
|
|
8
|
+
*
|
|
9
|
+
* The mermaid-cli programmatic API works off files, so the source is written to a
|
|
10
|
+
* temporary `.mmd` file and the PNG is produced either at `outputPath` or in a temp dir.
|
|
11
|
+
*/
|
|
12
|
+
export async function renderMermaidToPng(opts) {
|
|
13
|
+
const diagram = opts.diagram?.trim();
|
|
14
|
+
if (!diagram) {
|
|
15
|
+
throw new Error("`diagram` is empty — provide Mermaid markup to render.");
|
|
16
|
+
}
|
|
17
|
+
if (opts.outputPath && !isAbsolute(opts.outputPath)) {
|
|
18
|
+
throw new Error(`outputPath must be an absolute path, got: ${opts.outputPath}`);
|
|
19
|
+
}
|
|
20
|
+
if (opts.outputPath && !opts.outputPath.toLowerCase().endsWith(".png")) {
|
|
21
|
+
throw new Error(`outputPath must end in .png, got: ${opts.outputPath}`);
|
|
22
|
+
}
|
|
23
|
+
const workDir = await mkdtemp(join(tmpdir(), "mermaid-mcp-"));
|
|
24
|
+
const inputPath = join(workDir, "diagram.mmd");
|
|
25
|
+
const isTemp = !opts.outputPath;
|
|
26
|
+
const outputPath = opts.outputPath ?? join(workDir, "diagram.png");
|
|
27
|
+
await writeFile(inputPath, diagram, "utf8");
|
|
28
|
+
try {
|
|
29
|
+
await mmdcRun(inputPath, outputPath, {
|
|
30
|
+
quiet: true,
|
|
31
|
+
puppeteerConfig: { args: ["--no-sandbox", "--disable-setuid-sandbox"] },
|
|
32
|
+
parseMMDOptions: {
|
|
33
|
+
backgroundColor: opts.backgroundColor ?? "white",
|
|
34
|
+
mermaidConfig: { theme: opts.theme ?? "default" },
|
|
35
|
+
viewport: opts.width || opts.height || opts.scale
|
|
36
|
+
? {
|
|
37
|
+
width: opts.width ?? 800,
|
|
38
|
+
height: opts.height ?? 600,
|
|
39
|
+
deviceScaleFactor: opts.scale ?? 1,
|
|
40
|
+
}
|
|
41
|
+
: undefined,
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
const bytes = await readFile(outputPath);
|
|
45
|
+
if (!bytes.subarray(0, 4).equals(PNG_MAGIC)) {
|
|
46
|
+
throw new Error("Render produced a file that is not a valid PNG.");
|
|
47
|
+
}
|
|
48
|
+
return { path: outputPath, bytes, isTemp };
|
|
49
|
+
}
|
|
50
|
+
finally {
|
|
51
|
+
// Clean up the scratch dir (the temp .mmd input, and the temp PNG when one was
|
|
52
|
+
// used — its bytes are already in memory). A user-supplied outputPath lives
|
|
53
|
+
// outside workDir and is left untouched.
|
|
54
|
+
await rm(workDir, { recursive: true, force: true }).catch(() => { });
|
|
55
|
+
}
|
|
56
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@volare-consulting/mermaid-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server that renders Mermaid diagrams to PNG via @mermaid-js/mermaid-cli",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"bin": {
|
|
9
|
+
"mermaid-mcp": "dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
15
|
+
"publishConfig": {
|
|
16
|
+
"access": "public"
|
|
17
|
+
},
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "https://github.com/volare-consulting-software/mermaid-mcp.git"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"mermaid",
|
|
24
|
+
"diagram",
|
|
25
|
+
"png",
|
|
26
|
+
"mcp",
|
|
27
|
+
"model-context-protocol",
|
|
28
|
+
"claude",
|
|
29
|
+
"mermaid-cli"
|
|
30
|
+
],
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=18"
|
|
33
|
+
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"build": "tsc",
|
|
36
|
+
"prepublishOnly": "npm run build",
|
|
37
|
+
"start": "node dist/index.js",
|
|
38
|
+
"dev": "tsx src/index.ts",
|
|
39
|
+
"test": "node --import tsx --test test/render.test.ts test/mcp.test.ts"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@mermaid-js/mermaid-cli": "^11.4.2",
|
|
43
|
+
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
44
|
+
"zod": "^3.23.8"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"tsx": "^4.19.2",
|
|
48
|
+
"typescript": "^5.6.3",
|
|
49
|
+
"@types/node": "^22.9.0"
|
|
50
|
+
}
|
|
51
|
+
}
|