@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 +95 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +665 -0
- package/package.json +48 -0
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
|
+
```
|
package/dist/index.d.ts
ADDED
|
@@ -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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
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 · ${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
|
+
}
|