@volare-consulting/mermaid-mcp 0.1.0 → 0.2.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.
Files changed (3) hide show
  1. package/README.md +15 -5
  2. package/dist/render.js +64 -15
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -28,14 +28,24 @@ supplied, the file is written there and the path is included in the summary.
28
28
 
29
29
  ```bash
30
30
  npm install
31
- npx puppeteer browsers install chrome # download the headless Chromium used for rendering
32
31
  npm run build
33
32
  ```
34
33
 
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.
34
+ ## Browser requirement
35
+
36
+ Mermaid renders inside a real browser (it needs a DOM for layout), so **Google Chrome / Chromium is
37
+ required**. Puppeteer resolves it, in this order:
38
+
39
+ 1. **`PUPPETEER_EXECUTABLE_PATH`** — an explicit Chrome/Chromium binary you point it at.
40
+ 2. **Puppeteer's bundled Chromium** — downloaded by `@mermaid-js/mermaid-cli` during `npm install`.
41
+ 3. **A system-installed Google Chrome** — used as a fallback (`channel: "chrome"`) if the bundled
42
+ browser can't be launched.
43
+
44
+ If none can be launched, the tool returns an actionable error: install Chrome, run
45
+ `npx puppeteer browsers install chrome`, or set `PUPPETEER_EXECUTABLE_PATH`.
46
+
47
+ > If Puppeteer's automatic Chromium download is blocked (a locked-down network, or — on some Windows
48
+ > machines — a stalled extraction), just install Google Chrome and the renderer falls back to it.
39
49
 
40
50
  ## Test
41
51
 
package/dist/render.js CHANGED
@@ -3,11 +3,37 @@ import { tmpdir } from "node:os";
3
3
  import { join, isAbsolute } from "node:path";
4
4
  import { run as mmdcRun } from "@mermaid-js/mermaid-cli";
5
5
  const PNG_MAGIC = Buffer.from([0x89, 0x50, 0x4e, 0x47]);
6
+ // Substrings that mean "Puppeteer couldn't find or launch a browser" (as opposed
7
+ // to a genuine diagram/syntax error, which we must not silently retry).
8
+ const BROWSER_LAUNCH_HINTS = [
9
+ "could not find",
10
+ "failed to launch",
11
+ "browser was not found",
12
+ "executable doesn't exist",
13
+ "no usable sandbox",
14
+ "spawn",
15
+ "enoent",
16
+ ];
17
+ function isBrowserLaunchError(err) {
18
+ const message = (err instanceof Error ? err.message : String(err)).toLowerCase();
19
+ return BROWSER_LAUNCH_HINTS.some((hint) => message.includes(hint));
20
+ }
21
+ function browserSetupError(original) {
22
+ const detail = original instanceof Error ? original.message : String(original);
23
+ return new Error("Could not launch Chrome to render the diagram. Fix any one of:\n" +
24
+ " - Install Google Chrome, or run: npx puppeteer browsers install chrome\n" +
25
+ " - Or set PUPPETEER_EXECUTABLE_PATH to a Chrome/Chromium binary.\n" +
26
+ `Original error: ${detail}`);
27
+ }
6
28
  /**
7
29
  * Render a Mermaid diagram to a PNG using @mermaid-js/mermaid-cli (Puppeteer/Chromium).
8
30
  *
9
31
  * The mermaid-cli programmatic API works off files, so the source is written to a
10
32
  * temporary `.mmd` file and the PNG is produced either at `outputPath` or in a temp dir.
33
+ *
34
+ * Puppeteer resolves the browser: the first attempt uses `PUPPETEER_EXECUTABLE_PATH`
35
+ * (if set) or Puppeteer's bundled Chromium; if that can't launch, it falls back to a
36
+ * system-installed Google Chrome (`channel: "chrome"`). No hardcoded paths.
11
37
  */
12
38
  export async function renderMermaidToPng(opts) {
13
39
  const diagram = opts.diagram?.trim();
@@ -25,22 +51,45 @@ export async function renderMermaidToPng(opts) {
25
51
  const isTemp = !opts.outputPath;
26
52
  const outputPath = opts.outputPath ?? join(workDir, "diagram.png");
27
53
  await writeFile(inputPath, diagram, "utf8");
54
+ const parseMMDOptions = {
55
+ backgroundColor: opts.backgroundColor ?? "white",
56
+ mermaidConfig: { theme: opts.theme ?? "default" },
57
+ viewport: opts.width || opts.height || opts.scale
58
+ ? {
59
+ width: opts.width ?? 800,
60
+ height: opts.height ?? 600,
61
+ deviceScaleFactor: opts.scale ?? 1,
62
+ }
63
+ : undefined,
64
+ };
65
+ const baseArgs = ["--no-sandbox", "--disable-setuid-sandbox"];
66
+ // Browser sources tried in order, all resolved by Puppeteer itself.
67
+ const puppeteerConfigs = [
68
+ { args: baseArgs }, // PUPPETEER_EXECUTABLE_PATH or Puppeteer's bundled Chromium
69
+ { args: baseArgs, channel: "chrome" }, // system-installed Google Chrome
70
+ ];
28
71
  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
- });
72
+ let launchError;
73
+ let rendered = false;
74
+ for (const puppeteerConfig of puppeteerConfigs) {
75
+ try {
76
+ await mmdcRun(inputPath, outputPath, {
77
+ quiet: true,
78
+ puppeteerConfig,
79
+ parseMMDOptions,
80
+ });
81
+ rendered = true;
82
+ break;
83
+ }
84
+ catch (err) {
85
+ if (!isBrowserLaunchError(err))
86
+ throw err; // genuine render/syntax error
87
+ launchError = err; // browser source unavailable — try the next one
88
+ }
89
+ }
90
+ if (!rendered) {
91
+ throw browserSetupError(launchError);
92
+ }
44
93
  const bytes = await readFile(outputPath);
45
94
  if (!bytes.subarray(0, 4).equals(PNG_MAGIC)) {
46
95
  throw new Error("Render produced a file that is not a valid PNG.");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volare-consulting/mermaid-mcp",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "MCP server that renders Mermaid diagrams to PNG via @mermaid-js/mermaid-cli",
5
5
  "license": "MIT",
6
6
  "type": "module",