@archform/mcp 0.0.0-stage → 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 Himanshu Mamgain
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 CHANGED
@@ -1,3 +1,149 @@
1
- # Temporary Holding Version
1
+ # @archform/mcp
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ An [MCP](https://modelcontextprotocol.io) server that lets AI assistants render and validate **diagrams as code**: Mermaid, PlantUML, C4-PlantUML, Graphviz, D2, Structurizr, BPMN and Excalidraw.
4
+
5
+ It works in Claude Desktop, Claude Code, Cursor and VS Code over stdio, and in ChatGPT developer mode as a remote Streamable HTTP endpoint.
6
+
7
+ > **Privacy:** rendering is done by [Kroki](https://kroki.io). By default, **diagram source is sent to the public kroki.io service**. If your diagrams describe anything you would not paste into a public website, [self-host Kroki](#self-hosting-kroki) and set `KROKI_URL`.
8
+
9
+ ## Tools
10
+
11
+ All tools are read-only (`readOnlyHint: true`, `openWorldHint: true`).
12
+
13
+ | Tool | Input | Result |
14
+ | --- | --- | --- |
15
+ | `list_formats` | none | Every supported format with aliases, outputs, a docs link and a minimal valid example |
16
+ | `render_diagram` | `format`, `source` (≤ 100 KB), `output` (`svg` default, or `png`) | SVG as an embedded `image/svg+xml` resource, or PNG as image content, plus a shareable kroki.io link. Invalid source returns `isError: true` with the parser message and line |
17
+ | `validate_diagram` | `format`, `source` | `{ valid, errors: [{ line?, message }] }`. An invalid diagram is a normal result, not a tool error |
18
+
19
+ ## Formats
20
+
21
+ | Format | Aliases | Outputs |
22
+ | --- | --- | --- |
23
+ | `mermaid` | | svg, png |
24
+ | `plantuml` | | svg, png |
25
+ | `c4plantuml` | | svg, png |
26
+ | `graphviz` | `dot` | svg, png |
27
+ | `d2` | | svg |
28
+ | `structurizr` | | svg, png |
29
+ | `bpmn` | | svg |
30
+ | `excalidraw` | | svg |
31
+
32
+ ## Use it locally (stdio)
33
+
34
+ Requires Node.js 22 or later.
35
+
36
+ **Claude Desktop**: add to `claude_desktop_config.json`:
37
+
38
+ ```json
39
+ {
40
+ "mcpServers": {
41
+ "archform": {
42
+ "command": "npx",
43
+ "args": ["-y", "@archform/mcp"]
44
+ }
45
+ }
46
+ }
47
+ ```
48
+
49
+ **Claude Code**:
50
+
51
+ ```sh
52
+ claude mcp add archform -- npx -y @archform/mcp
53
+ ```
54
+
55
+ **Cursor**: add to `~/.cursor/mcp.json` (or `.cursor/mcp.json` in a project):
56
+
57
+ ```json
58
+ {
59
+ "mcpServers": {
60
+ "archform": { "command": "npx", "args": ["-y", "@archform/mcp"] }
61
+ }
62
+ }
63
+ ```
64
+
65
+ **VS Code**: add to `.vscode/mcp.json`:
66
+
67
+ ```json
68
+ {
69
+ "servers": {
70
+ "archform": { "type": "stdio", "command": "npx", "args": ["-y", "@archform/mcp"] }
71
+ }
72
+ }
73
+ ```
74
+
75
+ To use your own Kroki, add `"env": { "KROKI_URL": "https://kroki.internal.example.com" }` to the server entry.
76
+
77
+ ## Run it remotely (Streamable HTTP)
78
+
79
+ ```sh
80
+ npx -y @archform/mcp --http --port 3000
81
+ ```
82
+
83
+ - `POST /mcp`: MCP traffic (stateless)
84
+ - `GET /health`: health check
85
+
86
+ By default the server binds to `127.0.0.1` and rejects requests whose `Host` or `Origin` is not localhost (DNS rebinding protection). To expose it publicly, for example for ChatGPT developer mode, bind to all interfaces and name your public hostname:
87
+
88
+ ```sh
89
+ HOST=0.0.0.0 ALLOWED_HOSTS=mcp.example.com PORT=8080 npx -y @archform/mcp --http
90
+ ```
91
+
92
+ Then add `https://mcp.example.com/mcp` as a connector in ChatGPT (Settings → Connectors, developer mode) with no authentication. Serve it behind HTTPS.
93
+
94
+ The HTTP endpoint limits request bodies to 512 KB and rate-limits each client IP (60 requests per minute by default, set by `RATE_LIMIT`). The limiter is in memory, so when you run several instances, put a shared limiter in front.
95
+
96
+ ## Configuration
97
+
98
+ | Flag / variable | Default | Meaning |
99
+ | --- | --- | --- |
100
+ | `--http` | off | Serve Streamable HTTP instead of stdio |
101
+ | `--port`, `PORT` | `3000` | HTTP port |
102
+ | `--host`, `HOST` | `127.0.0.1` | HTTP bind address |
103
+ | `ALLOWED_HOSTS` | | Comma-separated `Host` values accepted on a public bind |
104
+ | `RATE_LIMIT` | `60` | Requests per minute per IP on `/mcp`; `0` disables |
105
+ | `KROKI_URL` | `https://kroki.io` | Kroki server used for rendering and share links |
106
+
107
+ ## Self-hosting Kroki
108
+
109
+ Kroki ships as Docker images. The core image covers PlantUML, C4-PlantUML, Graphviz, D2 and Structurizr; Mermaid, BPMN and Excalidraw each need a companion container:
110
+
111
+ ```yaml
112
+ # docker-compose.yml
113
+ services:
114
+ kroki:
115
+ image: yuzutech/kroki
116
+ ports: ["8000:8000"]
117
+ environment:
118
+ KROKI_MERMAID_HOST: mermaid
119
+ KROKI_BPMN_HOST: bpmn
120
+ KROKI_EXCALIDRAW_HOST: excalidraw
121
+ mermaid:
122
+ image: yuzutech/kroki-mermaid
123
+ bpmn:
124
+ image: yuzutech/kroki-bpmn
125
+ excalidraw:
126
+ image: yuzutech/kroki-excalidraw
127
+ ```
128
+
129
+ ```sh
130
+ docker compose up -d
131
+ KROKI_URL=http://localhost:8000 npx -y @archform/mcp
132
+ ```
133
+
134
+ Shareable links then point at your Kroki instance, so they only open for people who can reach it. See the [Kroki installation docs](https://docs.kroki.io/kroki/setup/install/) for details.
135
+
136
+ ## Development
137
+
138
+ ```sh
139
+ pnpm install
140
+ pnpm test # unit + in-process integration tests (Kroki stubbed)
141
+ KROKI_LIVE=1 pnpm test # also run the live tests against KROKI_URL
142
+ pnpm build
143
+ npx @modelcontextprotocol/inspector node dist/index.js # stdio
144
+ node dist/index.js --http # then point the Inspector at http://127.0.0.1:3000/mcp
145
+ ```
146
+
147
+ ## License
148
+
149
+ MIT
package/dist/index.js ADDED
@@ -0,0 +1,532 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { parseArgs } from "util";
5
+ import { serve } from "@hono/node-server";
6
+ import { serveStdio } from "@modelcontextprotocol/server/stdio";
7
+
8
+ // src/http.ts
9
+ import { createMcpHonoApp } from "@modelcontextprotocol/hono";
10
+ import { createMcpHandler } from "@modelcontextprotocol/server";
11
+
12
+ // src/server.ts
13
+ import { McpServer } from "@modelcontextprotocol/server";
14
+
15
+ // src/renderers/kroki.ts
16
+ import { deflateSync } from "zlib";
17
+
18
+ // src/renderers/kroki-errors.ts
19
+ var ZERO_BASED_LINES = /* @__PURE__ */ new Set(["plantuml", "c4plantuml"]);
20
+ var LINE_PATTERNS = [/\(line:\s*(\d+)\)/i, /-:(\d+):\d+:/, /\bline\s+(\d+)/i];
21
+ function parseKrokiError(format, body) {
22
+ const lines = body.replace(/^\s*Error \d{3}:\s*/, "").split(/\r?\n/).map((l) => l.trimEnd()).filter((l) => l.trim() !== "" && !/^\s+at\s/.test(l) && !/^\(exit code \d+\)$/.test(l.trim()));
23
+ const unique = lines.filter((l, i) => lines.indexOf(l) === i);
24
+ let message = unique.join("\n").trim() || "The diagram could not be rendered.";
25
+ if (message.length > 2e3) message = `${message.slice(0, 2e3)}\u2026`;
26
+ for (const pattern of LINE_PATTERNS) {
27
+ const match = pattern.exec(message);
28
+ if (match?.[1]) {
29
+ const raw = Number.parseInt(match[1], 10);
30
+ const line = ZERO_BASED_LINES.has(format) ? raw + 1 : raw;
31
+ return { line: Math.max(line, 1), message };
32
+ }
33
+ }
34
+ return { message };
35
+ }
36
+
37
+ // src/renderers/renderer.ts
38
+ var DiagramSyntaxError = class extends Error {
39
+ constructor(errors) {
40
+ super(errors.map(formatDiagramError).join("\n"));
41
+ this.errors = errors;
42
+ }
43
+ errors;
44
+ name = "DiagramSyntaxError";
45
+ };
46
+ var RenderServiceError = class extends Error {
47
+ name = "RenderServiceError";
48
+ };
49
+ function formatDiagramError(error) {
50
+ return error.line === void 0 ? error.message : `Line ${error.line}: ${error.message}`;
51
+ }
52
+
53
+ // src/renderers/kroki.ts
54
+ var DEFAULT_KROKI_URL = "https://kroki.io";
55
+ var TIMEOUT_MS = 15e3;
56
+ function encodeKrokiSource(source) {
57
+ return deflateSync(Buffer.from(source, "utf8"), { level: 9 }).toString("base64url");
58
+ }
59
+ var KrokiRenderer = class {
60
+ baseUrl;
61
+ timeoutMs;
62
+ constructor(options = {}) {
63
+ const base = options.baseUrl ?? process.env.KROKI_URL ?? DEFAULT_KROKI_URL;
64
+ this.baseUrl = base.replace(/\/+$/, "");
65
+ this.timeoutMs = options.timeoutMs ?? TIMEOUT_MS;
66
+ }
67
+ async render(format, source, output) {
68
+ let res;
69
+ try {
70
+ res = await fetch(`${this.baseUrl}/${format}/${output}`, {
71
+ method: "POST",
72
+ headers: { "Content-Type": "text/plain; charset=utf-8", Accept: contentType(output) },
73
+ body: source,
74
+ signal: AbortSignal.timeout(this.timeoutMs)
75
+ });
76
+ } catch (error) {
77
+ if (error instanceof DOMException && (error.name === "TimeoutError" || error.name === "AbortError")) {
78
+ throw new RenderServiceError(
79
+ `Kroki at ${this.baseUrl} did not respond within ${this.timeoutMs / 1e3}s. Try again, or simplify the diagram.`
80
+ );
81
+ }
82
+ const reason = error instanceof Error ? error.cause instanceof Error ? error.cause.message : error.message : "";
83
+ throw new RenderServiceError(`Could not reach Kroki at ${this.baseUrl}${reason ? `: ${reason}` : ""}.`);
84
+ }
85
+ if (res.ok) return Buffer.from(await res.arrayBuffer());
86
+ const body = await res.text().catch(() => "");
87
+ if (res.status === 400) {
88
+ throw new DiagramSyntaxError([parseKrokiError(format, body)]);
89
+ }
90
+ throw new RenderServiceError(
91
+ `Kroki at ${this.baseUrl} returned HTTP ${res.status}${res.status >= 500 ? " (a server-side failure, not necessarily a problem with the diagram)" : ""}. Try again later or use a self-hosted Kroki via KROKI_URL.`
92
+ );
93
+ }
94
+ shareUrl(format, source) {
95
+ return `${this.baseUrl}/${format}/svg/${encodeKrokiSource(source)}`;
96
+ }
97
+ };
98
+ function contentType(output) {
99
+ return output === "png" ? "image/png" : "image/svg+xml";
100
+ }
101
+
102
+ // src/tools/list-formats.ts
103
+ import * as z2 from "zod/v4";
104
+
105
+ // src/formats.ts
106
+ var FORMATS = [
107
+ {
108
+ id: "mermaid",
109
+ aliases: [],
110
+ outputs: ["svg", "png"],
111
+ description: "Flowcharts, sequence, class, state, ER, Gantt and other diagrams in Markdown-like syntax.",
112
+ docsUrl: "https://mermaid.js.org/intro/syntax-reference.html",
113
+ example: "flowchart LR\n A[Client] --> B[API]\n B --> C[(Database)]\n"
114
+ },
115
+ {
116
+ id: "plantuml",
117
+ aliases: [],
118
+ outputs: ["svg", "png"],
119
+ description: "UML diagrams (sequence, class, component, activity, state, deployment) and more.",
120
+ docsUrl: "https://plantuml.com/",
121
+ example: "@startuml\nAlice -> Bob: Request\nBob --> Alice: Response\n@enduml\n"
122
+ },
123
+ {
124
+ id: "c4plantuml",
125
+ aliases: [],
126
+ outputs: ["svg", "png"],
127
+ description: "C4 model architecture diagrams (context, container, component) on top of PlantUML.",
128
+ docsUrl: "https://github.com/plantuml-stdlib/C4-PlantUML",
129
+ example: '@startuml\n!include <C4/C4_Context>\nPerson(user, "User")\nSystem(app, "Web App")\nRel(user, app, "Uses")\n@enduml\n'
130
+ },
131
+ {
132
+ id: "graphviz",
133
+ aliases: ["dot"],
134
+ outputs: ["svg", "png"],
135
+ description: "Graphs and networks in the Graphviz DOT language.",
136
+ docsUrl: "https://graphviz.org/doc/info/lang.html",
137
+ example: "digraph G {\n rankdir=LR;\n client -> api -> db;\n}\n"
138
+ },
139
+ {
140
+ id: "d2",
141
+ aliases: [],
142
+ outputs: ["svg"],
143
+ description: "Declarative diagrams with automatic layout, well suited to software architecture.",
144
+ docsUrl: "https://d2lang.com/tour/intro",
145
+ example: "client -> api: HTTPS\napi -> db: SQL\ndb: {shape: cylinder}\n"
146
+ },
147
+ {
148
+ id: "structurizr",
149
+ aliases: [],
150
+ outputs: ["svg", "png"],
151
+ description: "C4 model workspaces in the Structurizr DSL (model plus views).",
152
+ docsUrl: "https://docs.structurizr.com/dsl/language",
153
+ example: 'workspace {\n model {\n user = person "User"\n app = softwareSystem "App"\n user -> app "Uses"\n }\n views {\n systemContext app {\n include *\n autolayout lr\n }\n }\n}\n'
154
+ },
155
+ {
156
+ id: "bpmn",
157
+ aliases: [],
158
+ outputs: ["svg"],
159
+ description: "Business process diagrams in BPMN 2.0 XML (including diagram interchange coordinates).",
160
+ docsUrl: "https://www.omg.org/spec/BPMN/2.0/",
161
+ example: '<?xml version="1.0" encoding="UTF-8"?>\n<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="defs" targetNamespace="http://bpmn.io/schema/bpmn">\n <bpmn:process id="process" isExecutable="false">\n <bpmn:startEvent id="start"><bpmn:outgoing>flow</bpmn:outgoing></bpmn:startEvent>\n <bpmn:endEvent id="end"><bpmn:incoming>flow</bpmn:incoming></bpmn:endEvent>\n <bpmn:sequenceFlow id="flow" sourceRef="start" targetRef="end" />\n </bpmn:process>\n <bpmndi:BPMNDiagram id="diagram">\n <bpmndi:BPMNPlane id="plane" bpmnElement="process">\n <bpmndi:BPMNShape id="start_di" bpmnElement="start"><dc:Bounds x="100" y="100" width="36" height="36" /></bpmndi:BPMNShape>\n <bpmndi:BPMNShape id="end_di" bpmnElement="end"><dc:Bounds x="250" y="100" width="36" height="36" /></bpmndi:BPMNShape>\n <bpmndi:BPMNEdge id="flow_di" bpmnElement="flow"><di:waypoint x="136" y="118" /><di:waypoint x="250" y="118" /></bpmndi:BPMNEdge>\n </bpmndi:BPMNPlane>\n </bpmndi:BPMNDiagram>\n</bpmn:definitions>\n'
162
+ },
163
+ {
164
+ id: "excalidraw",
165
+ aliases: [],
166
+ outputs: ["svg"],
167
+ description: "Hand-drawn style sketches from an Excalidraw JSON scene.",
168
+ docsUrl: "https://docs.excalidraw.com/docs/codebase/json-schema",
169
+ example: '{\n "type": "excalidraw",\n "version": 2,\n "source": "https://excalidraw.com",\n "elements": [\n {\n "id": "box",\n "type": "rectangle",\n "x": 0,\n "y": 0,\n "width": 160,\n "height": 80,\n "angle": 0,\n "strokeColor": "#1e1e1e",\n "backgroundColor": "transparent",\n "fillStyle": "solid",\n "strokeWidth": 2,\n "strokeStyle": "solid",\n "roughness": 1,\n "opacity": 100,\n "groupIds": [],\n "seed": 1,\n "version": 1,\n "versionNonce": 1,\n "isDeleted": false,\n "boundElements": null,\n "updated": 1,\n "link": null,\n "locked": false\n }\n ],\n "appState": { "viewBackgroundColor": "#ffffff" },\n "files": {}\n}\n'
170
+ }
171
+ ];
172
+ var FORMAT_NAMES = [...FORMATS.map((f) => f.id), ...FORMATS.flatMap((f) => f.aliases)];
173
+ var byName = /* @__PURE__ */ new Map();
174
+ for (const format of FORMATS) {
175
+ byName.set(format.id, format);
176
+ for (const alias of format.aliases) byName.set(alias, format);
177
+ }
178
+ function resolveFormat(name) {
179
+ return byName.get(name.trim().toLowerCase());
180
+ }
181
+
182
+ // src/tools/shared.ts
183
+ import * as z from "zod/v4";
184
+ var MAX_SOURCE_BYTES = 100 * 1024;
185
+ var READ_ONLY_ANNOTATIONS = { readOnlyHint: true, openWorldHint: true };
186
+ var formatSchema = z.enum(FORMAT_NAMES).describe(`Diagram language. One of: ${FORMAT_NAMES.join(", ")} ("dot" is an alias for graphviz).`);
187
+ var sourceSchema = z.string().min(1, "source is empty").refine((s) => Buffer.byteLength(s, "utf8") <= MAX_SOURCE_BYTES, {
188
+ message: `source must be at most ${MAX_SOURCE_BYTES / 1024} KB`
189
+ }).describe("The diagram source text, exactly as it would appear in a file of that format.");
190
+ function toolError(text) {
191
+ return { content: [{ type: "text", text }], isError: true };
192
+ }
193
+
194
+ // src/tools/list-formats.ts
195
+ var formatEntry = z2.object({
196
+ id: z2.string(),
197
+ aliases: z2.array(z2.string()),
198
+ outputs: z2.array(z2.enum(["svg", "png"])),
199
+ description: z2.string(),
200
+ docsUrl: z2.string(),
201
+ example: z2.string()
202
+ });
203
+ function registerListFormats(server) {
204
+ server.registerTool(
205
+ "list_formats",
206
+ {
207
+ title: "List diagram formats",
208
+ description: "List the diagram languages this server can render (Mermaid, PlantUML, C4-PlantUML, Graphviz, D2, Structurizr, BPMN, Excalidraw), with each one's aliases, supported outputs (svg, png), syntax docs link and a minimal valid example. Call this before writing a diagram in a format you are unsure of, or to check whether PNG output is available.",
209
+ outputSchema: z2.object({ formats: z2.array(formatEntry) }),
210
+ annotations: READ_ONLY_ANNOTATIONS
211
+ },
212
+ async () => {
213
+ const formats = FORMATS.map((f) => ({ ...f, aliases: [...f.aliases], outputs: [...f.outputs] }));
214
+ return {
215
+ content: [{ type: "text", text: JSON.stringify({ formats }, null, 2) }],
216
+ structuredContent: { formats }
217
+ };
218
+ }
219
+ );
220
+ }
221
+
222
+ // src/tools/render-diagram.ts
223
+ import { createHash } from "crypto";
224
+ import * as z3 from "zod/v4";
225
+ function registerRenderDiagram(server, renderer) {
226
+ server.registerTool(
227
+ "render_diagram",
228
+ {
229
+ title: "Render diagram",
230
+ description: "Render diagram source (Mermaid, PlantUML, C4-PlantUML, Graphviz/DOT, D2, Structurizr, BPMN, Excalidraw) to an SVG or PNG image, and get a shareable link that opens the diagram in a browser. Use it when the user wants to see a diagram. If the source is invalid the result is an error with the parser message and line number: fix the source and retry. Call validate_diagram to check syntax without rendering, or list_formats for examples and which formats support PNG.",
231
+ inputSchema: z3.object({
232
+ format: formatSchema,
233
+ source: sourceSchema,
234
+ output: z3.enum(["svg", "png"]).default("svg").describe("Image type. Defaults to svg. d2, bpmn and excalidraw support svg only.")
235
+ }),
236
+ annotations: READ_ONLY_ANNOTATIONS
237
+ },
238
+ async ({ format: name, source, output }) => {
239
+ const format = resolveFormat(name);
240
+ if (!format) return toolError(`Unknown format "${name}". Call list_formats to see the supported formats.`);
241
+ if (!format.outputs.includes(output)) {
242
+ return toolError(
243
+ `${format.id} does not support ${output} output. Supported: ${format.outputs.join(", ")}. Retry with output "svg".`
244
+ );
245
+ }
246
+ let image;
247
+ try {
248
+ image = await renderer.render(format.id, source, output);
249
+ } catch (error) {
250
+ if (error instanceof DiagramSyntaxError) {
251
+ return toolError(
252
+ `The ${format.id} diagram is invalid:
253
+ ${error.message}
254
+
255
+ Fix the source and call render_diagram again. list_formats has a valid ${format.id} example.`
256
+ );
257
+ }
258
+ if (error instanceof RenderServiceError) return toolError(error.message);
259
+ throw error;
260
+ }
261
+ const url = renderer.shareUrl?.(format.id, source);
262
+ const content = [];
263
+ if (output === "png") {
264
+ content.push({ type: "image", data: image.toString("base64"), mimeType: "image/png" });
265
+ } else {
266
+ const hash = createHash("sha256").update(source).digest("hex").slice(0, 16);
267
+ content.push({
268
+ type: "resource",
269
+ resource: {
270
+ uri: `archform://diagram/${format.id}/${hash}.svg`,
271
+ mimeType: "image/svg+xml",
272
+ text: image.toString("utf8")
273
+ }
274
+ });
275
+ }
276
+ content.push({
277
+ type: "text",
278
+ text: url ? `Rendered ${format.id} diagram as ${output}. Shareable link (opens the SVG in a browser): ${url}` : `Rendered ${format.id} diagram as ${output}.`
279
+ });
280
+ return { content };
281
+ }
282
+ );
283
+ }
284
+
285
+ // src/tools/validate-diagram.ts
286
+ import * as z4 from "zod/v4";
287
+ var outputSchema = z4.object({
288
+ valid: z4.boolean(),
289
+ errors: z4.array(z4.object({ line: z4.number().int().optional(), message: z4.string() }))
290
+ });
291
+ function registerValidateDiagram(server, renderer) {
292
+ server.registerTool(
293
+ "validate_diagram",
294
+ {
295
+ title: "Validate diagram",
296
+ description: "Check whether diagram source (Mermaid, PlantUML, C4-PlantUML, Graphviz/DOT, D2, Structurizr, BPMN, Excalidraw) is valid, without returning an image. Returns { valid, errors: [{ line?, message }] }. Use it to check syntax you are unsure of before rendering or before handing diagram code to the user. Call list_formats for a valid example of each format.",
297
+ inputSchema: z4.object({ format: formatSchema, source: sourceSchema }),
298
+ outputSchema,
299
+ annotations: READ_ONLY_ANNOTATIONS
300
+ },
301
+ async ({ format: name, source }) => {
302
+ const format = resolveFormat(name);
303
+ if (!format) return toolError(`Unknown format "${name}". Call list_formats to see the supported formats.`);
304
+ let errors = [];
305
+ try {
306
+ await renderer.render(format.id, source, "svg");
307
+ } catch (error) {
308
+ if (error instanceof DiagramSyntaxError) errors = error.errors;
309
+ else if (error instanceof RenderServiceError) {
310
+ return toolError(`Could not validate the diagram: ${error.message}`);
311
+ } else throw error;
312
+ }
313
+ const result = { valid: errors.length === 0, errors };
314
+ return {
315
+ content: [{ type: "text", text: JSON.stringify(result) }],
316
+ structuredContent: result
317
+ };
318
+ }
319
+ );
320
+ }
321
+
322
+ // package.json
323
+ var package_default = {
324
+ name: "@archform/mcp",
325
+ version: "0.1.0",
326
+ description: "MCP server that renders and validates diagrams as code: Mermaid, PlantUML, C4, Graphviz, D2, Structurizr, BPMN and Excalidraw.",
327
+ mcpName: "io.github.himanshu-mamgain/archform",
328
+ license: "MIT",
329
+ author: "Himanshu Mamgain",
330
+ homepage: "https://github.com/himanshu-mamgain/archform/tree/main/packages/mcp#readme",
331
+ repository: {
332
+ type: "git",
333
+ url: "git+https://github.com/himanshu-mamgain/archform.git",
334
+ directory: "packages/mcp"
335
+ },
336
+ bugs: "https://github.com/himanshu-mamgain/archform/issues",
337
+ type: "module",
338
+ bin: {
339
+ "archform-mcp": "dist/index.js"
340
+ },
341
+ files: [
342
+ "dist"
343
+ ],
344
+ engines: {
345
+ node: ">=22"
346
+ },
347
+ publishConfig: {
348
+ access: "public"
349
+ },
350
+ keywords: [
351
+ "mcp",
352
+ "model-context-protocol",
353
+ "diagrams-as-code",
354
+ "mermaid",
355
+ "plantuml",
356
+ "graphviz",
357
+ "d2",
358
+ "c4",
359
+ "architecture"
360
+ ],
361
+ scripts: {
362
+ build: "tsup",
363
+ dev: "tsup --watch",
364
+ start: "node dist/index.js",
365
+ typecheck: "tsc -p tsconfig.json",
366
+ test: "vitest run",
367
+ "test:live": "cross-env KROKI_LIVE=1 vitest run test/live.test.ts",
368
+ prepublishOnly: "tsup"
369
+ },
370
+ devDependencies: {
371
+ "@modelcontextprotocol/client": "2.3.1",
372
+ "@types/node": "22.20.5",
373
+ "cross-env": "^10.1.0",
374
+ tsup: "8.5.1",
375
+ vitest: "4.1.12"
376
+ },
377
+ dependencies: {
378
+ "@hono/node-server": "2.1.5",
379
+ "@modelcontextprotocol/hono": "2.0.2",
380
+ "@modelcontextprotocol/server": "2.3.1",
381
+ hono: "4.13.13",
382
+ zod: "4.6.7"
383
+ }
384
+ };
385
+
386
+ // src/version.ts
387
+ var VERSION = package_default.version;
388
+
389
+ // src/server.ts
390
+ function createServer(options = {}) {
391
+ const renderer = options.renderer ?? new KrokiRenderer();
392
+ const server = new McpServer(
393
+ { name: "archform", title: "Archform", version: VERSION },
394
+ {
395
+ instructions: "Archform renders and validates diagrams as code. Call list_formats to see supported languages and examples, validate_diagram to check syntax, and render_diagram to produce an image and a shareable link."
396
+ }
397
+ );
398
+ registerListFormats(server);
399
+ registerRenderDiagram(server, renderer);
400
+ registerValidateDiagram(server, renderer);
401
+ return server;
402
+ }
403
+
404
+ // src/http.ts
405
+ var MAX_REQUEST_BODY_BYTES = 512 * 1024;
406
+ function createHttpApp(options = {}) {
407
+ const handler = createMcpHandler(() => createServer(options), { maxRequestBodySize: MAX_REQUEST_BODY_BYTES });
408
+ const app = createMcpHonoApp({
409
+ host: options.host ?? "127.0.0.1",
410
+ ...options.allowedHosts?.length ? { allowedHosts: options.allowedHosts } : {},
411
+ maxRequestBodySize: MAX_REQUEST_BODY_BYTES
412
+ });
413
+ app.get("/health", (c) => c.json({ status: "ok", name: "archform", version: VERSION }));
414
+ const limit = options.rateLimit ?? 60;
415
+ if (limit > 0) app.use("/mcp", rateLimiter(limit, options.rateLimitWindowMs ?? 6e4));
416
+ app.post("/mcp", (c) => handler.fetch(c.req.raw, { parsedBody: c.get("parsedBody") }));
417
+ app.on(["GET", "DELETE"], "/mcp", (c) => c.json({ error: "Method not allowed. Use POST /mcp." }, 405));
418
+ return { app, handler };
419
+ }
420
+ function rateLimiter(limit, windowMs) {
421
+ const hits = /* @__PURE__ */ new Map();
422
+ return async (c, next) => {
423
+ const now = Date.now();
424
+ if (hits.size > 1e4) {
425
+ for (const [key2, entry2] of hits) if (entry2.resetAt <= now) hits.delete(key2);
426
+ }
427
+ const key = clientIp(c);
428
+ let entry = hits.get(key);
429
+ if (!entry || entry.resetAt <= now) {
430
+ entry = { count: 0, resetAt: now + windowMs };
431
+ hits.set(key, entry);
432
+ }
433
+ entry.count++;
434
+ c.header("RateLimit-Limit", String(limit));
435
+ c.header("RateLimit-Remaining", String(Math.max(0, limit - entry.count)));
436
+ if (entry.count > limit) {
437
+ c.header("Retry-After", String(Math.ceil((entry.resetAt - now) / 1e3)));
438
+ return c.json({ error: "Too many requests. Try again later." }, 429);
439
+ }
440
+ await next();
441
+ };
442
+ }
443
+ function clientIp(c) {
444
+ const forwarded = c.req.header("x-forwarded-for")?.split(",")[0]?.trim();
445
+ if (forwarded) return forwarded;
446
+ const incoming = c.env?.incoming;
447
+ return incoming?.socket?.remoteAddress ?? "unknown";
448
+ }
449
+
450
+ // src/index.ts
451
+ var log = (message) => console.error(`[archform] ${message}`);
452
+ var HELP = `archform-mcp ${VERSION}
453
+
454
+ Usage:
455
+ archform-mcp Serve MCP over stdio (default)
456
+ archform-mcp --http Serve MCP over Streamable HTTP (POST /mcp, GET /health)
457
+
458
+ Options:
459
+ --http Use Streamable HTTP instead of stdio
460
+ --port <n> HTTP port (default: $PORT or 3000)
461
+ --host <addr> HTTP bind address (default: $HOST or 127.0.0.1)
462
+ -v, --version Print the version
463
+ -h, --help Print this help
464
+
465
+ Environment:
466
+ KROKI_URL Kroki server (default: https://kroki.io)
467
+ ALLOWED_HOSTS Comma-separated Host header values accepted when binding publicly
468
+ RATE_LIMIT Requests per minute per client IP on /mcp (default: 60, 0 disables)
469
+ `;
470
+ function main() {
471
+ let values;
472
+ try {
473
+ values = parse().values;
474
+ } catch (error) {
475
+ log(error instanceof Error ? error.message : String(error));
476
+ console.error(HELP);
477
+ process.exit(2);
478
+ }
479
+ if (values.help) {
480
+ console.error(HELP);
481
+ return;
482
+ }
483
+ if (values.version) {
484
+ console.error(VERSION);
485
+ return;
486
+ }
487
+ if (values.http) {
488
+ startHttp(values.port, values.host);
489
+ return;
490
+ }
491
+ const handle = serveStdio(() => createServer());
492
+ const shutdown = () => void handle.close().finally(() => process.exit(0));
493
+ process.on("SIGINT", shutdown);
494
+ process.on("SIGTERM", shutdown);
495
+ log(`v${VERSION} running on stdio (Kroki: ${process.env.KROKI_URL ?? "https://kroki.io"})`);
496
+ }
497
+ function parse() {
498
+ return parseArgs({
499
+ options: {
500
+ http: { type: "boolean", default: false },
501
+ port: { type: "string" },
502
+ host: { type: "string" },
503
+ version: { type: "boolean", short: "v" },
504
+ help: { type: "boolean", short: "h" }
505
+ },
506
+ strict: true
507
+ });
508
+ }
509
+ function startHttp(portArg, hostArg) {
510
+ const port = Number(portArg ?? process.env.PORT ?? 3e3);
511
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
512
+ log(`Invalid port "${portArg ?? process.env.PORT}".`);
513
+ process.exit(2);
514
+ }
515
+ const host = hostArg ?? process.env.HOST ?? "127.0.0.1";
516
+ const allowedHosts = (process.env.ALLOWED_HOSTS ?? "").split(",").map((h) => h.trim()).filter(Boolean);
517
+ const rateLimit = process.env.RATE_LIMIT === void 0 ? void 0 : Number(process.env.RATE_LIMIT);
518
+ const { app, handler } = createHttpApp({ host, allowedHosts, ...rateLimit === void 0 ? {} : { rateLimit } });
519
+ const server = serve({ fetch: app.fetch, port, hostname: host }, () => {
520
+ log(`v${VERSION} listening on http://${host.includes(":") ? `[${host}]` : host}:${port}/mcp`);
521
+ if (!["127.0.0.1", "localhost", "::1"].includes(host) && allowedHosts.length === 0) {
522
+ log("Bound to a public interface without ALLOWED_HOSTS: Host header validation is off.");
523
+ }
524
+ });
525
+ const shutdown = () => {
526
+ void handler.close().finally(() => server.close(() => process.exit(0)));
527
+ };
528
+ process.on("SIGINT", shutdown);
529
+ process.on("SIGTERM", shutdown);
530
+ }
531
+ main();
532
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/http.ts","../src/server.ts","../src/renderers/kroki.ts","../src/renderers/kroki-errors.ts","../src/renderers/renderer.ts","../src/tools/list-formats.ts","../src/formats.ts","../src/tools/shared.ts","../src/tools/render-diagram.ts","../src/tools/validate-diagram.ts","../package.json","../src/version.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { parseArgs } from 'node:util';\nimport { serve } from '@hono/node-server';\nimport { serveStdio } from '@modelcontextprotocol/server/stdio';\nimport { createHttpApp } from './http.js';\nimport { createServer } from './server.js';\nimport { VERSION } from './version.js';\n\n// stdout carries the MCP protocol in stdio mode: every log goes to stderr.\nconst log = (message: string) => console.error(`[archform] ${message}`);\n\nconst HELP = `archform-mcp ${VERSION}\n\nUsage:\n archform-mcp Serve MCP over stdio (default)\n archform-mcp --http Serve MCP over Streamable HTTP (POST /mcp, GET /health)\n\nOptions:\n --http Use Streamable HTTP instead of stdio\n --port <n> HTTP port (default: $PORT or 3000)\n --host <addr> HTTP bind address (default: $HOST or 127.0.0.1)\n -v, --version Print the version\n -h, --help Print this help\n\nEnvironment:\n KROKI_URL Kroki server (default: https://kroki.io)\n ALLOWED_HOSTS Comma-separated Host header values accepted when binding publicly\n RATE_LIMIT Requests per minute per client IP on /mcp (default: 60, 0 disables)\n`;\n\nfunction main(): void {\n let values: ReturnType<typeof parse>['values'];\n try {\n values = parse().values;\n } catch (error) {\n log(error instanceof Error ? error.message : String(error));\n console.error(HELP);\n process.exit(2);\n }\n\n if (values.help) {\n console.error(HELP);\n return;\n }\n if (values.version) {\n console.error(VERSION);\n return;\n }\n if (values.http) {\n startHttp(values.port, values.host);\n return;\n }\n\n const handle = serveStdio(() => createServer());\n const shutdown = () => void handle.close().finally(() => process.exit(0));\n process.on('SIGINT', shutdown);\n process.on('SIGTERM', shutdown);\n log(`v${VERSION} running on stdio (Kroki: ${process.env.KROKI_URL ?? 'https://kroki.io'})`);\n}\n\nfunction parse() {\n return parseArgs({\n options: {\n http: { type: 'boolean', default: false },\n port: { type: 'string' },\n host: { type: 'string' },\n version: { type: 'boolean', short: 'v' },\n help: { type: 'boolean', short: 'h' },\n },\n strict: true,\n });\n}\n\nfunction startHttp(portArg: string | undefined, hostArg: string | undefined): void {\n const port = Number(portArg ?? process.env.PORT ?? 3000);\n if (!Number.isInteger(port) || port < 1 || port > 65_535) {\n log(`Invalid port \"${portArg ?? process.env.PORT}\".`);\n process.exit(2);\n }\n const host = hostArg ?? process.env.HOST ?? '127.0.0.1';\n const allowedHosts = (process.env.ALLOWED_HOSTS ?? '')\n .split(',')\n .map((h) => h.trim())\n .filter(Boolean);\n const rateLimit = process.env.RATE_LIMIT === undefined ? undefined : Number(process.env.RATE_LIMIT);\n\n const { app, handler } = createHttpApp({ host, allowedHosts, ...(rateLimit === undefined ? {} : { rateLimit }) });\n const server = serve({ fetch: app.fetch, port, hostname: host }, () => {\n log(`v${VERSION} listening on http://${host.includes(':') ? `[${host}]` : host}:${port}/mcp`);\n if (!['127.0.0.1', 'localhost', '::1'].includes(host) && allowedHosts.length === 0) {\n log('Bound to a public interface without ALLOWED_HOSTS: Host header validation is off.');\n }\n });\n\n const shutdown = () => {\n void handler.close().finally(() => server.close(() => process.exit(0)));\n };\n process.on('SIGINT', shutdown);\n process.on('SIGTERM', shutdown);\n}\n\nmain();\n","import { createMcpHonoApp } from '@modelcontextprotocol/hono';\nimport { createMcpHandler, type McpHttpHandler } from '@modelcontextprotocol/server';\nimport type { Context, Hono, MiddlewareHandler } from 'hono';\nimport { type CreateServerOptions, createServer } from './server.js';\nimport { VERSION } from './version.js';\n\n// A 100 KB source can roughly double once JSON-escaped; leave room for the envelope.\nexport const MAX_REQUEST_BODY_BYTES = 512 * 1024;\n\nexport interface HttpAppOptions extends CreateServerOptions {\n /** Interface the server binds to; drives the SDK's DNS rebinding protection. */\n host?: string;\n /** Hostnames accepted in the Host header when binding to a public interface. */\n allowedHosts?: string[];\n /** Requests per client IP per window on /mcp. 0 disables the limit. */\n rateLimit?: number;\n rateLimitWindowMs?: number;\n}\n\nexport interface HttpApp {\n app: Hono;\n handler: McpHttpHandler;\n}\n\nexport function createHttpApp(options: HttpAppOptions = {}): HttpApp {\n const handler = createMcpHandler(() => createServer(options), { maxRequestBodySize: MAX_REQUEST_BODY_BYTES });\n\n const app = createMcpHonoApp({\n host: options.host ?? '127.0.0.1',\n ...(options.allowedHosts?.length ? { allowedHosts: options.allowedHosts } : {}),\n maxRequestBodySize: MAX_REQUEST_BODY_BYTES,\n });\n\n app.get('/health', (c) => c.json({ status: 'ok', name: 'archform', version: VERSION }));\n\n const limit = options.rateLimit ?? 60;\n if (limit > 0) app.use('/mcp', rateLimiter(limit, options.rateLimitWindowMs ?? 60_000));\n\n app.post('/mcp', (c: Context) => handler.fetch(c.req.raw, { parsedBody: c.get('parsedBody') }));\n // Stateless server: no SSE stream to resume and no session to delete.\n app.on(['GET', 'DELETE'], '/mcp', (c) => c.json({ error: 'Method not allowed. Use POST /mcp.' }, 405));\n\n return { app, handler };\n}\n\n/**\n * Fixed-window, in-memory rate limiter keyed by client IP. Good enough for a\n * single instance; put a shared limiter in front when running several.\n */\nfunction rateLimiter(limit: number, windowMs: number): MiddlewareHandler {\n const hits = new Map<string, { count: number; resetAt: number }>();\n return async (c, next) => {\n const now = Date.now();\n if (hits.size > 10_000) {\n for (const [key, entry] of hits) if (entry.resetAt <= now) hits.delete(key);\n }\n const key = clientIp(c);\n let entry = hits.get(key);\n if (!entry || entry.resetAt <= now) {\n entry = { count: 0, resetAt: now + windowMs };\n hits.set(key, entry);\n }\n entry.count++;\n c.header('RateLimit-Limit', String(limit));\n c.header('RateLimit-Remaining', String(Math.max(0, limit - entry.count)));\n if (entry.count > limit) {\n c.header('Retry-After', String(Math.ceil((entry.resetAt - now) / 1000)));\n return c.json({ error: 'Too many requests. Try again later.' }, 429);\n }\n await next();\n };\n}\n\nfunction clientIp(c: Context): string {\n // Behind a proxy (Fly, Render, Cloudflare) the first X-Forwarded-For hop is the client.\n const forwarded = c.req.header('x-forwarded-for')?.split(',')[0]?.trim();\n if (forwarded) return forwarded;\n const incoming = (c.env as { incoming?: { socket?: { remoteAddress?: string } } } | undefined)?.incoming;\n return incoming?.socket?.remoteAddress ?? 'unknown';\n}\n","import { McpServer } from '@modelcontextprotocol/server';\nimport { KrokiRenderer } from './renderers/kroki.js';\nimport type { Renderer } from './renderers/renderer.js';\nimport { registerListFormats } from './tools/list-formats.js';\nimport { registerRenderDiagram } from './tools/render-diagram.js';\nimport { registerValidateDiagram } from './tools/validate-diagram.js';\nimport { VERSION } from './version.js';\n\nexport interface CreateServerOptions {\n renderer?: Renderer;\n}\n\nexport function createServer(options: CreateServerOptions = {}): McpServer {\n const renderer = options.renderer ?? new KrokiRenderer();\n const server = new McpServer(\n { name: 'archform', title: 'Archform', version: VERSION },\n {\n instructions:\n 'Archform renders and validates diagrams as code. Call list_formats to see supported languages and examples, ' +\n 'validate_diagram to check syntax, and render_diagram to produce an image and a shareable link.',\n },\n );\n registerListFormats(server);\n registerRenderDiagram(server, renderer);\n registerValidateDiagram(server, renderer);\n return server;\n}\n","import { deflateSync } from 'node:zlib';\nimport type { OutputFormat } from '../formats.js';\nimport { parseKrokiError } from './kroki-errors.js';\nimport { DiagramSyntaxError, type Renderer, RenderServiceError } from './renderer.js';\n\nexport const DEFAULT_KROKI_URL = 'https://kroki.io';\nconst TIMEOUT_MS = 15_000;\n\nexport interface KrokiRendererOptions {\n /** Base URL of the Kroki server. Defaults to `KROKI_URL` or https://kroki.io. */\n baseUrl?: string;\n /** Request timeout in milliseconds. Defaults to 15 000. */\n timeoutMs?: number;\n}\n\n/** Deflate (zlib) then base64url, the encoding Kroki's GET API expects. */\nexport function encodeKrokiSource(source: string): string {\n return deflateSync(Buffer.from(source, 'utf8'), { level: 9 }).toString('base64url');\n}\n\nexport class KrokiRenderer implements Renderer {\n readonly baseUrl: string;\n private readonly timeoutMs: number;\n\n constructor(options: KrokiRendererOptions = {}) {\n const base = options.baseUrl ?? process.env.KROKI_URL ?? DEFAULT_KROKI_URL;\n this.baseUrl = base.replace(/\\/+$/, '');\n this.timeoutMs = options.timeoutMs ?? TIMEOUT_MS;\n }\n\n async render(format: string, source: string, output: OutputFormat): Promise<Buffer> {\n let res: Response;\n try {\n res = await fetch(`${this.baseUrl}/${format}/${output}`, {\n method: 'POST',\n headers: { 'Content-Type': 'text/plain; charset=utf-8', Accept: contentType(output) },\n body: source,\n signal: AbortSignal.timeout(this.timeoutMs),\n });\n } catch (error) {\n if (error instanceof DOMException && (error.name === 'TimeoutError' || error.name === 'AbortError')) {\n throw new RenderServiceError(\n `Kroki at ${this.baseUrl} did not respond within ${this.timeoutMs / 1000}s. Try again, or simplify the diagram.`,\n );\n }\n const reason = error instanceof Error ? (error.cause instanceof Error ? error.cause.message : error.message) : '';\n throw new RenderServiceError(`Could not reach Kroki at ${this.baseUrl}${reason ? `: ${reason}` : ''}.`);\n }\n\n if (res.ok) return Buffer.from(await res.arrayBuffer());\n\n const body = await res.text().catch(() => '');\n if (res.status === 400) {\n throw new DiagramSyntaxError([parseKrokiError(format, body)]);\n }\n throw new RenderServiceError(\n `Kroki at ${this.baseUrl} returned HTTP ${res.status}${\n res.status >= 500 ? ' (a server-side failure, not necessarily a problem with the diagram)' : ''\n }. Try again later or use a self-hosted Kroki via KROKI_URL.`,\n );\n }\n\n shareUrl(format: string, source: string): string {\n return `${this.baseUrl}/${format}/svg/${encodeKrokiSource(source)}`;\n }\n}\n\nfunction contentType(output: OutputFormat): string {\n return output === 'png' ? 'image/png' : 'image/svg+xml';\n}\n","import type { DiagramError } from './renderer.js';\n\n// Formats whose parser reports 0-based line numbers (PlantUML counts from @startuml as line 0).\nconst ZERO_BASED_LINES = new Set(['plantuml', 'c4plantuml']);\n\n// Patterns seen in Kroki 400 bodies, tried in order:\n// PlantUML: \"Syntax Error? (Assumed diagram type: sequence) (line: 4)\"\n// Graphviz: \"Error: <stdin>: syntax error in line 4 near '}'\"\n// D2: \"err: failed to compile -: -:2:4: maps must be terminated with }\"\n// Structurizr: \"Unexpected tokens (...) at line 3: u = persn \\\"User\\\".\"\n// Mermaid: \"Parse error on line 3:\"\n// JSON: \"... in JSON at position 1 (line 1 column 2)\"\nconst LINE_PATTERNS = [/\\(line:\\s*(\\d+)\\)/i, /-:(\\d+):\\d+:/, /\\bline\\s+(\\d+)/i];\n\n/**\n * Turns a Kroki error body into a diagram error the model can act on:\n * strips the \"Error 400:\" prefix, stack frames and process noise, and\n * extracts a 1-based line number when the message carries one.\n */\nexport function parseKrokiError(format: string, body: string): DiagramError {\n const lines = body\n .replace(/^\\s*Error \\d{3}:\\s*/, '')\n .split(/\\r?\\n/)\n .map((l) => l.trimEnd())\n .filter((l) => l.trim() !== '' && !/^\\s+at\\s/.test(l) && !/^\\(exit code \\d+\\)$/.test(l.trim()));\n\n // Kroki often repeats the first line verbatim after a JS stack trace; keep one copy.\n const unique = lines.filter((l, i) => lines.indexOf(l) === i);\n let message = unique.join('\\n').trim() || 'The diagram could not be rendered.';\n if (message.length > 2000) message = `${message.slice(0, 2000)}…`;\n\n for (const pattern of LINE_PATTERNS) {\n const match = pattern.exec(message);\n if (match?.[1]) {\n const raw = Number.parseInt(match[1], 10);\n const line = ZERO_BASED_LINES.has(format) ? raw + 1 : raw;\n return { line: Math.max(line, 1), message };\n }\n }\n return { message };\n}\n","import type { OutputFormat } from '../formats.js';\n\nexport interface DiagramError {\n line?: number;\n message: string;\n}\n\nexport interface Renderer {\n /**\n * Renders `source` in the given canonical format.\n * Rejects with `DiagramSyntaxError` when the diagram is invalid and with\n * `RenderServiceError` when the renderer itself fails.\n */\n render(format: string, source: string, output: OutputFormat): Promise<Buffer>;\n /** A URL that renders the diagram as SVG when opened in a browser, if the renderer has one. */\n shareUrl?(format: string, source: string): string;\n}\n\n/** The diagram source is invalid. The message comes from the parser. */\nexport class DiagramSyntaxError extends Error {\n override readonly name = 'DiagramSyntaxError';\n constructor(readonly errors: DiagramError[]) {\n super(errors.map(formatDiagramError).join('\\n'));\n }\n}\n\n/** The renderer could not be reached, timed out, or failed for reasons unrelated to the source. */\nexport class RenderServiceError extends Error {\n override readonly name = 'RenderServiceError';\n}\n\nexport function formatDiagramError(error: DiagramError): string {\n return error.line === undefined ? error.message : `Line ${error.line}: ${error.message}`;\n}\n","import type { McpServer } from '@modelcontextprotocol/server';\nimport * as z from 'zod/v4';\nimport { FORMATS } from '../formats.js';\nimport { READ_ONLY_ANNOTATIONS } from './shared.js';\n\nconst formatEntry = z.object({\n id: z.string(),\n aliases: z.array(z.string()),\n outputs: z.array(z.enum(['svg', 'png'])),\n description: z.string(),\n docsUrl: z.string(),\n example: z.string(),\n});\n\nexport function registerListFormats(server: McpServer): void {\n server.registerTool(\n 'list_formats',\n {\n title: 'List diagram formats',\n description:\n 'List the diagram languages this server can render (Mermaid, PlantUML, C4-PlantUML, Graphviz, D2, Structurizr, BPMN, Excalidraw), ' +\n \"with each one's aliases, supported outputs (svg, png), syntax docs link and a minimal valid example. \" +\n 'Call this before writing a diagram in a format you are unsure of, or to check whether PNG output is available.',\n outputSchema: z.object({ formats: z.array(formatEntry) }),\n annotations: READ_ONLY_ANNOTATIONS,\n },\n async () => {\n const formats = FORMATS.map((f) => ({ ...f, aliases: [...f.aliases], outputs: [...f.outputs] }));\n return {\n content: [{ type: 'text', text: JSON.stringify({ formats }, null, 2) }],\n structuredContent: { formats },\n };\n },\n );\n}\n","export type OutputFormat = 'svg' | 'png';\n\nexport interface DiagramFormat {\n /** Canonical id, also the Kroki diagram type. */\n id: string;\n aliases: readonly string[];\n /** Outputs Kroki can produce for this format. */\n outputs: readonly OutputFormat[];\n description: string;\n docsUrl: string;\n /** A minimal diagram that renders without errors. */\n example: string;\n}\n\n// Output support checked against https://kroki.io/#support.\nexport const FORMATS = [\n {\n id: 'mermaid',\n aliases: [],\n outputs: ['svg', 'png'],\n description: 'Flowcharts, sequence, class, state, ER, Gantt and other diagrams in Markdown-like syntax.',\n docsUrl: 'https://mermaid.js.org/intro/syntax-reference.html',\n example: 'flowchart LR\\n A[Client] --> B[API]\\n B --> C[(Database)]\\n',\n },\n {\n id: 'plantuml',\n aliases: [],\n outputs: ['svg', 'png'],\n description: 'UML diagrams (sequence, class, component, activity, state, deployment) and more.',\n docsUrl: 'https://plantuml.com/',\n example: '@startuml\\nAlice -> Bob: Request\\nBob --> Alice: Response\\n@enduml\\n',\n },\n {\n id: 'c4plantuml',\n aliases: [],\n outputs: ['svg', 'png'],\n description: 'C4 model architecture diagrams (context, container, component) on top of PlantUML.',\n docsUrl: 'https://github.com/plantuml-stdlib/C4-PlantUML',\n example:\n '@startuml\\n!include <C4/C4_Context>\\nPerson(user, \"User\")\\nSystem(app, \"Web App\")\\nRel(user, app, \"Uses\")\\n@enduml\\n',\n },\n {\n id: 'graphviz',\n aliases: ['dot'],\n outputs: ['svg', 'png'],\n description: 'Graphs and networks in the Graphviz DOT language.',\n docsUrl: 'https://graphviz.org/doc/info/lang.html',\n example: 'digraph G {\\n rankdir=LR;\\n client -> api -> db;\\n}\\n',\n },\n {\n id: 'd2',\n aliases: [],\n outputs: ['svg'],\n description: 'Declarative diagrams with automatic layout, well suited to software architecture.',\n docsUrl: 'https://d2lang.com/tour/intro',\n example: 'client -> api: HTTPS\\napi -> db: SQL\\ndb: {shape: cylinder}\\n',\n },\n {\n id: 'structurizr',\n aliases: [],\n outputs: ['svg', 'png'],\n description: 'C4 model workspaces in the Structurizr DSL (model plus views).',\n docsUrl: 'https://docs.structurizr.com/dsl/language',\n example:\n 'workspace {\\n model {\\n user = person \"User\"\\n app = softwareSystem \"App\"\\n user -> app \"Uses\"\\n }\\n views {\\n systemContext app {\\n include *\\n autolayout lr\\n }\\n }\\n}\\n',\n },\n {\n id: 'bpmn',\n aliases: [],\n outputs: ['svg'],\n description: 'Business process diagrams in BPMN 2.0 XML (including diagram interchange coordinates).',\n docsUrl: 'https://www.omg.org/spec/BPMN/2.0/',\n example:\n '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n<bpmn:definitions xmlns:bpmn=\"http://www.omg.org/spec/BPMN/20100524/MODEL\" xmlns:bpmndi=\"http://www.omg.org/spec/BPMN/20100524/DI\" xmlns:dc=\"http://www.omg.org/spec/DD/20100524/DC\" xmlns:di=\"http://www.omg.org/spec/DD/20100524/DI\" id=\"defs\" targetNamespace=\"http://bpmn.io/schema/bpmn\">\\n <bpmn:process id=\"process\" isExecutable=\"false\">\\n <bpmn:startEvent id=\"start\"><bpmn:outgoing>flow</bpmn:outgoing></bpmn:startEvent>\\n <bpmn:endEvent id=\"end\"><bpmn:incoming>flow</bpmn:incoming></bpmn:endEvent>\\n <bpmn:sequenceFlow id=\"flow\" sourceRef=\"start\" targetRef=\"end\" />\\n </bpmn:process>\\n <bpmndi:BPMNDiagram id=\"diagram\">\\n <bpmndi:BPMNPlane id=\"plane\" bpmnElement=\"process\">\\n <bpmndi:BPMNShape id=\"start_di\" bpmnElement=\"start\"><dc:Bounds x=\"100\" y=\"100\" width=\"36\" height=\"36\" /></bpmndi:BPMNShape>\\n <bpmndi:BPMNShape id=\"end_di\" bpmnElement=\"end\"><dc:Bounds x=\"250\" y=\"100\" width=\"36\" height=\"36\" /></bpmndi:BPMNShape>\\n <bpmndi:BPMNEdge id=\"flow_di\" bpmnElement=\"flow\"><di:waypoint x=\"136\" y=\"118\" /><di:waypoint x=\"250\" y=\"118\" /></bpmndi:BPMNEdge>\\n </bpmndi:BPMNPlane>\\n </bpmndi:BPMNDiagram>\\n</bpmn:definitions>\\n',\n },\n {\n id: 'excalidraw',\n aliases: [],\n outputs: ['svg'],\n description: 'Hand-drawn style sketches from an Excalidraw JSON scene.',\n docsUrl: 'https://docs.excalidraw.com/docs/codebase/json-schema',\n example:\n '{\\n \"type\": \"excalidraw\",\\n \"version\": 2,\\n \"source\": \"https://excalidraw.com\",\\n \"elements\": [\\n {\\n \"id\": \"box\",\\n \"type\": \"rectangle\",\\n \"x\": 0,\\n \"y\": 0,\\n \"width\": 160,\\n \"height\": 80,\\n \"angle\": 0,\\n \"strokeColor\": \"#1e1e1e\",\\n \"backgroundColor\": \"transparent\",\\n \"fillStyle\": \"solid\",\\n \"strokeWidth\": 2,\\n \"strokeStyle\": \"solid\",\\n \"roughness\": 1,\\n \"opacity\": 100,\\n \"groupIds\": [],\\n \"seed\": 1,\\n \"version\": 1,\\n \"versionNonce\": 1,\\n \"isDeleted\": false,\\n \"boundElements\": null,\\n \"updated\": 1,\\n \"link\": null,\\n \"locked\": false\\n }\\n ],\\n \"appState\": { \"viewBackgroundColor\": \"#ffffff\" },\\n \"files\": {}\\n}\\n',\n },\n] as const satisfies readonly DiagramFormat[];\n\nexport type FormatId = (typeof FORMATS)[number]['id'];\n\n/** Every accepted `format` value: canonical ids followed by aliases. */\nexport const FORMAT_NAMES = [...FORMATS.map((f) => f.id), ...FORMATS.flatMap((f) => f.aliases)] as [\n string,\n ...string[],\n];\n\nconst byName = new Map<string, DiagramFormat>();\nfor (const format of FORMATS) {\n byName.set(format.id, format);\n for (const alias of format.aliases) byName.set(alias, format);\n}\n\n/** Resolves an id or alias (case-insensitive) to its format, or undefined. */\nexport function resolveFormat(name: string): DiagramFormat | undefined {\n return byName.get(name.trim().toLowerCase());\n}\n","import type { CallToolResult, ToolAnnotations } from '@modelcontextprotocol/server';\nimport * as z from 'zod/v4';\nimport { FORMAT_NAMES } from '../formats.js';\n\nexport const MAX_SOURCE_BYTES = 100 * 1024;\n\n/** Every tool only reads input and calls an external renderer. */\nexport const READ_ONLY_ANNOTATIONS: ToolAnnotations = { readOnlyHint: true, openWorldHint: true };\n\nexport const formatSchema = z\n .enum(FORMAT_NAMES)\n .describe(`Diagram language. One of: ${FORMAT_NAMES.join(', ')} (\"dot\" is an alias for graphviz).`);\n\nexport const sourceSchema = z\n .string()\n .min(1, 'source is empty')\n .refine((s) => Buffer.byteLength(s, 'utf8') <= MAX_SOURCE_BYTES, {\n message: `source must be at most ${MAX_SOURCE_BYTES / 1024} KB`,\n })\n .describe('The diagram source text, exactly as it would appear in a file of that format.');\n\nexport function toolError(text: string): CallToolResult {\n return { content: [{ type: 'text', text }], isError: true };\n}\n","import { createHash } from 'node:crypto';\nimport type { CallToolResult, McpServer } from '@modelcontextprotocol/server';\nimport * as z from 'zod/v4';\nimport { resolveFormat } from '../formats.js';\nimport { DiagramSyntaxError, type Renderer, RenderServiceError } from '../renderers/renderer.js';\nimport { formatSchema, READ_ONLY_ANNOTATIONS, sourceSchema, toolError } from './shared.js';\n\nexport function registerRenderDiagram(server: McpServer, renderer: Renderer): void {\n server.registerTool(\n 'render_diagram',\n {\n title: 'Render diagram',\n description:\n 'Render diagram source (Mermaid, PlantUML, C4-PlantUML, Graphviz/DOT, D2, Structurizr, BPMN, Excalidraw) to an SVG or PNG image, ' +\n 'and get a shareable link that opens the diagram in a browser. Use it when the user wants to see a diagram. ' +\n 'If the source is invalid the result is an error with the parser message and line number: fix the source and retry. ' +\n 'Call validate_diagram to check syntax without rendering, or list_formats for examples and which formats support PNG.',\n inputSchema: z.object({\n format: formatSchema,\n source: sourceSchema,\n output: z\n .enum(['svg', 'png'])\n .default('svg')\n .describe('Image type. Defaults to svg. d2, bpmn and excalidraw support svg only.'),\n }),\n annotations: READ_ONLY_ANNOTATIONS,\n },\n async ({ format: name, source, output }): Promise<CallToolResult> => {\n const format = resolveFormat(name);\n if (!format) return toolError(`Unknown format \"${name}\". Call list_formats to see the supported formats.`);\n if (!format.outputs.includes(output)) {\n return toolError(\n `${format.id} does not support ${output} output. Supported: ${format.outputs.join(', ')}. Retry with output \"svg\".`,\n );\n }\n\n let image: Buffer;\n try {\n image = await renderer.render(format.id, source, output);\n } catch (error) {\n if (error instanceof DiagramSyntaxError) {\n return toolError(\n `The ${format.id} diagram is invalid:\\n${error.message}\\n\\nFix the source and call render_diagram again. ` +\n `list_formats has a valid ${format.id} example.`,\n );\n }\n if (error instanceof RenderServiceError) return toolError(error.message);\n throw error;\n }\n\n const url = renderer.shareUrl?.(format.id, source);\n const content: CallToolResult['content'] = [];\n if (output === 'png') {\n content.push({ type: 'image', data: image.toString('base64'), mimeType: 'image/png' });\n } else {\n const hash = createHash('sha256').update(source).digest('hex').slice(0, 16);\n content.push({\n type: 'resource',\n resource: {\n uri: `archform://diagram/${format.id}/${hash}.svg`,\n mimeType: 'image/svg+xml',\n text: image.toString('utf8'),\n },\n });\n }\n content.push({\n type: 'text',\n text: url\n ? `Rendered ${format.id} diagram as ${output}. Shareable link (opens the SVG in a browser): ${url}`\n : `Rendered ${format.id} diagram as ${output}.`,\n });\n return { content };\n },\n );\n}\n","import type { CallToolResult, McpServer } from '@modelcontextprotocol/server';\nimport * as z from 'zod/v4';\nimport { resolveFormat } from '../formats.js';\nimport { type DiagramError, DiagramSyntaxError, type Renderer, RenderServiceError } from '../renderers/renderer.js';\nimport { formatSchema, READ_ONLY_ANNOTATIONS, sourceSchema, toolError } from './shared.js';\n\nconst outputSchema = z.object({\n valid: z.boolean(),\n errors: z.array(z.object({ line: z.number().int().optional(), message: z.string() })),\n});\n\nexport function registerValidateDiagram(server: McpServer, renderer: Renderer): void {\n server.registerTool(\n 'validate_diagram',\n {\n title: 'Validate diagram',\n description:\n 'Check whether diagram source (Mermaid, PlantUML, C4-PlantUML, Graphviz/DOT, D2, Structurizr, BPMN, Excalidraw) is valid, ' +\n 'without returning an image. Returns { valid, errors: [{ line?, message }] }. ' +\n 'Use it to check syntax you are unsure of before rendering or before handing diagram code to the user. ' +\n 'Call list_formats for a valid example of each format.',\n inputSchema: z.object({ format: formatSchema, source: sourceSchema }),\n outputSchema,\n annotations: READ_ONLY_ANNOTATIONS,\n },\n async ({ format: name, source }): Promise<CallToolResult> => {\n const format = resolveFormat(name);\n if (!format) return toolError(`Unknown format \"${name}\". Call list_formats to see the supported formats.`);\n\n let errors: DiagramError[] = [];\n try {\n // Every format supports SVG, and rendering is the only check Kroki offers.\n await renderer.render(format.id, source, 'svg');\n } catch (error) {\n if (error instanceof DiagramSyntaxError) errors = error.errors;\n else if (error instanceof RenderServiceError) {\n return toolError(`Could not validate the diagram: ${error.message}`);\n } else throw error;\n }\n\n const result = { valid: errors.length === 0, errors };\n return {\n content: [{ type: 'text', text: JSON.stringify(result) }],\n structuredContent: result,\n };\n },\n );\n}\n","{\n \"name\": \"@archform/mcp\",\n \"version\": \"0.1.0\",\n \"description\": \"MCP server that renders and validates diagrams as code: Mermaid, PlantUML, C4, Graphviz, D2, Structurizr, BPMN and Excalidraw.\",\n \"mcpName\": \"io.github.himanshu-mamgain/archform\",\n \"license\": \"MIT\",\n \"author\": \"Himanshu Mamgain\",\n \"homepage\": \"https://github.com/himanshu-mamgain/archform/tree/main/packages/mcp#readme\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/himanshu-mamgain/archform.git\",\n \"directory\": \"packages/mcp\"\n },\n \"bugs\": \"https://github.com/himanshu-mamgain/archform/issues\",\n \"type\": \"module\",\n \"bin\": {\n \"archform-mcp\": \"dist/index.js\"\n },\n \"files\": [\n \"dist\"\n ],\n \"engines\": {\n \"node\": \">=22\"\n },\n \"publishConfig\": {\n \"access\": \"public\"\n },\n \"keywords\": [\n \"mcp\",\n \"model-context-protocol\",\n \"diagrams-as-code\",\n \"mermaid\",\n \"plantuml\",\n \"graphviz\",\n \"d2\",\n \"c4\",\n \"architecture\"\n ],\n \"scripts\": {\n \"build\": \"tsup\",\n \"dev\": \"tsup --watch\",\n \"start\": \"node dist/index.js\",\n \"typecheck\": \"tsc -p tsconfig.json\",\n \"test\": \"vitest run\",\n \"test:live\": \"cross-env KROKI_LIVE=1 vitest run test/live.test.ts\",\n \"prepublishOnly\": \"tsup\"\n },\n \"devDependencies\": {\n \"@modelcontextprotocol/client\": \"2.3.1\",\n \"@types/node\": \"22.20.5\",\n \"cross-env\": \"^10.1.0\",\n \"tsup\": \"8.5.1\",\n \"vitest\": \"4.1.12\"\n },\n \"dependencies\": {\n \"@hono/node-server\": \"2.1.5\",\n \"@modelcontextprotocol/hono\": \"2.0.2\",\n \"@modelcontextprotocol/server\": \"2.3.1\",\n \"hono\": \"4.13.13\",\n \"zod\": \"4.6.7\"\n }\n}\n","import pkg from '../package.json' with { type: 'json' };\n\nexport const VERSION: string = pkg.version;\n"],"mappings":"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","names":["z","z","z","key","entry"]}
package/package.json CHANGED
@@ -1,6 +1,62 @@
1
1
  {
2
2
  "name": "@archform/mcp",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "MCP server that renders and validates diagrams as code: Mermaid, PlantUML, C4, Graphviz, D2, Structurizr, BPMN and Excalidraw.",
5
+ "mcpName": "io.github.himanshu-mamgain/archform",
6
+ "license": "MIT",
7
+ "author": "Himanshu Mamgain",
8
+ "homepage": "https://github.com/himanshu-mamgain/archform/tree/main/packages/mcp#readme",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/himanshu-mamgain/archform.git",
12
+ "directory": "packages/mcp"
13
+ },
14
+ "bugs": "https://github.com/himanshu-mamgain/archform/issues",
15
+ "type": "module",
16
+ "bin": {
17
+ "archform-mcp": "dist/index.js"
18
+ },
19
+ "files": [
20
+ "dist"
21
+ ],
22
+ "engines": {
23
+ "node": ">=22"
24
+ },
25
+ "publishConfig": {
26
+ "access": "public"
27
+ },
28
+ "keywords": [
29
+ "mcp",
30
+ "model-context-protocol",
31
+ "diagrams-as-code",
32
+ "mermaid",
33
+ "plantuml",
34
+ "graphviz",
35
+ "d2",
36
+ "c4",
37
+ "architecture"
38
+ ],
39
+ "scripts": {
40
+ "build": "tsup",
41
+ "dev": "tsup --watch",
42
+ "start": "node dist/index.js",
43
+ "typecheck": "tsc -p tsconfig.json",
44
+ "test": "vitest run",
45
+ "test:live": "cross-env KROKI_LIVE=1 vitest run test/live.test.ts",
46
+ "prepublishOnly": "tsup"
47
+ },
48
+ "devDependencies": {
49
+ "@modelcontextprotocol/client": "2.3.1",
50
+ "@types/node": "22.20.5",
51
+ "cross-env": "^10.1.0",
52
+ "tsup": "8.5.1",
53
+ "vitest": "4.1.12"
54
+ },
55
+ "dependencies": {
56
+ "@hono/node-server": "2.1.5",
57
+ "@modelcontextprotocol/hono": "2.0.2",
58
+ "@modelcontextprotocol/server": "2.3.1",
59
+ "hono": "4.13.13",
60
+ "zod": "4.6.7"
61
+ }
62
+ }