@diagrammo/dgmo-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/README.md ADDED
@@ -0,0 +1,95 @@
1
+ # @diagrammo/dgmo-mcp
2
+
3
+ MCP server for rendering DGMO diagrams. Works with Claude Desktop, Claude Code, and any MCP-compatible AI tool.
4
+
5
+ ## Tools
6
+
7
+ | Tool | Description |
8
+ |------|-------------|
9
+ | `render_diagram` | Render DGMO markup to SVG or PNG |
10
+ | `share_diagram` | Generate a shareable diagrammo.app URL |
11
+ | `open_in_app` | Open diagram in Diagrammo desktop app (falls back to browser if app not installed) |
12
+ | `list_chart_types` | List all 29 supported chart types |
13
+ | `get_language_reference` | Get DGMO syntax documentation |
14
+ | `preview_diagram` | Render one or more diagrams and open an HTML preview in the browser |
15
+ | `generate_report` | Generate a polished HTML report with multiple diagrams, ToC, and optional source |
16
+
17
+ ### preview_diagram
18
+
19
+ Renders one or more DGMO diagrams to SVG and opens a self-contained HTML page in the default browser. The page includes a light/dark theme toggle and responsive SVG layout.
20
+
21
+ | Parameter | Type | Default | Description |
22
+ |-----------|------|---------|-------------|
23
+ | `diagrams` | `[{ title?, dgmo }]` | *(required)* | One or more diagrams to preview |
24
+ | `theme` | `'light' \| 'dark'` | `'light'` | Color theme for rendered SVGs |
25
+ | `palette` | `string` | `'nord'` | Color palette |
26
+ | `include_source` | `boolean` | `false` | Show DGMO source in collapsible blocks |
27
+
28
+ A single diagram renders as a simple preview page. Multiple diagrams produce a report-style layout with a table of contents (when >3 sections). If some diagrams fail to render, successful ones are shown with error placeholders for the failures.
29
+
30
+ ### generate_report
31
+
32
+ Generates a polished multi-section HTML report and optionally opens it in the browser. Includes a title, optional subtitle, auto-generated table of contents, per-section descriptions, and a timestamp footer. Suitable for bundling project analysis into a shareable document.
33
+
34
+ | Parameter | Type | Default | Description |
35
+ |-----------|------|---------|-------------|
36
+ | `title` | `string` | *(required)* | Report title |
37
+ | `subtitle` | `string` | — | Optional subtitle |
38
+ | `sections` | `[{ title, description?, dgmo }]` | *(required)* | Report sections, each with a diagram |
39
+ | `theme` | `'light' \| 'dark'` | `'light'` | Color theme for rendered SVGs |
40
+ | `palette` | `string` | `'nord'` | Color palette |
41
+ | `include_source` | `boolean` | `false` | Show DGMO source in collapsible blocks |
42
+ | `open` | `boolean` | `true` | Open the report in the browser |
43
+
44
+ ## Setup
45
+
46
+ ### Claude Code
47
+
48
+ Add to your project's `.claude/settings.local.json`:
49
+
50
+ ```json
51
+ {
52
+ "mcpServers": {
53
+ "dgmo": {
54
+ "command": "node",
55
+ "args": ["/path/to/dgmo-mcp/dist/index.js"]
56
+ }
57
+ }
58
+ }
59
+ ```
60
+
61
+ Or with npx (after publishing):
62
+
63
+ ```json
64
+ {
65
+ "mcpServers": {
66
+ "dgmo": {
67
+ "command": "npx",
68
+ "args": ["-y", "@diagrammo/dgmo-mcp"]
69
+ }
70
+ }
71
+ }
72
+ ```
73
+
74
+ ### Claude Desktop
75
+
76
+ Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
77
+
78
+ ```json
79
+ {
80
+ "mcpServers": {
81
+ "dgmo": {
82
+ "command": "node",
83
+ "args": ["/path/to/dgmo-mcp/dist/index.js"]
84
+ }
85
+ }
86
+ }
87
+ ```
88
+
89
+ ## Development
90
+
91
+ ```bash
92
+ pnpm install
93
+ pnpm build
94
+ pnpm typecheck
95
+ ```
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/index.js ADDED
@@ -0,0 +1,665 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
18
+ // If the importer is in node compatibility mode or this is not an ESM
19
+ // file that has been converted to a CommonJS file using a Babel-
20
+ // compatible transform (i.e. "__esModule" has not been set), then set
21
+ // "default" to the CommonJS "module.exports" for node compatibility.
22
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
+ mod
24
+ ));
25
+
26
+ // src/index.ts
27
+ var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
28
+ var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
29
+ var import_zod = require("zod");
30
+ var import_node_child_process2 = require("child_process");
31
+ var import_node_fs = require("fs");
32
+ var import_node_path = require("path");
33
+ var import_node_os2 = require("os");
34
+ var import_node_crypto = require("crypto");
35
+ var import_dgmo = require("@diagrammo/dgmo");
36
+ var import_resvg_js = require("@resvg/resvg-js");
37
+
38
+ // src/html-report.ts
39
+ function buildCss(palette) {
40
+ const { light, dark } = palette;
41
+ return `
42
+ :root, [data-theme="light"] {
43
+ --bg: ${light.bg};
44
+ --surface: ${light.surface};
45
+ --border: ${light.border};
46
+ --text: ${light.text};
47
+ --text-muted: ${light.textMuted};
48
+ --primary: ${light.primary};
49
+ --destructive: ${light.destructive};
50
+ }
51
+ [data-theme="dark"] {
52
+ --bg: ${dark.bg};
53
+ --surface: ${dark.surface};
54
+ --border: ${dark.border};
55
+ --text: ${dark.text};
56
+ --text-muted: ${dark.textMuted};
57
+ --primary: ${dark.primary};
58
+ --destructive: ${dark.destructive};
59
+ }
60
+ * { box-sizing: border-box; margin: 0; padding: 0; }
61
+ body {
62
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
63
+ background: var(--bg);
64
+ color: var(--text);
65
+ line-height: 1.6;
66
+ }
67
+ .container { max-width: 960px; margin: 0 auto; padding: 2rem 1.5rem; }
68
+ h1 { font-size: 1.75rem; margin-bottom: 0.25rem; }
69
+ h2 { font-size: 1.35rem; margin-top: 2.5rem; margin-bottom: 0.75rem; border-bottom: 1px solid var(--border); padding-bottom: 0.35rem; }
70
+ .subtitle { color: var(--text-muted); margin-bottom: 1.5rem; }
71
+ .diagram-wrapper { margin: 1rem 0; }
72
+ .diagram-wrapper svg { max-width: 100%; height: auto; display: block; }
73
+ .description { color: var(--text-muted); margin-bottom: 0.75rem; }
74
+ details { margin: 0.75rem 0; }
75
+ summary { cursor: pointer; color: var(--text-muted); font-size: 0.85rem; user-select: none; }
76
+ summary:hover { color: var(--primary); }
77
+ pre {
78
+ background: var(--surface);
79
+ border: 1px solid var(--border);
80
+ border-radius: 6px;
81
+ padding: 1rem;
82
+ overflow-x: auto;
83
+ font-size: 0.8rem;
84
+ line-height: 1.5;
85
+ margin-top: 0.5rem;
86
+ }
87
+ code { font-family: 'SF Mono', SFMono-Regular, Menlo, Monaco, Consolas, monospace; }
88
+ .error-placeholder {
89
+ border: 2px solid var(--destructive);
90
+ border-radius: 6px;
91
+ padding: 1rem 1.25rem;
92
+ color: var(--destructive);
93
+ margin: 1rem 0;
94
+ }
95
+ .toc { margin: 1.5rem 0; padding: 1rem 1.25rem; background: var(--surface); border-radius: 6px; }
96
+ .toc h3 { font-size: 0.9rem; margin-bottom: 0.5rem; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.05em; }
97
+ .toc ol { padding-left: 1.25rem; }
98
+ .toc li { margin: 0.25rem 0; }
99
+ .toc a { color: var(--primary); text-decoration: none; }
100
+ .toc a:hover { text-decoration: underline; }
101
+ .theme-toggle {
102
+ position: fixed; top: 1rem; right: 1rem;
103
+ background: var(--surface); border: 1px solid var(--border);
104
+ color: var(--text); border-radius: 6px;
105
+ padding: 0.35rem 0.65rem; cursor: pointer; font-size: 0.8rem;
106
+ z-index: 100;
107
+ }
108
+ .theme-toggle:hover { border-color: var(--primary); }
109
+ footer { margin-top: 3rem; padding-top: 1rem; border-top: 1px solid var(--border); color: var(--text-muted); font-size: 0.8rem; text-align: center; }
110
+ @media print {
111
+ .theme-toggle { display: none; }
112
+ body { background: white; color: black; }
113
+ .toc { break-after: page; }
114
+ h2 { break-after: avoid; }
115
+ .diagram-wrapper { break-inside: avoid; }
116
+ }
117
+ `;
118
+ }
119
+ var THEME_TOGGLE_SCRIPT = `
120
+ <script>
121
+ (function() {
122
+ var btn = document.querySelector('.theme-toggle');
123
+ if (!btn) return;
124
+ btn.addEventListener('click', function() {
125
+ var html = document.documentElement;
126
+ var current = html.getAttribute('data-theme') || 'light';
127
+ var next = current === 'light' ? 'dark' : 'light';
128
+ html.setAttribute('data-theme', next);
129
+ btn.textContent = next === 'light' ? '\u263E Dark' : '\u2600 Light';
130
+ });
131
+ })();
132
+ </script>
133
+ `;
134
+ function slugify(text) {
135
+ return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
136
+ }
137
+ function escapeHtml(text) {
138
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
139
+ }
140
+ function sourceBlock(dgmoSource) {
141
+ return `<details><summary>DGMO source</summary><pre><code>${escapeHtml(dgmoSource)}</code></pre></details>`;
142
+ }
143
+ function errorPlaceholder(error) {
144
+ return `<div class="error-placeholder">\u26A0 Render error: ${escapeHtml(error)}</div>`;
145
+ }
146
+ function buildPreviewHtml(options) {
147
+ const { svg, title, dgmoSource, palette } = options;
148
+ const pageTitle = title || "Diagram Preview";
149
+ return `<!DOCTYPE html>
150
+ <html lang="en" data-theme="light">
151
+ <head>
152
+ <meta charset="utf-8">
153
+ <meta name="viewport" content="width=device-width, initial-scale=1">
154
+ <title>${escapeHtml(pageTitle)}</title>
155
+ <style>${buildCss(palette)}</style>
156
+ </head>
157
+ <body>
158
+ <button class="theme-toggle">\u263E Dark</button>
159
+ <div class="container">
160
+ ${title ? `<h1>${escapeHtml(title)}</h1>` : ""}
161
+ <div class="diagram-wrapper">${svg}</div>
162
+ ${dgmoSource ? sourceBlock(dgmoSource) : ""}
163
+ </div>
164
+ ${THEME_TOGGLE_SCRIPT}
165
+ </body>
166
+ </html>`;
167
+ }
168
+ function buildReportHtml(options) {
169
+ const { title, subtitle, sections, palette, includeSource } = options;
170
+ const showToc = sections.length > 3;
171
+ let toc = "";
172
+ if (showToc) {
173
+ const items = sections.map((s, i) => `<li><a href="#section-${i}">${escapeHtml(s.title)}</a></li>`).join("\n");
174
+ toc = `<nav class="toc"><h3>Contents</h3><ol>${items}</ol></nav>`;
175
+ }
176
+ const sectionHtml = sections.map((s, i) => {
177
+ const anchor = `section-${i}`;
178
+ const slug = slugify(s.title);
179
+ const id = showToc ? anchor : slug;
180
+ const desc = s.description ? `<p class="description">${escapeHtml(s.description)}</p>` : "";
181
+ const diagram = s.svg ? `<div class="diagram-wrapper">${s.svg}</div>` : errorPlaceholder(s.error || "Unknown render error");
182
+ const source = includeSource && s.dgmoSource ? sourceBlock(s.dgmoSource) : "";
183
+ return `<section id="${id}"><h2>${escapeHtml(s.title)}</h2>${desc}${diagram}${source}</section>`;
184
+ }).join("\n");
185
+ const now = (/* @__PURE__ */ new Date()).toLocaleString();
186
+ return `<!DOCTYPE html>
187
+ <html lang="en" data-theme="light">
188
+ <head>
189
+ <meta charset="utf-8">
190
+ <meta name="viewport" content="width=device-width, initial-scale=1">
191
+ <title>${escapeHtml(title)}</title>
192
+ <style>${buildCss(palette)}</style>
193
+ </head>
194
+ <body>
195
+ <button class="theme-toggle">\u263E Dark</button>
196
+ <div class="container">
197
+ <h1>${escapeHtml(title)}</h1>
198
+ ${subtitle ? `<p class="subtitle">${escapeHtml(subtitle)}</p>` : ""}
199
+ ${toc}
200
+ ${sectionHtml}
201
+ <footer>Generated by Diagrammo &middot; ${escapeHtml(now)}</footer>
202
+ </div>
203
+ ${THEME_TOGGLE_SCRIPT}
204
+ </body>
205
+ </html>`;
206
+ }
207
+
208
+ // src/open-browser.ts
209
+ var import_node_child_process = require("child_process");
210
+ var import_node_os = require("os");
211
+ function openInBrowser(filePath) {
212
+ const os = (0, import_node_os.platform)();
213
+ const cmd = os === "darwin" ? "open" : os === "win32" ? 'start ""' : "xdg-open";
214
+ return new Promise((resolve, reject) => {
215
+ (0, import_node_child_process.exec)(`${cmd} ${JSON.stringify(filePath)}`, (error) => {
216
+ if (error) reject(error);
217
+ else resolve();
218
+ });
219
+ });
220
+ }
221
+
222
+ // src/index.ts
223
+ var CHART_TYPE_DESCRIPTIONS = {
224
+ bar: "Bar chart \u2014 categorical comparisons",
225
+ line: "Line chart \u2014 trends over time",
226
+ "multi-line": "Multi-line chart \u2014 multiple series trends",
227
+ area: "Area chart \u2014 filled line chart",
228
+ pie: "Pie chart \u2014 part-to-whole proportions",
229
+ doughnut: "Doughnut chart \u2014 ring-style pie chart",
230
+ radar: "Radar chart \u2014 multi-dimensional metrics",
231
+ "polar-area": "Polar area chart \u2014 radial bar chart",
232
+ "bar-stacked": "Stacked bar chart \u2014 multi-series categorical",
233
+ scatter: "Scatter plot \u2014 2D data points or bubble chart",
234
+ sankey: "Sankey diagram \u2014 flow/allocation visualization",
235
+ chord: "Chord diagram \u2014 circular flow relationships",
236
+ function: "Function plot \u2014 mathematical expressions",
237
+ heatmap: "Heatmap \u2014 matrix intensity visualization",
238
+ funnel: "Funnel chart \u2014 conversion pipeline",
239
+ slope: "Slope chart \u2014 change between two periods",
240
+ wordcloud: "Word cloud \u2014 term frequency visualization",
241
+ arc: "Arc diagram \u2014 network relationships",
242
+ timeline: "Timeline \u2014 events, eras, and date ranges",
243
+ venn: "Venn diagram \u2014 set overlaps",
244
+ quadrant: "Quadrant chart \u2014 2x2 positioning matrix",
245
+ sequence: "Sequence diagram \u2014 message/interaction flows",
246
+ flowchart: "Flowchart \u2014 decision trees and process flows",
247
+ class: "Class diagram \u2014 UML class hierarchies",
248
+ er: "ER diagram \u2014 database schemas and relationships",
249
+ org: "Org chart \u2014 hierarchical tree structures",
250
+ kanban: "Kanban board \u2014 task/workflow columns",
251
+ c4: "C4 diagram \u2014 system architecture (context, container, component, deployment)",
252
+ "initiative-status": "Initiative status \u2014 project roadmap with dependency tracking"
253
+ };
254
+ var DEFAULT_FONT_NAME = "Helvetica";
255
+ function svgToPngBase64(svg, background) {
256
+ const resvg = new import_resvg_js.Resvg(svg, {
257
+ fitTo: { mode: "zoom", value: 2 },
258
+ ...background ? { background } : {},
259
+ font: {
260
+ loadSystemFonts: true,
261
+ defaultFontFamily: DEFAULT_FONT_NAME,
262
+ sansSerifFamily: DEFAULT_FONT_NAME
263
+ }
264
+ });
265
+ const rendered = resvg.render();
266
+ return Buffer.from(rendered.asPng()).toString("base64");
267
+ }
268
+ function resolveLanguageReference() {
269
+ try {
270
+ const dgmoMain = require.resolve("@diagrammo/dgmo");
271
+ const pkgRoot = (0, import_node_path.dirname)((0, import_node_path.dirname)(dgmoMain));
272
+ const docsPath = (0, import_node_path.join)(pkgRoot, "docs", "language-reference.md");
273
+ return (0, import_node_fs.readFileSync)(docsPath, "utf-8");
274
+ } catch {
275
+ const workspacePath = (0, import_node_path.join)(__dirname, "..", "..", "dgmo", "docs", "language-reference.md");
276
+ return (0, import_node_fs.readFileSync)(workspacePath, "utf-8");
277
+ }
278
+ }
279
+ function extractSection(markdown, chartType) {
280
+ const escaped = chartType.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
281
+ const pattern = new RegExp(`^###? ${escaped}\\b.*$`, "im");
282
+ const match = pattern.exec(markdown);
283
+ if (!match) return null;
284
+ const level = match[0].startsWith("### ") ? "### " : "## ";
285
+ const start = match.index;
286
+ const rest = markdown.slice(start + match[0].length);
287
+ const nextHeading = rest.search(new RegExp(`^${level.replace(/ $/, "")}[# ]`, "m"));
288
+ const end = nextHeading === -1 ? markdown.length : start + match[0].length + nextHeading;
289
+ return markdown.slice(start, end).trim();
290
+ }
291
+ function writeTempHtml(html, prefix) {
292
+ const filePath = (0, import_node_path.join)((0, import_node_os2.tmpdir)(), `${prefix}-${(0, import_node_crypto.randomUUID)()}.html`);
293
+ (0, import_node_fs.writeFileSync)(filePath, html, "utf-8");
294
+ return filePath;
295
+ }
296
+ async function tryRender(dgmo, theme, palette) {
297
+ const { diagnostics } = (0, import_dgmo.parseDgmo)(dgmo);
298
+ const errors = diagnostics.filter((d) => d.severity === "error");
299
+ if (errors.length > 0) {
300
+ return { svg: null, error: errors.map(import_dgmo.formatDgmoError).join("\n") };
301
+ }
302
+ try {
303
+ const svg = await (0, import_dgmo.render)(dgmo, { theme, palette, branding: false });
304
+ if (!svg) return { svg: null, error: "Render returned empty SVG." };
305
+ return { svg, error: null };
306
+ } catch (err) {
307
+ return { svg: null, error: err instanceof Error ? err.message : String(err) };
308
+ }
309
+ }
310
+ var server = new import_mcp.McpServer({
311
+ name: "dgmo",
312
+ version: "0.1.0"
313
+ });
314
+ server.tool(
315
+ "render_diagram",
316
+ 'Render DGMO markup to SVG or PNG. Returns SVG text or base64 PNG image. IMPORTANT DGMO syntax rules: (1) Parentheses after a label specify color \u2014 "Sales (red)" colors it red, the text becomes just "Sales". Never use parentheses for annotation. Use dashes or separate words instead, e.g. "Diagrammo App - TS" not "Diagrammo App (TS)". (2) All element/label names must be unique \u2014 if parentheses are stripped as color, two labels like "App (TS)" and "App (Rust)" both become "App" causing a duplicate name error.',
317
+ {
318
+ dgmo: import_zod.z.string().describe("DGMO diagram markup. Parentheses in labels = color notation (stripped from display name). All labels must be unique after color stripping."),
319
+ format: import_zod.z.enum(["svg", "png"]).default("svg").describe("Output format"),
320
+ theme: import_zod.z.enum(["light", "dark", "transparent"]).default("light").describe("Color theme"),
321
+ palette: import_zod.z.string().default("nord").describe("Color palette (nord, solarized, catppuccin, rose-pine, gruvbox, tokyo-night, one-dark, bold)")
322
+ },
323
+ async ({ dgmo, format, theme, palette }) => {
324
+ const { diagnostics } = (0, import_dgmo.parseDgmo)(dgmo);
325
+ const errors = diagnostics.filter((d) => d.severity === "error");
326
+ if (errors.length > 0) {
327
+ return {
328
+ content: [
329
+ {
330
+ type: "text",
331
+ text: "Diagram has errors:\n" + errors.map(import_dgmo.formatDgmoError).join("\n")
332
+ }
333
+ ],
334
+ isError: true
335
+ };
336
+ }
337
+ const svg = await (0, import_dgmo.render)(dgmo, { theme, palette, branding: false });
338
+ if (!svg) {
339
+ return {
340
+ content: [{ type: "text", text: "Render returned empty SVG." }],
341
+ isError: true
342
+ };
343
+ }
344
+ if (format === "png") {
345
+ const paletteColors = (0, import_dgmo.getPalette)(palette);
346
+ const bg = theme === "transparent" ? void 0 : paletteColors[theme === "dark" ? "dark" : "light"].bg;
347
+ const base64 = svgToPngBase64(svg, bg);
348
+ return {
349
+ content: [{ type: "image", data: base64, mimeType: "image/png" }]
350
+ };
351
+ }
352
+ return {
353
+ content: [{ type: "text", text: svg }]
354
+ };
355
+ }
356
+ );
357
+ server.tool(
358
+ "share_diagram",
359
+ "Generate a shareable diagrammo.app URL for a DGMO diagram.",
360
+ {
361
+ dgmo: import_zod.z.string().describe("DGMO diagram markup")
362
+ },
363
+ async ({ dgmo }) => {
364
+ const result = (0, import_dgmo.encodeDiagramUrl)(dgmo);
365
+ if (result.error === "too-large") {
366
+ return {
367
+ content: [
368
+ {
369
+ type: "text",
370
+ text: `Diagram is too large to share via URL. Compressed size: ${result.compressedSize} bytes (limit: ${result.limit} bytes). Try simplifying the diagram.`
371
+ }
372
+ ],
373
+ isError: true
374
+ };
375
+ }
376
+ return {
377
+ content: [{ type: "text", text: result.url }]
378
+ };
379
+ }
380
+ );
381
+ server.tool(
382
+ "open_in_app",
383
+ "Open a DGMO diagram in the Diagrammo desktop app (macOS only). Falls back to browser preview if the app is not installed.",
384
+ {
385
+ dgmo: import_zod.z.string().describe("DGMO diagram markup")
386
+ },
387
+ async ({ dgmo }) => {
388
+ const result = (0, import_dgmo.encodeDiagramUrl)(dgmo);
389
+ if (result.error === "too-large") {
390
+ return {
391
+ content: [
392
+ {
393
+ type: "text",
394
+ text: `Diagram is too large for URL encoding. Compressed size: ${result.compressedSize} bytes (limit: ${result.limit} bytes).`
395
+ }
396
+ ],
397
+ isError: true
398
+ };
399
+ }
400
+ const url = result.url;
401
+ const hash = url.split("#")[1] ?? "";
402
+ const deepLink = `diagrammo://open?dgmo=${hash}`;
403
+ return new Promise((resolve) => {
404
+ (0, import_node_child_process2.exec)(`open ${JSON.stringify(deepLink)}`, async (error) => {
405
+ if (error) {
406
+ try {
407
+ const rendered = await tryRender(dgmo, "light", "nord");
408
+ if (!rendered.svg) {
409
+ resolve({
410
+ content: [
411
+ {
412
+ type: "text",
413
+ text: `App not installed and render failed: ${rendered.error}`
414
+ }
415
+ ],
416
+ isError: true
417
+ });
418
+ return;
419
+ }
420
+ const paletteConfig = (0, import_dgmo.getPalette)("nord");
421
+ const html = buildPreviewHtml({
422
+ svg: rendered.svg,
423
+ title: "Diagram Preview",
424
+ dgmoSource: dgmo,
425
+ palette: paletteConfig
426
+ });
427
+ const filePath = writeTempHtml(html, "dgmo-preview");
428
+ await openInBrowser(filePath);
429
+ resolve({
430
+ content: [
431
+ {
432
+ type: "text",
433
+ text: `Diagrammo app not found \u2014 opened preview in browser: ${filePath}`
434
+ }
435
+ ]
436
+ });
437
+ } catch (fallbackErr) {
438
+ resolve({
439
+ content: [
440
+ {
441
+ type: "text",
442
+ text: `Failed to open Diagrammo app and browser fallback failed: ${fallbackErr instanceof Error ? fallbackErr.message : String(fallbackErr)}`
443
+ }
444
+ ],
445
+ isError: true
446
+ });
447
+ }
448
+ } else {
449
+ resolve({
450
+ content: [
451
+ {
452
+ type: "text",
453
+ text: "Opened diagram in Diagrammo app."
454
+ }
455
+ ]
456
+ });
457
+ }
458
+ });
459
+ });
460
+ }
461
+ );
462
+ server.tool(
463
+ "list_chart_types",
464
+ "List all supported DGMO chart types with descriptions.",
465
+ {},
466
+ async () => {
467
+ const types = Object.keys(import_dgmo.DGMO_CHART_TYPE_MAP);
468
+ const lines = types.map((id) => {
469
+ const desc = CHART_TYPE_DESCRIPTIONS[id];
470
+ return desc ? `- ${id}: ${desc}` : `- ${id}`;
471
+ });
472
+ return {
473
+ content: [
474
+ {
475
+ type: "text",
476
+ text: `Supported chart types (${types.length}):
477
+
478
+ ${lines.join("\n")}`
479
+ }
480
+ ]
481
+ };
482
+ }
483
+ );
484
+ server.tool(
485
+ "get_language_reference",
486
+ "Get the DGMO language reference documentation. Optionally filter by chart type.",
487
+ {
488
+ chart_type: import_zod.z.string().optional().describe('Optional chart type to get reference for (e.g. "sequence", "flowchart", "bar")')
489
+ },
490
+ async ({ chart_type }) => {
491
+ let content;
492
+ try {
493
+ content = resolveLanguageReference();
494
+ } catch {
495
+ return {
496
+ content: [
497
+ {
498
+ type: "text",
499
+ text: "Could not find language-reference.md. Is @diagrammo/dgmo installed?"
500
+ }
501
+ ],
502
+ isError: true
503
+ };
504
+ }
505
+ if (chart_type) {
506
+ const section = extractSection(content, chart_type);
507
+ if (!section) {
508
+ return {
509
+ content: [
510
+ {
511
+ type: "text",
512
+ text: `No section found for chart type "${chart_type}". Use list_chart_types to see available types.`
513
+ }
514
+ ],
515
+ isError: true
516
+ };
517
+ }
518
+ return { content: [{ type: "text", text: section }] };
519
+ }
520
+ return { content: [{ type: "text", text: content }] };
521
+ }
522
+ );
523
+ server.tool(
524
+ "preview_diagram",
525
+ 'Render one or more DGMO diagrams and open an HTML preview in the browser. Supports theme toggle and optional source display. IMPORTANT: Parentheses in DGMO labels = color notation (stripped from name). All labels must be unique. Use dashes for qualifiers, e.g. "App - TS" not "App (TS)".',
526
+ {
527
+ diagrams: import_zod.z.array(
528
+ import_zod.z.object({
529
+ title: import_zod.z.string().optional().describe("Optional title for this diagram"),
530
+ dgmo: import_zod.z.string().describe("DGMO diagram markup. Parentheses in labels = color notation. All labels must be unique.")
531
+ })
532
+ ).min(1).describe("One or more diagrams to preview"),
533
+ theme: import_zod.z.enum(["light", "dark"]).default("light").describe("Color theme"),
534
+ palette: import_zod.z.string().default("nord").describe("Color palette"),
535
+ include_source: import_zod.z.boolean().default(false).describe("Show DGMO source in collapsible blocks")
536
+ },
537
+ async ({ diagrams, theme, palette, include_source }) => {
538
+ const paletteConfig = (0, import_dgmo.getPalette)(palette);
539
+ const results = await Promise.all(
540
+ diagrams.map(async (d) => {
541
+ const { svg, error } = await tryRender(d.dgmo, theme, palette);
542
+ return { title: d.title, dgmo: d.dgmo, svg, error };
543
+ })
544
+ );
545
+ const successes = results.filter((r) => r.svg);
546
+ const failures = results.filter((r) => !r.svg);
547
+ if (successes.length === 0) {
548
+ return {
549
+ content: [
550
+ {
551
+ type: "text",
552
+ text: "All diagrams failed to render:\n" + failures.map((f) => `- ${f.title || "untitled"}: ${f.error}`).join("\n")
553
+ }
554
+ ],
555
+ isError: true
556
+ };
557
+ }
558
+ let html;
559
+ if (diagrams.length === 1 && successes.length === 1) {
560
+ const r = results[0];
561
+ html = buildPreviewHtml({
562
+ svg: r.svg,
563
+ title: r.title,
564
+ dgmoSource: include_source ? r.dgmo : void 0,
565
+ palette: paletteConfig
566
+ });
567
+ } else {
568
+ const sections = results.map((r) => ({
569
+ title: r.title || "Untitled",
570
+ svg: r.svg,
571
+ dgmoSource: r.dgmo,
572
+ error: r.error ?? void 0
573
+ }));
574
+ html = buildReportHtml({
575
+ title: "Diagram Preview",
576
+ sections,
577
+ palette: paletteConfig,
578
+ includeSource: include_source
579
+ });
580
+ }
581
+ const filePath = writeTempHtml(html, "dgmo-preview");
582
+ await openInBrowser(filePath);
583
+ let message = `Opened preview in browser: ${filePath}`;
584
+ if (failures.length > 0) {
585
+ message += "\n\nWarning \u2014 some diagrams failed to render:\n" + failures.map((f) => `- ${f.title || "untitled"}: ${f.error}`).join("\n");
586
+ }
587
+ return {
588
+ content: [{ type: "text", text: message }]
589
+ };
590
+ }
591
+ );
592
+ server.tool(
593
+ "generate_report",
594
+ 'Generate a polished HTML report with multiple DGMO diagrams, table of contents, and optional source blocks. Opens in browser by default. IMPORTANT: Parentheses in DGMO labels = color notation (stripped from name). All labels must be unique. Use dashes for qualifiers, e.g. "App - TS" not "App (TS)".',
595
+ {
596
+ title: import_zod.z.string().describe("Report title"),
597
+ subtitle: import_zod.z.string().optional().describe("Optional subtitle"),
598
+ sections: import_zod.z.array(
599
+ import_zod.z.object({
600
+ title: import_zod.z.string().describe("Section title"),
601
+ description: import_zod.z.string().optional().describe("Optional section description"),
602
+ dgmo: import_zod.z.string().describe("DGMO diagram markup. Parentheses in labels = color notation. All labels must be unique.")
603
+ })
604
+ ).min(1).describe("Report sections, each with a diagram"),
605
+ theme: import_zod.z.enum(["light", "dark"]).default("light").describe("Color theme"),
606
+ palette: import_zod.z.string().default("nord").describe("Color palette"),
607
+ include_source: import_zod.z.boolean().default(false).describe("Show DGMO source in collapsible blocks"),
608
+ open: import_zod.z.boolean().default(true).describe("Open the report in the browser")
609
+ },
610
+ async ({ title, subtitle, sections: inputSections, theme, palette, include_source, open }) => {
611
+ const paletteConfig = (0, import_dgmo.getPalette)(palette);
612
+ const results = await Promise.all(
613
+ inputSections.map(async (s) => {
614
+ const { svg, error } = await tryRender(s.dgmo, theme, palette);
615
+ return { ...s, svg, error };
616
+ })
617
+ );
618
+ const successes = results.filter((r) => r.svg);
619
+ const failures = results.filter((r) => !r.svg);
620
+ if (successes.length === 0) {
621
+ return {
622
+ content: [
623
+ {
624
+ type: "text",
625
+ text: "All sections failed to render:\n" + failures.map((f) => `- ${f.title}: ${f.error}`).join("\n")
626
+ }
627
+ ],
628
+ isError: true
629
+ };
630
+ }
631
+ const sections = results.map((r) => ({
632
+ title: r.title,
633
+ description: r.description,
634
+ svg: r.svg,
635
+ dgmoSource: r.dgmo,
636
+ error: r.error ?? void 0
637
+ }));
638
+ const html = buildReportHtml({
639
+ title,
640
+ subtitle,
641
+ sections,
642
+ palette: paletteConfig,
643
+ includeSource: include_source
644
+ });
645
+ const filePath = writeTempHtml(html, "dgmo-report");
646
+ if (open) {
647
+ await openInBrowser(filePath);
648
+ }
649
+ let message = open ? `Opened report in browser: ${filePath}` : `Report saved to: ${filePath}`;
650
+ if (failures.length > 0) {
651
+ message += "\n\nWarning \u2014 some sections failed to render:\n" + failures.map((f) => `- ${f.title}: ${f.error}`).join("\n");
652
+ }
653
+ return {
654
+ content: [{ type: "text", text: message }]
655
+ };
656
+ }
657
+ );
658
+ async function main() {
659
+ const transport = new import_stdio.StdioServerTransport();
660
+ await server.connect(transport);
661
+ }
662
+ main().catch((err) => {
663
+ console.error("Failed to start dgmo MCP server:", err);
664
+ process.exit(1);
665
+ });
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@diagrammo/dgmo-mcp",
3
+ "version": "0.1.0",
4
+ "description": "MCP server for rendering DGMO diagrams — SVG/PNG output, browser preview, HTML reports, and 29 chart types. Works with Claude Desktop, Claude Code, and any MCP client.",
5
+ "keywords": [
6
+ "mcp",
7
+ "mcp-server",
8
+ "diagrams",
9
+ "svg",
10
+ "charts",
11
+ "sequence-diagram",
12
+ "flowchart",
13
+ "diagrammo",
14
+ "dgmo",
15
+ "claude",
16
+ "ai"
17
+ ],
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "https://github.com/diagrammo/dgmo-mcp.git"
21
+ },
22
+ "homepage": "https://diagrammo.app/mcp",
23
+ "bin": {
24
+ "dgmo-mcp": "./dist/index.js"
25
+ },
26
+ "main": "./dist/index.js",
27
+ "types": "./dist/index.d.ts",
28
+ "scripts": {
29
+ "build": "tsup src/index.ts --format cjs --dts",
30
+ "dev": "tsup src/index.ts --format cjs --dts --watch",
31
+ "typecheck": "tsc --noEmit"
32
+ },
33
+ "dependencies": {
34
+ "@diagrammo/dgmo": "^0.2.27",
35
+ "@modelcontextprotocol/sdk": "^1.12.1",
36
+ "@resvg/resvg-js": "^2.6.2",
37
+ "zod": "^3.24.0"
38
+ },
39
+ "devDependencies": {
40
+ "@types/node": "^22.0.0",
41
+ "tsup": "^8.4.0",
42
+ "typescript": "^5.7.0"
43
+ },
44
+ "files": [
45
+ "dist"
46
+ ],
47
+ "license": "MIT"
48
+ }