@archform/mcp 0.1.0 → 0.2.1
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 +21 -21
- package/README.md +15 -3
- package/dist/index.js +144 -44
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/LICENSE
CHANGED
|
@@ -1,21 +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.
|
|
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
|
@@ -86,12 +86,22 @@ npx -y @archform/mcp --http --port 3000
|
|
|
86
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
87
|
|
|
88
88
|
```sh
|
|
89
|
-
HOST=0.0.0.0 ALLOWED_HOSTS=mcp.example.com PORT=8080 npx -y @archform/mcp --http
|
|
89
|
+
HOST=0.0.0.0 ALLOWED_HOSTS=mcp.example.com TRUST_PROXY=1 RATE_LIMIT_PER_CLIENT=0 PORT=8080 npx -y @archform/mcp --http
|
|
90
90
|
```
|
|
91
91
|
|
|
92
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
93
|
|
|
94
|
-
|
|
94
|
+
### Limits
|
|
95
|
+
|
|
96
|
+
The HTTP endpoint limits request bodies to 512 KB and counts **tool calls**, not HTTP requests. A session's `initialize` and `tools/list` requests are not counted.
|
|
97
|
+
|
|
98
|
+
- **Overall:** `RATE_LIMIT` (default 120 per minute) caps tool calls from all clients combined. Size it to what your Kroki backend can handle. The default is polite to the public kroki.io; raise it for a self-hosted Kroki.
|
|
99
|
+
- **Per client:** `RATE_LIMIT_PER_CLIENT` (default 60 per minute) stops one client from using the whole budget. ChatGPT calls your server from OpenAI's servers, so **all ChatGPT users share a handful of IPs**, and this limit applies to them together. On a deployment that mainly serves ChatGPT, set `RATE_LIMIT_PER_CLIENT=0` (as in the example above) or make it equal to `RATE_LIMIT`, so ChatGPT users can use the whole overall budget.
|
|
100
|
+
- **Behind a proxy:** most hosts (Render, Fly.io, Railway, Cloud Run) put a proxy in front of your app, so every request seems to come from the proxy's address. Set `TRUST_PROXY=1` (the number of proxies in front of the server) so the limiter uses the address your proxy added to `X-Forwarded-For`. Leave it unset when clients connect directly. Clients can write anything into that header, so it is ignored unless `TRUST_PROXY` is set.
|
|
101
|
+
|
|
102
|
+
When a single tool call is over a limit, the model gets a normal tool result with `isError: true` and a message like "Rate limit reached: … Retry in 42s.", so it can tell the user to wait. An HTTP 429 would show up only as a generic connector failure. Batched or malformed requests over the limit get `429` with `Retry-After`.
|
|
103
|
+
|
|
104
|
+
The counters are in memory, so each instance enforces its own limits.
|
|
95
105
|
|
|
96
106
|
## Configuration
|
|
97
107
|
|
|
@@ -101,7 +111,9 @@ The HTTP endpoint limits request bodies to 512 KB and rate-limits each client IP
|
|
|
101
111
|
| `--port`, `PORT` | `3000` | HTTP port |
|
|
102
112
|
| `--host`, `HOST` | `127.0.0.1` | HTTP bind address |
|
|
103
113
|
| `ALLOWED_HOSTS` | | Comma-separated `Host` values accepted on a public bind |
|
|
104
|
-
| `RATE_LIMIT` | `
|
|
114
|
+
| `RATE_LIMIT` | `120` | Tool calls per minute across all clients; `0` disables |
|
|
115
|
+
| `RATE_LIMIT_PER_CLIENT` | `60` | Tool calls per minute per client IP; `0` disables |
|
|
116
|
+
| `TRUST_PROXY` | `0` | Number of reverse proxies whose `X-Forwarded-For` entries are trusted (`true` = 1) |
|
|
105
117
|
| `KROKI_URL` | `https://kroki.io` | Kroki server used for rendering and share links |
|
|
106
118
|
|
|
107
119
|
## Self-hosting Kroki
|
package/dist/index.js
CHANGED
|
@@ -9,6 +9,91 @@ import { serveStdio } from "@modelcontextprotocol/server/stdio";
|
|
|
9
9
|
import { createMcpHonoApp } from "@modelcontextprotocol/hono";
|
|
10
10
|
import { createMcpHandler } from "@modelcontextprotocol/server";
|
|
11
11
|
|
|
12
|
+
// src/rate-limit.ts
|
|
13
|
+
function createToolCallLimiter(options) {
|
|
14
|
+
const windowMs = options.windowMs ?? 6e4;
|
|
15
|
+
const trustProxy = options.trustProxy ?? 0;
|
|
16
|
+
const maxClients = options.maxClients ?? 1e4;
|
|
17
|
+
const global = { count: 0, resetAt: 0 };
|
|
18
|
+
const clients = /* @__PURE__ */ new Map();
|
|
19
|
+
const clientWindow = (key, now) => {
|
|
20
|
+
let entry = clients.get(key);
|
|
21
|
+
if (entry && entry.resetAt > now) return entry;
|
|
22
|
+
if (entry) clients.delete(key);
|
|
23
|
+
if (clients.size >= maxClients) {
|
|
24
|
+
for (const [k, w] of clients) if (w.resetAt <= now) clients.delete(k);
|
|
25
|
+
while (clients.size >= maxClients) {
|
|
26
|
+
const oldest = clients.keys().next().value;
|
|
27
|
+
if (oldest === void 0) break;
|
|
28
|
+
clients.delete(oldest);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
entry = { count: 0, resetAt: now + windowMs };
|
|
32
|
+
clients.set(key, entry);
|
|
33
|
+
return entry;
|
|
34
|
+
};
|
|
35
|
+
const middleware = async (c, next) => {
|
|
36
|
+
const calls = countToolCalls(c);
|
|
37
|
+
if (calls === 0) return next();
|
|
38
|
+
const now = Date.now();
|
|
39
|
+
if (global.resetAt <= now) {
|
|
40
|
+
global.count = 0;
|
|
41
|
+
global.resetAt = now + windowMs;
|
|
42
|
+
}
|
|
43
|
+
const client = options.perClient > 0 ? clientWindow(clientIp(c, trustProxy), now) : void 0;
|
|
44
|
+
if (client && client.count + calls > options.perClient) {
|
|
45
|
+
return tooManyRequests(c, next, client.resetAt - now, "Too many tool calls from this client.");
|
|
46
|
+
}
|
|
47
|
+
if (options.global > 0 && global.count + calls > options.global) {
|
|
48
|
+
return tooManyRequests(c, next, global.resetAt - now, "The server is at capacity.");
|
|
49
|
+
}
|
|
50
|
+
if (client) client.count += calls;
|
|
51
|
+
global.count += calls;
|
|
52
|
+
return next();
|
|
53
|
+
};
|
|
54
|
+
return {
|
|
55
|
+
middleware,
|
|
56
|
+
get trackedClients() {
|
|
57
|
+
return clients.size;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function countToolCalls(c) {
|
|
62
|
+
if (c.req.method !== "POST") return 0;
|
|
63
|
+
const body = c.get("parsedBody");
|
|
64
|
+
if (body === void 0) return 1;
|
|
65
|
+
const messages = Array.isArray(body) ? body : [body];
|
|
66
|
+
return messages.filter(
|
|
67
|
+
(m) => typeof m === "object" && m !== null && m.method === "tools/call"
|
|
68
|
+
).length;
|
|
69
|
+
}
|
|
70
|
+
function clientIp(c, trustProxy) {
|
|
71
|
+
if (trustProxy > 0) {
|
|
72
|
+
const hops = (c.req.header("x-forwarded-for") ?? "").split(",").map((h) => h.trim()).filter(Boolean);
|
|
73
|
+
const ip = hops[Math.max(0, hops.length - trustProxy)];
|
|
74
|
+
if (ip) return ip;
|
|
75
|
+
}
|
|
76
|
+
const incoming = c.env?.incoming;
|
|
77
|
+
return incoming?.socket?.remoteAddress ?? "unknown";
|
|
78
|
+
}
|
|
79
|
+
var RATE_LIMITED = "archformRateLimited";
|
|
80
|
+
async function tooManyRequests(c, next, retryMs, message) {
|
|
81
|
+
const seconds = Math.max(1, Math.ceil(retryMs / 1e3));
|
|
82
|
+
const text = `Rate limit reached: ${message} Retry in ${seconds}s.`;
|
|
83
|
+
if (isSingleToolCall(c.get("parsedBody"))) {
|
|
84
|
+
c.set(RATE_LIMITED, text);
|
|
85
|
+
await next();
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
c.header("Retry-After", String(seconds));
|
|
89
|
+
return c.json({ error: text }, 429);
|
|
90
|
+
}
|
|
91
|
+
function isSingleToolCall(body) {
|
|
92
|
+
if (typeof body !== "object" || body === null || Array.isArray(body)) return false;
|
|
93
|
+
const { method, id } = body;
|
|
94
|
+
return method === "tools/call" && (typeof id === "string" || typeof id === "number");
|
|
95
|
+
}
|
|
96
|
+
|
|
12
97
|
// src/server.ts
|
|
13
98
|
import { McpServer } from "@modelcontextprotocol/server";
|
|
14
99
|
|
|
@@ -200,7 +285,7 @@ var formatEntry = z2.object({
|
|
|
200
285
|
docsUrl: z2.string(),
|
|
201
286
|
example: z2.string()
|
|
202
287
|
});
|
|
203
|
-
function registerListFormats(server) {
|
|
288
|
+
function registerListFormats(server, unavailable) {
|
|
204
289
|
server.registerTool(
|
|
205
290
|
"list_formats",
|
|
206
291
|
{
|
|
@@ -210,6 +295,7 @@ function registerListFormats(server) {
|
|
|
210
295
|
annotations: READ_ONLY_ANNOTATIONS
|
|
211
296
|
},
|
|
212
297
|
async () => {
|
|
298
|
+
if (unavailable) return toolError(unavailable);
|
|
213
299
|
const formats = FORMATS.map((f) => ({ ...f, aliases: [...f.aliases], outputs: [...f.outputs] }));
|
|
214
300
|
return {
|
|
215
301
|
content: [{ type: "text", text: JSON.stringify({ formats }, null, 2) }],
|
|
@@ -222,7 +308,7 @@ function registerListFormats(server) {
|
|
|
222
308
|
// src/tools/render-diagram.ts
|
|
223
309
|
import { createHash } from "crypto";
|
|
224
310
|
import * as z3 from "zod/v4";
|
|
225
|
-
function registerRenderDiagram(server, renderer) {
|
|
311
|
+
function registerRenderDiagram(server, renderer, unavailable) {
|
|
226
312
|
server.registerTool(
|
|
227
313
|
"render_diagram",
|
|
228
314
|
{
|
|
@@ -236,6 +322,7 @@ function registerRenderDiagram(server, renderer) {
|
|
|
236
322
|
annotations: READ_ONLY_ANNOTATIONS
|
|
237
323
|
},
|
|
238
324
|
async ({ format: name, source, output }) => {
|
|
325
|
+
if (unavailable) return toolError(unavailable);
|
|
239
326
|
const format = resolveFormat(name);
|
|
240
327
|
if (!format) return toolError(`Unknown format "${name}". Call list_formats to see the supported formats.`);
|
|
241
328
|
if (!format.outputs.includes(output)) {
|
|
@@ -288,7 +375,7 @@ var outputSchema = z4.object({
|
|
|
288
375
|
valid: z4.boolean(),
|
|
289
376
|
errors: z4.array(z4.object({ line: z4.number().int().optional(), message: z4.string() }))
|
|
290
377
|
});
|
|
291
|
-
function registerValidateDiagram(server, renderer) {
|
|
378
|
+
function registerValidateDiagram(server, renderer, unavailable) {
|
|
292
379
|
server.registerTool(
|
|
293
380
|
"validate_diagram",
|
|
294
381
|
{
|
|
@@ -299,6 +386,7 @@ function registerValidateDiagram(server, renderer) {
|
|
|
299
386
|
annotations: READ_ONLY_ANNOTATIONS
|
|
300
387
|
},
|
|
301
388
|
async ({ format: name, source }) => {
|
|
389
|
+
if (unavailable) return toolError(unavailable);
|
|
302
390
|
const format = resolveFormat(name);
|
|
303
391
|
if (!format) return toolError(`Unknown format "${name}". Call list_formats to see the supported formats.`);
|
|
304
392
|
let errors = [];
|
|
@@ -322,7 +410,7 @@ function registerValidateDiagram(server, renderer) {
|
|
|
322
410
|
// package.json
|
|
323
411
|
var package_default = {
|
|
324
412
|
name: "@archform/mcp",
|
|
325
|
-
version: "0.1
|
|
413
|
+
version: "0.2.1",
|
|
326
414
|
description: "MCP server that renders and validates diagrams as code: Mermaid, PlantUML, C4, Graphviz, D2, Structurizr, BPMN and Excalidraw.",
|
|
327
415
|
mcpName: "io.github.himanshu-mamgain/archform",
|
|
328
416
|
license: "MIT",
|
|
@@ -395,56 +483,49 @@ function createServer(options = {}) {
|
|
|
395
483
|
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
484
|
}
|
|
397
485
|
);
|
|
398
|
-
registerListFormats(server);
|
|
399
|
-
registerRenderDiagram(server, renderer);
|
|
400
|
-
registerValidateDiagram(server, renderer);
|
|
486
|
+
registerListFormats(server, options.unavailable);
|
|
487
|
+
registerRenderDiagram(server, renderer, options.unavailable);
|
|
488
|
+
registerValidateDiagram(server, renderer, options.unavailable);
|
|
401
489
|
return server;
|
|
402
490
|
}
|
|
403
491
|
|
|
404
492
|
// src/http.ts
|
|
405
493
|
var MAX_REQUEST_BODY_BYTES = 512 * 1024;
|
|
494
|
+
var RATE_LIMIT_HEADER = "x-archform-rate-limited";
|
|
495
|
+
var DEFAULT_RATE_LIMIT = { global: 120, perClient: 60 };
|
|
406
496
|
function createHttpApp(options = {}) {
|
|
407
|
-
const handler = createMcpHandler(
|
|
497
|
+
const handler = createMcpHandler(
|
|
498
|
+
({ requestInfo }) => {
|
|
499
|
+
const limited = requestInfo?.headers.get(RATE_LIMIT_HEADER);
|
|
500
|
+
return createServer(limited ? { ...options, unavailable: decodeURIComponent(limited) } : options);
|
|
501
|
+
},
|
|
502
|
+
{ maxRequestBodySize: MAX_REQUEST_BODY_BYTES }
|
|
503
|
+
);
|
|
408
504
|
const app = createMcpHonoApp({
|
|
409
505
|
host: options.host ?? "127.0.0.1",
|
|
410
506
|
...options.allowedHosts?.length ? { allowedHosts: options.allowedHosts } : {},
|
|
411
507
|
maxRequestBodySize: MAX_REQUEST_BODY_BYTES
|
|
412
508
|
});
|
|
413
509
|
app.get("/health", (c) => c.json({ status: "ok", name: "archform", version: VERSION }));
|
|
414
|
-
const
|
|
415
|
-
|
|
416
|
-
app.post("/mcp", (c) =>
|
|
510
|
+
const limiter = createToolCallLimiter({ ...DEFAULT_RATE_LIMIT, ...options.rateLimit });
|
|
511
|
+
app.use("/mcp", limiter.middleware);
|
|
512
|
+
app.post("/mcp", (c) => {
|
|
513
|
+
const parsedBody = c.get("parsedBody");
|
|
514
|
+
const limited = c.get(RATE_LIMITED);
|
|
515
|
+
if (!limited && !c.req.raw.headers.has(RATE_LIMIT_HEADER)) return handler.fetch(c.req.raw, { parsedBody });
|
|
516
|
+
const headers = new Headers(c.req.raw.headers);
|
|
517
|
+
headers.delete(RATE_LIMIT_HEADER);
|
|
518
|
+
if (limited) headers.set(RATE_LIMIT_HEADER, encodeURIComponent(limited));
|
|
519
|
+
const request = new Request(c.req.url, {
|
|
520
|
+
method: "POST",
|
|
521
|
+
headers,
|
|
522
|
+
body: parsedBody === void 0 ? c.req.raw.body : JSON.stringify(parsedBody),
|
|
523
|
+
...parsedBody === void 0 ? { duplex: "half" } : {}
|
|
524
|
+
});
|
|
525
|
+
return handler.fetch(request, { parsedBody });
|
|
526
|
+
});
|
|
417
527
|
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";
|
|
528
|
+
return { app, handler, limiter };
|
|
448
529
|
}
|
|
449
530
|
|
|
450
531
|
// src/index.ts
|
|
@@ -465,7 +546,10 @@ Options:
|
|
|
465
546
|
Environment:
|
|
466
547
|
KROKI_URL Kroki server (default: https://kroki.io)
|
|
467
548
|
ALLOWED_HOSTS Comma-separated Host header values accepted when binding publicly
|
|
468
|
-
RATE_LIMIT
|
|
549
|
+
RATE_LIMIT Tool calls per minute across all clients (default: 120, 0 disables)
|
|
550
|
+
RATE_LIMIT_PER_CLIENT Tool calls per minute per client IP (default: 60, 0 disables)
|
|
551
|
+
TRUST_PROXY Reverse proxies in front of the server whose X-Forwarded-For is trusted
|
|
552
|
+
(default: 0 = ignore the header; "true" = 1)
|
|
469
553
|
`;
|
|
470
554
|
function main() {
|
|
471
555
|
let values;
|
|
@@ -514,8 +598,12 @@ function startHttp(portArg, hostArg) {
|
|
|
514
598
|
}
|
|
515
599
|
const host = hostArg ?? process.env.HOST ?? "127.0.0.1";
|
|
516
600
|
const allowedHosts = (process.env.ALLOWED_HOSTS ?? "").split(",").map((h) => h.trim()).filter(Boolean);
|
|
517
|
-
const rateLimit =
|
|
518
|
-
|
|
601
|
+
const rateLimit = {
|
|
602
|
+
global: envCount("RATE_LIMIT", DEFAULT_RATE_LIMIT.global),
|
|
603
|
+
perClient: envCount("RATE_LIMIT_PER_CLIENT", DEFAULT_RATE_LIMIT.perClient),
|
|
604
|
+
trustProxy: envCount("TRUST_PROXY", 0)
|
|
605
|
+
};
|
|
606
|
+
const { app, handler } = createHttpApp({ host, allowedHosts, rateLimit });
|
|
519
607
|
const server = serve({ fetch: app.fetch, port, hostname: host }, () => {
|
|
520
608
|
log(`v${VERSION} listening on http://${host.includes(":") ? `[${host}]` : host}:${port}/mcp`);
|
|
521
609
|
if (!["127.0.0.1", "localhost", "::1"].includes(host) && allowedHosts.length === 0) {
|
|
@@ -528,5 +616,17 @@ function startHttp(portArg, hostArg) {
|
|
|
528
616
|
process.on("SIGINT", shutdown);
|
|
529
617
|
process.on("SIGTERM", shutdown);
|
|
530
618
|
}
|
|
619
|
+
function envCount(name, fallback) {
|
|
620
|
+
const raw = process.env[name]?.trim().toLowerCase();
|
|
621
|
+
if (!raw) return fallback;
|
|
622
|
+
if (raw === "true") return 1;
|
|
623
|
+
if (raw === "false") return 0;
|
|
624
|
+
const value = Number(raw);
|
|
625
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
626
|
+
log(`Invalid ${name}="${process.env[name]}": expected a non-negative integer.`);
|
|
627
|
+
process.exit(2);
|
|
628
|
+
}
|
|
629
|
+
return value;
|
|
630
|
+
}
|
|
531
631
|
main();
|
|
532
632
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +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"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/http.ts","../src/rate-limit.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, DEFAULT_RATE_LIMIT } 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 Tool calls per minute across all clients (default: 120, 0 disables)\n RATE_LIMIT_PER_CLIENT Tool calls per minute per client IP (default: 60, 0 disables)\n TRUST_PROXY Reverse proxies in front of the server whose X-Forwarded-For is trusted\n (default: 0 = ignore the header; \"true\" = 1)\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 = {\n global: envCount('RATE_LIMIT', DEFAULT_RATE_LIMIT.global),\n perClient: envCount('RATE_LIMIT_PER_CLIENT', DEFAULT_RATE_LIMIT.perClient),\n trustProxy: envCount('TRUST_PROXY', 0),\n };\n\n const { app, handler } = createHttpApp({ host, allowedHosts, 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\n/** Reads a non-negative integer env var; \"true\"/\"false\" map to 1/0. Exits on garbage. */\nfunction envCount(name: string, fallback: number): number {\n const raw = process.env[name]?.trim().toLowerCase();\n if (!raw) return fallback;\n if (raw === 'true') return 1;\n if (raw === 'false') return 0;\n const value = Number(raw);\n if (!Number.isInteger(value) || value < 0) {\n log(`Invalid ${name}=\"${process.env[name]}\": expected a non-negative integer.`);\n process.exit(2);\n }\n return value;\n}\n\nmain();\n","import { createMcpHonoApp } from '@modelcontextprotocol/hono';\nimport { createMcpHandler, type McpHttpHandler } from '@modelcontextprotocol/server';\nimport type { Context, Hono } from 'hono';\nimport { createToolCallLimiter, RATE_LIMITED, type ToolCallLimiter, type ToolCallLimitOptions } from './rate-limit.js';\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\n// Carries the limiter's verdict from the route into the per-request server\n// factory. Always stripped from the client's request before being set.\nconst RATE_LIMIT_HEADER = 'x-archform-rate-limited';\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 /** Tool-call limits on /mcp. Defaults: 120 per minute overall, 60 per client, X-Forwarded-For ignored. */\n rateLimit?: Partial<ToolCallLimitOptions>;\n}\n\nexport const DEFAULT_RATE_LIMIT = { global: 120, perClient: 60 } as const;\n\nexport interface HttpApp {\n app: Hono;\n handler: McpHttpHandler;\n limiter: ToolCallLimiter;\n}\n\nexport function createHttpApp(options: HttpAppOptions = {}): HttpApp {\n const handler = createMcpHandler(\n ({ requestInfo }) => {\n const limited = requestInfo?.headers.get(RATE_LIMIT_HEADER);\n return createServer(limited ? { ...options, unavailable: decodeURIComponent(limited) } : options);\n },\n { maxRequestBodySize: MAX_REQUEST_BODY_BYTES },\n );\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 limiter = createToolCallLimiter({ ...DEFAULT_RATE_LIMIT, ...options.rateLimit });\n app.use('/mcp', limiter.middleware);\n\n app.post('/mcp', (c: Context) => {\n const parsedBody: unknown = c.get('parsedBody');\n const limited: string | undefined = c.get(RATE_LIMITED);\n if (!limited && !c.req.raw.headers.has(RATE_LIMIT_HEADER)) return handler.fetch(c.req.raw, { parsedBody });\n\n const headers = new Headers(c.req.raw.headers);\n headers.delete(RATE_LIMIT_HEADER);\n if (limited) headers.set(RATE_LIMIT_HEADER, encodeURIComponent(limited));\n const request = new Request(c.req.url, {\n method: 'POST',\n headers,\n body: parsedBody === undefined ? c.req.raw.body : JSON.stringify(parsedBody),\n ...(parsedBody === undefined ? { duplex: 'half' } : {}),\n } as RequestInit);\n return handler.fetch(request, { parsedBody });\n });\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, limiter };\n}\n","import type { Context, MiddlewareHandler, Next } from 'hono';\n\nexport interface ToolCallLimitOptions {\n /** Tool calls per window across all clients: size it to what the Kroki backend can take. 0 disables. */\n global: number;\n /** Tool calls per window per client IP. 0 disables. */\n perClient: number;\n windowMs?: number;\n /**\n * Number of reverse proxies in front of the server. With 0, X-Forwarded-For is\n * ignored (clients can set it to anything) and the socket address is used.\n * With N, the client is the entry the outermost trusted proxy appended:\n * the Nth from the end.\n */\n trustProxy?: number;\n /** Upper bound on tracked client buckets, so rotating addresses cannot grow memory. */\n maxClients?: number;\n}\n\nexport interface ToolCallLimiter {\n middleware: MiddlewareHandler;\n /** Number of client buckets currently held in memory. */\n readonly trackedClients: number;\n}\n\ninterface Window {\n count: number;\n resetAt: number;\n}\n\n/**\n * Fixed-window, in-memory limiter that counts `tools/call` requests (the calls\n * that reach Kroki), not HTTP requests: a session's initialize and tools/list\n * are free. Good for a single instance; use a shared store when running several.\n */\nexport function createToolCallLimiter(options: ToolCallLimitOptions): ToolCallLimiter {\n const windowMs = options.windowMs ?? 60_000;\n const trustProxy = options.trustProxy ?? 0;\n const maxClients = options.maxClients ?? 10_000;\n const global: Window = { count: 0, resetAt: 0 };\n const clients = new Map<string, Window>();\n\n const clientWindow = (key: string, now: number): Window => {\n let entry = clients.get(key);\n if (entry && entry.resetAt > now) return entry;\n if (entry) clients.delete(key);\n if (clients.size >= maxClients) {\n for (const [k, w] of clients) if (w.resetAt <= now) clients.delete(k);\n // Still full of live buckets: evict the oldest (Map keeps insertion order).\n while (clients.size >= maxClients) {\n const oldest = clients.keys().next().value;\n if (oldest === undefined) break;\n clients.delete(oldest);\n }\n }\n entry = { count: 0, resetAt: now + windowMs };\n clients.set(key, entry);\n return entry;\n };\n\n const middleware: MiddlewareHandler = async (c, next) => {\n const calls = countToolCalls(c);\n if (calls === 0) return next();\n\n const now = Date.now();\n if (global.resetAt <= now) {\n global.count = 0;\n global.resetAt = now + windowMs;\n }\n const client = options.perClient > 0 ? clientWindow(clientIp(c, trustProxy), now) : undefined;\n\n if (client && client.count + calls > options.perClient) {\n return tooManyRequests(c, next, client.resetAt - now, 'Too many tool calls from this client.');\n }\n if (options.global > 0 && global.count + calls > options.global) {\n return tooManyRequests(c, next, global.resetAt - now, 'The server is at capacity.');\n }\n if (client) client.count += calls;\n global.count += calls;\n return next();\n };\n\n return {\n middleware,\n get trackedClients() {\n return clients.size;\n },\n };\n}\n\n/**\n * Counts JSON-RPC `tools/call` messages in the body parsed by the MCP Hono app.\n * A POST whose body was not parsed as JSON counts as one call, so changing the\n * Content-Type cannot slip past the limit.\n */\nfunction countToolCalls(c: Context): number {\n if (c.req.method !== 'POST') return 0;\n const body: unknown = c.get('parsedBody');\n if (body === undefined) return 1;\n const messages = Array.isArray(body) ? body : [body];\n return messages.filter(\n (m) => typeof m === 'object' && m !== null && (m as { method?: unknown }).method === 'tools/call',\n ).length;\n}\n\nexport function clientIp(c: Context, trustProxy: number): string {\n if (trustProxy > 0) {\n const hops = (c.req.header('x-forwarded-for') ?? '')\n .split(',')\n .map((h) => h.trim())\n .filter(Boolean);\n const ip = hops[Math.max(0, hops.length - trustProxy)];\n if (ip) return ip;\n }\n const incoming = (c.env as { incoming?: { socket?: { remoteAddress?: string } } } | undefined)?.incoming;\n return incoming?.socket?.remoteAddress ?? 'unknown';\n}\n\n/** Context key under which the limiter leaves its message for a rate-limited single tool call. */\nexport const RATE_LIMITED = 'archformRateLimited';\n\n/**\n * A single `tools/call` is let through with a message under `RATE_LIMITED`; the\n * route turns it into a tool result with `isError: true` (through the SDK, so it\n * is valid on every protocol revision) and the model can tell the user to wait.\n * An HTTP 429 would surface as an opaque transport failure. Batches and\n * unparsed bodies still get 429.\n */\nasync function tooManyRequests(\n c: Context,\n next: Next,\n retryMs: number,\n message: string,\n): Promise<Response | undefined> {\n const seconds = Math.max(1, Math.ceil(retryMs / 1000));\n const text = `Rate limit reached: ${message} Retry in ${seconds}s.`;\n if (isSingleToolCall(c.get('parsedBody'))) {\n c.set(RATE_LIMITED, text);\n await next();\n return;\n }\n c.header('Retry-After', String(seconds));\n return c.json({ error: text }, 429);\n}\n\nfunction isSingleToolCall(body: unknown): boolean {\n if (typeof body !== 'object' || body === null || Array.isArray(body)) return false;\n const { method, id } = body as { method?: unknown; id?: unknown };\n return method === 'tools/call' && (typeof id === 'string' || typeof id === 'number');\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 /** When set, every tool returns this message as a tool error without doing any work (used for rate limiting). */\n unavailable?: string;\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, options.unavailable);\n registerRenderDiagram(server, renderer, options.unavailable);\n registerValidateDiagram(server, renderer, options.unavailable);\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, toolError } 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, unavailable?: string): 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 if (unavailable) return toolError(unavailable);\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, unavailable?: string): 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 if (unavailable) return toolError(unavailable);\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, unavailable?: string): 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 if (unavailable) return toolError(unavailable);\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.2.1\",\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"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@archform/mcp",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "MCP server that renders and validates diagrams as code: Mermaid, PlantUML, C4, Graphviz, D2, Structurizr, BPMN and Excalidraw.",
|
|
5
5
|
"mcpName": "io.github.himanshu-mamgain/archform",
|
|
6
6
|
"license": "MIT",
|