@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 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
+ }