@slickfast/mcp 0.7.5 → 0.7.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -22
- package/dist/index.js +80 -54
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -1,30 +1,43 @@
|
|
|
1
1
|
# SlickFast — charts & dashboards for AI agents
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
**A tiny JSON spec in → a finished, retina-quality chart out. Milliseconds, a handful of
|
|
4
|
+
tokens, nothing leaves your machine.**
|
|
5
|
+
|
|
6
|
+
SlickFast is a **native SVG engine built for AI agents** — not a browser screenshotting a
|
|
7
|
+
webpage, not a plotting library an agent has to write code against. A pure
|
|
8
|
+
`spec → SVG → PNG` pipeline: **47 chart and information-design types** (bar, line, pie, KPI,
|
|
9
|
+
cards, funnel, gauge, heatmap, calendar, gantt, waterfall…), plus **entire multi-chart
|
|
10
|
+
dashboards tiled into one image in a single call**. It runs as an
|
|
11
|
+
[MCP](https://modelcontextprotocol.io) tool, so an agent hands it a spec and gets back a
|
|
12
|
+
finished PNG (or SVG).
|
|
8
13
|
|
|
9
14
|
```txt
|
|
10
15
|
render_chart({ type: "bar", data: { labels: ["Q1","Q2","Q3"], series: [{ values: [12,19,8] }] } })
|
|
11
16
|
→ a retina PNG, rendered on your machine
|
|
12
17
|
```
|
|
13
18
|
|
|
14
|
-
## Why
|
|
15
|
-
|
|
16
|
-
- **
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
-
|
|
27
|
-
|
|
19
|
+
## Why agents (and the people paying for their tokens) pick SlickFast
|
|
20
|
+
|
|
21
|
+
- **Tokens are the real cost — a spec is nearly free.** An agent hand-writing SVG or
|
|
22
|
+
matplotlib code burns hundreds to thousands of output tokens, then often retries when it
|
|
23
|
+
doesn't render. A SlickFast spec is a few dozen tokens, and `{type, data}` alone is a
|
|
24
|
+
finished, well-designed chart. A whole dashboard is **one tool call**, not ten renders
|
|
25
|
+
and layout math.
|
|
26
|
+
- **Changes are one-field edits.** Swap `bar` → `line`, change a palette, resize for a
|
|
27
|
+
slide: edit one key, re-render. The agent never rewrites code or reasons about a diff —
|
|
28
|
+
which is what makes iteration with a human ("make it dark, bigger title") actually cheap.
|
|
29
|
+
- **No headless browser.** Most chart-to-image pipelines secretly spawn Puppeteer or
|
|
30
|
+
Playwright — hundreds of MB, slow cold starts, flaky output. SlickFast renders pure
|
|
31
|
+
in-memory, milliseconds per chart.
|
|
32
|
+
- **Native SVG, vector-first.** Output is a few KB of crisp-at-any-scale SVG (or retina PNG
|
|
33
|
+
on demand) — small enough to cache, embed, or ship anywhere.
|
|
34
|
+
- **Deterministic — same spec, same chart, every time.** No randomness, no timestamps, no
|
|
35
|
+
browser drift. Cacheable, testable, reproducible; zero flaky pixel diffs. Almost no
|
|
36
|
+
charting tool can promise this — and it's exactly what a tool-calling agent needs.
|
|
37
|
+
- **Never throws garbage at the model.** Empty data, a bad tile, a filtered-to-nothing
|
|
38
|
+
series → a clean, graceful frame, not a crash or a stack trace. A real mistake (unknown
|
|
39
|
+
palette, bad enum) → a **loud error listing the valid options**, so the agent
|
|
40
|
+
self-corrects in one step.
|
|
28
41
|
- **100% local & private.** Rendered and rasterized on your machine. Nothing is sent anywhere.
|
|
29
42
|
|
|
30
43
|
## Tools
|
|
@@ -41,7 +54,7 @@ render_chart({ type: "bar", data: { labels: ["Q1","Q2","Q3"], series: [{ values:
|
|
|
41
54
|
- **`list_palettes()`** → every valid `palette` name, grouped into flat palettes and nested
|
|
42
55
|
themes, with their colors. Ask *"what palettes are available?"*.
|
|
43
56
|
- **`report_issue(summary, spec?)`** → *"report this bug."* Formats a bug report and returns a
|
|
44
|
-
prefilled email link you click to send — SlickFast sends nothing itself.
|
|
57
|
+
prefilled GitHub-issue link and email link you click to send — SlickFast sends nothing itself.
|
|
45
58
|
|
|
46
59
|
## Install
|
|
47
60
|
|
|
@@ -95,11 +108,19 @@ or post** — it never auto-inserts links. (The endpoint stays unadvertised duri
|
|
|
95
108
|
**AGPL-3.0-only.** Free to use, self-host, and embed under the AGPL's terms (your friends
|
|
96
109
|
running it locally are completely unaffected). Building it into a **closed-source product
|
|
97
110
|
or a hosted service**? That needs the AGPL'd source opened — or a **commercial license**
|
|
98
|
-
from us instead.
|
|
111
|
+
from us instead. Write to feedback@slickfast.com for commercial terms.
|
|
112
|
+
|
|
113
|
+
## Feedback, bugs & feature requests
|
|
114
|
+
|
|
115
|
+
- **GitHub:** [SlickFast/slickfast](https://github.com/SlickFast/slickfast) — bug reports and
|
|
116
|
+
feature requests via the [issue templates](https://github.com/SlickFast/slickfast/issues/new/choose).
|
|
117
|
+
- **Email:** feedback@slickfast.com
|
|
118
|
+
- Or ask your agent to *"report this as a SlickFast issue"* — the `report_issue` tool drafts
|
|
119
|
+
the report (with the repro spec) for you. Nothing is ever sent automatically.
|
|
99
120
|
|
|
100
121
|
## Developing locally
|
|
101
122
|
|
|
102
|
-
Clone the repo, then from the package directory:
|
|
123
|
+
Clone [the repo](https://github.com/SlickFast/slickfast), then from the package directory:
|
|
103
124
|
|
|
104
125
|
```bash
|
|
105
126
|
cd apps/mcp
|
package/dist/index.js
CHANGED
|
@@ -199,6 +199,7 @@ function contrastFloor(color, bg, transparent) {
|
|
|
199
199
|
return hslToHex(hsl.h, hsl.s, Math.max(0, Math.min(100, hsl.l + dl)));
|
|
200
200
|
}
|
|
201
201
|
var esc = (s) => String(s ?? "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
202
|
+
var svgOpen = (W, H, font) => `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${W} ${H}" width="${W}" height="${H}" style="max-width:100%;height:auto" font-family="${esc(font)}">`;
|
|
202
203
|
var r = (n) => Math.round(n * 100) / 100;
|
|
203
204
|
var fmt = (n) => (Number(n) || 0).toLocaleString("en-US");
|
|
204
205
|
function pct100(vals) {
|
|
@@ -312,7 +313,7 @@ function renderBar(spec) {
|
|
|
312
313
|
const band = plotW / labels.length;
|
|
313
314
|
const barW = Math.min(band * 0.62, 96);
|
|
314
315
|
const p = [];
|
|
315
|
-
p.push(
|
|
316
|
+
p.push(svgOpen(W, H, font));
|
|
316
317
|
if (!transparent) p.push(`<rect x="0" y="0" width="${W}" height="${H}" fill="${bg}"/>`);
|
|
317
318
|
for (let v = 0; v <= niceMax + 1e-9; v += step) {
|
|
318
319
|
const y = r(yPix(v));
|
|
@@ -371,7 +372,7 @@ function renderBarGrouped(spec) {
|
|
|
371
372
|
const n = series.length;
|
|
372
373
|
const barW = groupW / n;
|
|
373
374
|
const p = [];
|
|
374
|
-
p.push(
|
|
375
|
+
p.push(svgOpen(W, H, font));
|
|
375
376
|
if (!transparent) p.push(`<rect x="0" y="0" width="${W}" height="${H}" fill="${bg}"/>`);
|
|
376
377
|
for (let v = 0; v <= niceMax + 1e-9; v += step) {
|
|
377
378
|
const y = r(yPix(v));
|
|
@@ -441,7 +442,7 @@ function renderBarStacked(spec) {
|
|
|
441
442
|
const band = plotW / labels.length;
|
|
442
443
|
const barW = Math.min(band * 0.62, 96);
|
|
443
444
|
const p = [];
|
|
444
|
-
p.push(
|
|
445
|
+
p.push(svgOpen(W, H, font));
|
|
445
446
|
if (!transparent) p.push(`<rect x="0" y="0" width="${W}" height="${H}" fill="${bg}"/>`);
|
|
446
447
|
for (let v = 0; v <= niceMax + 1e-9; v += step) {
|
|
447
448
|
const y = r(yPix(v));
|
|
@@ -520,7 +521,7 @@ function renderBarStackedH(spec) {
|
|
|
520
521
|
const band = plotH / labels.length;
|
|
521
522
|
const barH = Math.min(band * 0.62, 64);
|
|
522
523
|
const p = [];
|
|
523
|
-
p.push(
|
|
524
|
+
p.push(svgOpen(W, H, font));
|
|
524
525
|
if (!transparent) p.push(`<rect x="0" y="0" width="${W}" height="${H}" fill="${bg}"/>`);
|
|
525
526
|
for (let v = 0; v <= niceMax + 1e-9; v += step) {
|
|
526
527
|
const x = r(xPix(v));
|
|
@@ -604,7 +605,7 @@ function renderBarH(spec) {
|
|
|
604
605
|
const band = plotH / labels.length;
|
|
605
606
|
const barH = Math.min(band * 0.62, 56);
|
|
606
607
|
const p = [];
|
|
607
|
-
p.push(
|
|
608
|
+
p.push(svgOpen(W, H, font));
|
|
608
609
|
if (!transparent) p.push(`<rect x="0" y="0" width="${W}" height="${H}" fill="${bg}"/>`);
|
|
609
610
|
for (let v = 0; v <= niceMax + 1e-9; v += step) {
|
|
610
611
|
const x = r(xPix(v));
|
|
@@ -679,7 +680,7 @@ function renderDiverging(spec) {
|
|
|
679
680
|
const band = plotW / labels.length;
|
|
680
681
|
const barW = Math.min(band * 0.5, 72);
|
|
681
682
|
const p = [];
|
|
682
|
-
p.push(
|
|
683
|
+
p.push(svgOpen(W, H, font));
|
|
683
684
|
if (!transparent) p.push(`<rect x="0" y="0" width="${W}" height="${H}" fill="${bg}"/>`);
|
|
684
685
|
for (let v = step; v <= niceMax + 1e-9; v += step) {
|
|
685
686
|
[yUp(v), yDn(v)].forEach((y) => {
|
|
@@ -757,7 +758,7 @@ function renderLollipop(spec) {
|
|
|
757
758
|
const band = plotW / labels.length;
|
|
758
759
|
const baseY = M.top + plotH;
|
|
759
760
|
const p = [];
|
|
760
|
-
p.push(
|
|
761
|
+
p.push(svgOpen(W, H, font));
|
|
761
762
|
if (!transparent) p.push(`<rect x="0" y="0" width="${W}" height="${H}" fill="${bg}"/>`);
|
|
762
763
|
for (let v = 0; v <= niceMax + 1e-9; v += step) {
|
|
763
764
|
const y = r(yPix(v));
|
|
@@ -806,7 +807,7 @@ function renderKpi(spec) {
|
|
|
806
807
|
const pillBg = good ? isDark ? "#064e3b" : "#ecfdf5" : bad ? isDark ? "#7f1d1d" : "#fef2f2" : isDark ? "#334155" : "#f1f5f9";
|
|
807
808
|
const pillTx = good ? isDark ? "#6ee7b7" : "#059669" : bad ? isDark ? "#fca5a5" : "#dc2626" : labelCol;
|
|
808
809
|
const p = [];
|
|
809
|
-
p.push(
|
|
810
|
+
p.push(svgOpen(W, H, font));
|
|
810
811
|
if (!transparent) p.push(`<rect x="0" y="0" width="${W}" height="${H}" rx="14" fill="${bg}"/>`);
|
|
811
812
|
const portrait = H > W;
|
|
812
813
|
if (!portrait) {
|
|
@@ -899,7 +900,7 @@ function renderLine(spec) {
|
|
|
899
900
|
const n = labels.length;
|
|
900
901
|
const xPix = (i) => M.left + (n > 1 ? i / (n - 1) : 0.5) * plotW;
|
|
901
902
|
const p = [];
|
|
902
|
-
p.push(
|
|
903
|
+
p.push(svgOpen(W, H, font));
|
|
903
904
|
if (!transparent) p.push(`<rect x="0" y="0" width="${W}" height="${H}" fill="${bg}"/>`);
|
|
904
905
|
for (let v = 0; v <= niceMax + 1e-9; v += step) {
|
|
905
906
|
const y = r(yPix(v));
|
|
@@ -1008,7 +1009,7 @@ function renderSlope(spec) {
|
|
|
1008
1009
|
const { niceMax } = niceScale(Math.max(1, ...firstVals, ...lastVals));
|
|
1009
1010
|
const yPix = (v) => M.top + plotH - v / niceMax * plotH;
|
|
1010
1011
|
const p = [];
|
|
1011
|
-
p.push(
|
|
1012
|
+
p.push(svgOpen(W, H, font));
|
|
1012
1013
|
if (!transparent) p.push(`<rect x="0" y="0" width="${W}" height="${H}" fill="${bg}"/>`);
|
|
1013
1014
|
p.push(`<line x1="${leftX}" y1="${M.top}" x2="${leftX}" y2="${r(baseY)}" stroke="${guide}" stroke-width="1"/>`);
|
|
1014
1015
|
p.push(`<line x1="${rightX}" y1="${M.top}" x2="${rightX}" y2="${r(baseY)}" stroke="${guide}" stroke-width="1"/>`);
|
|
@@ -1076,7 +1077,7 @@ function renderPie(spec) {
|
|
|
1076
1077
|
const gapW = spec.sliceGap != null ? Number(spec.sliceGap) : Math.max(2, r(rad * 0.016));
|
|
1077
1078
|
const gapAttr = gapColor && gapW > 0 ? ` stroke="${gapColor}" stroke-width="${gapW}" stroke-linejoin="round"` : "";
|
|
1078
1079
|
const p = [];
|
|
1079
|
-
p.push(
|
|
1080
|
+
p.push(svgOpen(W, H, font));
|
|
1080
1081
|
if (!transparent) p.push(`<rect x="0" y="0" width="${W}" height="${H}" fill="${bg}"/>`);
|
|
1081
1082
|
let a = -Math.PI / 2;
|
|
1082
1083
|
values.forEach((v, i) => {
|
|
@@ -1171,7 +1172,7 @@ function renderPieOfPie(spec) {
|
|
|
1171
1172
|
});
|
|
1172
1173
|
const n = pies.length;
|
|
1173
1174
|
const p = [];
|
|
1174
|
-
p.push(
|
|
1175
|
+
p.push(svgOpen(W, H, font));
|
|
1175
1176
|
if (!transparent) p.push(`<rect x="0" y="0" width="${W}" height="${H}" fill="${bg}"/>`);
|
|
1176
1177
|
if (n === 0) {
|
|
1177
1178
|
p.push(`</svg>`);
|
|
@@ -1313,7 +1314,7 @@ function renderCards(spec) {
|
|
|
1313
1314
|
const cellW = (W - PAD * 2 - GAP * (cols - 1)) / cols;
|
|
1314
1315
|
const cellH = (H - PAD * 2 - titleH - GAP * (rows - 1)) / rows;
|
|
1315
1316
|
const p = [];
|
|
1316
|
-
p.push(
|
|
1317
|
+
p.push(svgOpen(W, H, font));
|
|
1317
1318
|
if (!transparent) p.push(`<rect x="0" y="0" width="${W}" height="${H}" fill="${bg}"/>`);
|
|
1318
1319
|
if (title) p.push(`<text x="${r(W / 2)}" y="33" text-anchor="middle" font-size="${fs + 5}" ${wAttr(spec, 700)} fill="${titleCol}">${esc(title)}</text>`);
|
|
1319
1320
|
cards.forEach((c, i) => {
|
|
@@ -1372,7 +1373,7 @@ function renderLayers(spec) {
|
|
|
1372
1373
|
const blockW = W - PAD * 2;
|
|
1373
1374
|
const blockH = (H - topY - 16 - GAP * (n - 1)) / n;
|
|
1374
1375
|
const p = [];
|
|
1375
|
-
p.push(
|
|
1376
|
+
p.push(svgOpen(W, H, font));
|
|
1376
1377
|
if (!transparent) p.push(`<rect x="0" y="0" width="${W}" height="${H}" fill="${bg}"/>`);
|
|
1377
1378
|
if (title) p.push(`<text x="${r(W / 2)}" y="33" text-anchor="middle" font-size="${fs + 5}" ${wAttr(spec, 700)} fill="${titleCol}">${esc(title)}</text>`);
|
|
1378
1379
|
layers.forEach((layer, i) => {
|
|
@@ -1416,7 +1417,7 @@ function renderProgress(spec) {
|
|
|
1416
1417
|
const H = spec.height || topY + n * ROW_H + 8;
|
|
1417
1418
|
const trackW = W - PAD * 2;
|
|
1418
1419
|
const p = [];
|
|
1419
|
-
p.push(
|
|
1420
|
+
p.push(svgOpen(W, H, font));
|
|
1420
1421
|
if (!transparent) p.push(`<rect x="0" y="0" width="${W}" height="${H}" fill="${bg}"/>`);
|
|
1421
1422
|
if (title) p.push(`<text x="${r(W / 2)}" y="33" text-anchor="middle" font-size="${fs + 5}" ${wAttr(spec, 700)} fill="${titleCol}">${esc(title)}</text>`);
|
|
1422
1423
|
bars.forEach((b, i) => {
|
|
@@ -1477,7 +1478,7 @@ function renderWaffle(spec) {
|
|
|
1477
1478
|
const W = spec.width || 600;
|
|
1478
1479
|
const H = spec.height || topY + gridSize + 16;
|
|
1479
1480
|
const p = [];
|
|
1480
|
-
p.push(
|
|
1481
|
+
p.push(svgOpen(W, H, font));
|
|
1481
1482
|
if (!transparent) p.push(`<rect x="0" y="0" width="${W}" height="${H}" fill="${bg}"/>`);
|
|
1482
1483
|
if (title) p.push(`<text x="${r(W / 2)}" y="33" text-anchor="middle" font-size="${fs + 5}" ${wAttr(spec, 700)} fill="${titleCol}">${esc(title)}</text>`);
|
|
1483
1484
|
for (let k = 0; k < 100; k++) {
|
|
@@ -1534,7 +1535,7 @@ function renderHeatmap(spec) {
|
|
|
1534
1535
|
const cellW = plotW / nc;
|
|
1535
1536
|
const gridTop = topY + COLH;
|
|
1536
1537
|
const p = [];
|
|
1537
|
-
p.push(
|
|
1538
|
+
p.push(svgOpen(W, H, font));
|
|
1538
1539
|
if (!transparent) p.push(`<rect x="0" y="0" width="${W}" height="${H}" fill="${bg}"/>`);
|
|
1539
1540
|
if (title) p.push(`<text x="${r(W / 2)}" y="33" text-anchor="middle" font-size="${fs + 5}" ${wAttr(spec, 700)} fill="${titleCol}">${esc(title)}</text>`);
|
|
1540
1541
|
cols.forEach((c, ci) => p.push(`<text x="${r(plotLeft + ci * cellW + cellW / 2)}" y="${r(gridTop - 8)}" text-anchor="middle" font-size="${fs - 1}"${wOpt(spec)} fill="${labelCol}">${esc(c)}</text>`));
|
|
@@ -1580,7 +1581,7 @@ function renderFunnel(spec) {
|
|
|
1580
1581
|
const maxV = Math.max(1, ...vals);
|
|
1581
1582
|
const top = vals[0] || 0;
|
|
1582
1583
|
const p = [];
|
|
1583
|
-
p.push(
|
|
1584
|
+
p.push(svgOpen(W, H, font));
|
|
1584
1585
|
if (!transparent) p.push(`<rect x="0" y="0" width="${W}" height="${H}" fill="${bg}"/>`);
|
|
1585
1586
|
if (title) p.push(`<text x="${r(cx)}" y="33" text-anchor="middle" font-size="${fs + 5}" ${wAttr(spec, 700)} fill="${titleCol}">${esc(title)}</text>`);
|
|
1586
1587
|
stages.forEach((s, i) => {
|
|
@@ -1621,7 +1622,7 @@ function renderPyramid(spec) {
|
|
|
1621
1622
|
const apexY = topY, cx = W / 2, bandH = totalH / n;
|
|
1622
1623
|
const widthAt = (y) => (y - apexY) / totalH * baseW;
|
|
1623
1624
|
const p = [];
|
|
1624
|
-
p.push(
|
|
1625
|
+
p.push(svgOpen(W, H, font));
|
|
1625
1626
|
if (!transparent) p.push(`<rect x="0" y="0" width="${W}" height="${H}" fill="${bg}"/>`);
|
|
1626
1627
|
if (title) p.push(`<text x="${r(cx)}" y="33" text-anchor="middle" font-size="${fs + 5}" ${wAttr(spec, 700)} fill="${titleCol}">${esc(title)}</text>`);
|
|
1627
1628
|
levels.forEach((lv, i) => {
|
|
@@ -1660,7 +1661,7 @@ function renderQuadrant(spec) {
|
|
|
1660
1661
|
const S = Math.min(W - PAD * 2, H - plotTop - 46);
|
|
1661
1662
|
const plotX = (W - S) / 2, plotBottom = plotTop + S;
|
|
1662
1663
|
const p = [];
|
|
1663
|
-
p.push(
|
|
1664
|
+
p.push(svgOpen(W, H, font));
|
|
1664
1665
|
if (!transparent) p.push(`<rect x="0" y="0" width="${W}" height="${H}" fill="${bg}"/>`);
|
|
1665
1666
|
if (title) p.push(`<text x="${r(W / 2)}" y="33" text-anchor="middle" font-size="${fs + 5}" ${wAttr(spec, 700)} fill="${titleCol}">${esc(title)}</text>`);
|
|
1666
1667
|
p.push(`<rect x="${r(plotX)}" y="${r(plotTop)}" width="${r(S)}" height="${r(S)}" rx="8" fill="none" stroke="${gridCol}" stroke-width="1"/>`);
|
|
@@ -1704,7 +1705,7 @@ function renderTimeline(spec) {
|
|
|
1704
1705
|
const plotW = W - PAD * 2;
|
|
1705
1706
|
const xOf = (i) => n === 1 ? W / 2 : PAD + i * (plotW / (n - 1));
|
|
1706
1707
|
const p = [];
|
|
1707
|
-
p.push(
|
|
1708
|
+
p.push(svgOpen(W, H, font));
|
|
1708
1709
|
if (!transparent) p.push(`<rect x="0" y="0" width="${W}" height="${H}" fill="${bg}"/>`);
|
|
1709
1710
|
if (title) p.push(`<text x="${r(W / 2)}" y="33" text-anchor="middle" font-size="${fs + 5}" ${wAttr(spec, 700)} fill="${titleCol}">${esc(title)}</text>`);
|
|
1710
1711
|
p.push(`<line x1="${PAD}" y1="${r(lineY)}" x2="${W - PAD}" y2="${r(lineY)}" stroke="${lineCol}" stroke-width="2"/>`);
|
|
@@ -1743,7 +1744,7 @@ function renderVenn(spec) {
|
|
|
1743
1744
|
const W = spec.width || 600, H = spec.height || 460;
|
|
1744
1745
|
const val = (i) => fmt(Number(sets[i] && sets[i].value) || 0);
|
|
1745
1746
|
const p = [];
|
|
1746
|
-
p.push(
|
|
1747
|
+
p.push(svgOpen(W, H, font));
|
|
1747
1748
|
if (!transparent) p.push(`<rect x="0" y="0" width="${W}" height="${H}" fill="${bg}"/>`);
|
|
1748
1749
|
if (title) p.push(`<text x="${r(W / 2)}" y="33" text-anchor="middle" font-size="${fs + 5}" ${wAttr(spec, 700)} fill="${titleCol}">${esc(title)}</text>`);
|
|
1749
1750
|
if (ns <= 2) {
|
|
@@ -1828,7 +1829,7 @@ function renderMatrix(spec) {
|
|
|
1828
1829
|
const plotLeft = PAD + LEFTW;
|
|
1829
1830
|
const cellW = (W - PAD - plotLeft) / nc;
|
|
1830
1831
|
const p = [];
|
|
1831
|
-
p.push(
|
|
1832
|
+
p.push(svgOpen(W, H, font));
|
|
1832
1833
|
if (!transparent) p.push(`<rect x="0" y="0" width="${W}" height="${H}" fill="${bg}"/>`);
|
|
1833
1834
|
if (title) p.push(`<text x="${r(W / 2)}" y="33" text-anchor="middle" font-size="${fs + 5}" ${wAttr(spec, 700)} fill="${titleCol}">${esc(title)}</text>`);
|
|
1834
1835
|
columns.forEach((c, ci) => p.push(`<text x="${r(plotLeft + ci * cellW + cellW / 2)}" y="${r(topY + HEAD - 14)}" text-anchor="middle" font-size="${fs}" ${wAttr(spec, 700)} fill="${headCol}">${esc(c)}</text>`));
|
|
@@ -1866,7 +1867,7 @@ function renderChecklist(spec) {
|
|
|
1866
1867
|
const H = spec.height || topY + n * ROWH + 12;
|
|
1867
1868
|
const s = fs * 1, gx = PAD + 11;
|
|
1868
1869
|
const p = [];
|
|
1869
|
-
p.push(
|
|
1870
|
+
p.push(svgOpen(W, H, font));
|
|
1870
1871
|
if (!transparent) p.push(`<rect x="0" y="0" width="${W}" height="${H}" fill="${bg}"/>`);
|
|
1871
1872
|
if (title) p.push(`<text x="${PAD}" y="33" font-size="${fs + 5}" ${wAttr(spec, 700)} fill="${titleCol}">${esc(title)}</text>`);
|
|
1872
1873
|
items.forEach((it, i) => {
|
|
@@ -1911,7 +1912,7 @@ function renderIconArray(spec) {
|
|
|
1911
1912
|
const W = spec.width || PAD * 2 + perRow * ICON + (perRow - 1) * GAP;
|
|
1912
1913
|
const H = spec.height || topY + rowsN * (ICON + GAP) + 12;
|
|
1913
1914
|
const p = [];
|
|
1914
|
-
p.push(
|
|
1915
|
+
p.push(svgOpen(W, H, font));
|
|
1915
1916
|
if (!transparent) p.push(`<rect x="0" y="0" width="${W}" height="${H}" fill="${bg}"/>`);
|
|
1916
1917
|
if (title) p.push(`<text x="${PAD}" y="33" font-size="${fs + 5}" ${wAttr(spec, 700)} fill="${titleCol}">${esc(title)}</text>`);
|
|
1917
1918
|
for (let k = 0; k < total; k++) {
|
|
@@ -1950,7 +1951,7 @@ function renderSteps(spec) {
|
|
|
1950
1951
|
const cy = topY + 36 + R;
|
|
1951
1952
|
const xOf = (i) => n === 1 ? W / 2 : PAD + R + i * ((W - 2 * (PAD + R)) / (n - 1));
|
|
1952
1953
|
const p = [];
|
|
1953
|
-
p.push(
|
|
1954
|
+
p.push(svgOpen(W, H, font));
|
|
1954
1955
|
if (!transparent) p.push(`<rect x="0" y="0" width="${W}" height="${H}" fill="${bg}"/>`);
|
|
1955
1956
|
if (title) p.push(`<text x="${r(W / 2)}" y="33" text-anchor="middle" font-size="${fs + 5}" ${wAttr(spec, 700)} fill="${titleCol}">${esc(title)}</text>`);
|
|
1956
1957
|
for (let i = 0; i < n - 1; i++) p.push(connectorLine(xOf(i) + R + 6, xOf(i + 1) - R - 6, cy, connCol));
|
|
@@ -1990,7 +1991,7 @@ function renderTable(spec) {
|
|
|
1990
1991
|
const isNum = (v) => typeof v === "number" || typeof v === "string" && v.trim() !== "" && !isNaN(Number(v));
|
|
1991
1992
|
const cellX = (ci, right) => right ? PAD + ci * colW + colW - 10 : PAD + ci * colW + 4;
|
|
1992
1993
|
const p = [];
|
|
1993
|
-
p.push(
|
|
1994
|
+
p.push(svgOpen(W, H, font));
|
|
1994
1995
|
if (!transparent) p.push(`<rect x="0" y="0" width="${W}" height="${H}" fill="${bg}"/>`);
|
|
1995
1996
|
if (title) p.push(`<text x="${r(W / 2)}" y="33" text-anchor="middle" font-size="${fs + 5}" ${wAttr(spec, 700)} fill="${titleCol}">${esc(title)}</text>`);
|
|
1996
1997
|
columns.forEach((c, ci) => {
|
|
@@ -2041,7 +2042,7 @@ function renderGauge(spec) {
|
|
|
2041
2042
|
const innerR = rad * 0.72;
|
|
2042
2043
|
const u = spec.valueUnit ? " " + spec.valueUnit : "";
|
|
2043
2044
|
const p = [];
|
|
2044
|
-
p.push(
|
|
2045
|
+
p.push(svgOpen(W, H, font));
|
|
2045
2046
|
if (!transparent) p.push(`<rect x="0" y="0" width="${W}" height="${H}" fill="${bg}"/>`);
|
|
2046
2047
|
if (title) p.push(`<text x="${r(cx)}" y="33" text-anchor="middle" font-size="${fs + 5}" ${wAttr(spec, 700)} fill="${valueCol}">${esc(title)}</text>`);
|
|
2047
2048
|
p.push(`<path d="${arcPath(cx, cy, rad, Math.PI, 2 * Math.PI, innerR)}" fill="${track}"/>`);
|
|
@@ -2076,7 +2077,7 @@ function renderBullet(spec) {
|
|
|
2076
2077
|
const H = spec.height || topY + n * ROW_H + 8;
|
|
2077
2078
|
const trackW = W - PAD * 2;
|
|
2078
2079
|
const p = [];
|
|
2079
|
-
p.push(
|
|
2080
|
+
p.push(svgOpen(W, H, font));
|
|
2080
2081
|
if (!transparent) p.push(`<rect x="0" y="0" width="${W}" height="${H}" fill="${bg}"/>`);
|
|
2081
2082
|
if (title) p.push(`<text x="${r(W / 2)}" y="33" text-anchor="middle" font-size="${fs + 5}" ${wAttr(spec, 700)} fill="${tickCol}">${esc(title)}</text>`);
|
|
2082
2083
|
bars.forEach((b, i) => {
|
|
@@ -2146,7 +2147,7 @@ function renderCalendar(spec) {
|
|
|
2146
2147
|
const W = spec.width || gridX + weeks * (CELL + GAP) + PAD;
|
|
2147
2148
|
const H = spec.height || gridY + 7 * (CELL + GAP) + 16;
|
|
2148
2149
|
const p = [];
|
|
2149
|
-
p.push(
|
|
2150
|
+
p.push(svgOpen(W, H, font));
|
|
2150
2151
|
if (!transparent) p.push(`<rect x="0" y="0" width="${W}" height="${H}" fill="${bg}"/>`);
|
|
2151
2152
|
if (title) p.push(`<text x="${PAD}" y="33" font-size="${fs + 5}" ${wAttr(spec, 700)} fill="${titleCol}">${esc(title)}</text>`);
|
|
2152
2153
|
const MON = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
@@ -2190,7 +2191,7 @@ function renderLeaderboard(spec) {
|
|
|
2190
2191
|
const barLeft = PAD + RANKW + LABELW, barRight = W - PAD - VALW;
|
|
2191
2192
|
const maxV = Math.max(1, ...items.map((s) => s.value));
|
|
2192
2193
|
const p = [];
|
|
2193
|
-
p.push(
|
|
2194
|
+
p.push(svgOpen(W, H, font));
|
|
2194
2195
|
if (!transparent) p.push(`<rect x="0" y="0" width="${W}" height="${H}" fill="${bg}"/>`);
|
|
2195
2196
|
if (title) p.push(`<text x="${r(W / 2)}" y="33" text-anchor="middle" font-size="${fs + 5}" ${wAttr(spec, 700)} fill="${titleCol}">${esc(title)}</text>`);
|
|
2196
2197
|
items.forEach((it, i) => {
|
|
@@ -2223,7 +2224,7 @@ function renderCallout(spec) {
|
|
|
2223
2224
|
const topY = title ? 50 : 20;
|
|
2224
2225
|
const valueStr = (spec.valuePrefix || "") + (spec.value != null ? fmt(spec.value) : "") + (spec.valueUnit || "");
|
|
2225
2226
|
const p = [];
|
|
2226
|
-
p.push(
|
|
2227
|
+
p.push(svgOpen(W, H, font));
|
|
2227
2228
|
if (!transparent) p.push(`<rect x="0" y="0" width="${W}" height="${H}" fill="${bg}"/>`);
|
|
2228
2229
|
if (title) p.push(`<text x="${PAD}" y="33" font-size="${fs + 5}" ${wAttr(spec, 700)} fill="${titleCol}">${esc(title)}</text>`);
|
|
2229
2230
|
const by = topY + (H - topY) * 0.5;
|
|
@@ -2264,7 +2265,7 @@ function renderRing(spec) {
|
|
|
2264
2265
|
const circ = 2 * Math.PI * R;
|
|
2265
2266
|
const dash = frac * circ;
|
|
2266
2267
|
const p = [];
|
|
2267
|
-
p.push(
|
|
2268
|
+
p.push(svgOpen(W, H, font));
|
|
2268
2269
|
if (!transparent) p.push(`<rect x="0" y="0" width="${W}" height="${H}" fill="${bg}"/>`);
|
|
2269
2270
|
if (title) p.push(`<text x="${r(cx)}" y="33" text-anchor="middle" font-size="${fs + 5}" ${wAttr(spec, 700)} fill="${valueCol}">${esc(title)}</text>`);
|
|
2270
2271
|
p.push(`<circle cx="${r(cx)}" cy="${r(cy)}" r="${r(R)}" fill="none" stroke="${track}" stroke-width="${r(sw)}"/>`);
|
|
@@ -2293,7 +2294,7 @@ function renderVersus(spec) {
|
|
|
2293
2294
|
const colW = (W - PAD * 2 - GAP) / 2;
|
|
2294
2295
|
const cols = [PAD, PAD + colW + GAP];
|
|
2295
2296
|
const p = [];
|
|
2296
|
-
p.push(
|
|
2297
|
+
p.push(svgOpen(W, H, font));
|
|
2297
2298
|
if (!transparent) p.push(`<rect x="0" y="0" width="${W}" height="${H}" fill="${bg}"/>`);
|
|
2298
2299
|
if (title) p.push(`<text x="${r(W / 2)}" y="33" text-anchor="middle" font-size="${fs + 5}" ${wAttr(spec, 700)} fill="${titleCol}">${esc(title)}</text>`);
|
|
2299
2300
|
[0, 1].forEach((si) => {
|
|
@@ -2341,7 +2342,7 @@ function renderGantt(spec) {
|
|
|
2341
2342
|
const plotLeft = PAD + LABELW, plotW = W - PAD - plotLeft, gridTop = topY + HEAD;
|
|
2342
2343
|
const xOf = (v) => plotLeft + (v - lo) / (hi - lo || 1) * plotW;
|
|
2343
2344
|
const p = [];
|
|
2344
|
-
p.push(
|
|
2345
|
+
p.push(svgOpen(W, H, font));
|
|
2345
2346
|
if (!transparent) p.push(`<rect x="0" y="0" width="${W}" height="${H}" fill="${bg}"/>`);
|
|
2346
2347
|
if (title) p.push(`<text x="${r(W / 2)}" y="33" text-anchor="middle" font-size="${fs + 5}" ${wAttr(spec, 700)} fill="${titleCol}">${esc(title)}</text>`);
|
|
2347
2348
|
for (let k = 0; k <= 4; k++) {
|
|
@@ -2390,7 +2391,7 @@ function renderWaterfall(spec) {
|
|
|
2390
2391
|
const bandW = (plotW - GAP * (N - 1)) / N;
|
|
2391
2392
|
const yOf = (v) => plotBottom - (v - lo) / (hi - lo || 1) * (plotBottom - plotTop);
|
|
2392
2393
|
const p = [];
|
|
2393
|
-
p.push(
|
|
2394
|
+
p.push(svgOpen(W, H, font));
|
|
2394
2395
|
if (!transparent) p.push(`<rect x="0" y="0" width="${W}" height="${H}" fill="${bg}"/>`);
|
|
2395
2396
|
if (title) p.push(`<text x="${r(W / 2)}" y="33" text-anchor="middle" font-size="${fs + 5}" ${wAttr(spec, 700)} fill="${titleCol}">${esc(title)}</text>`);
|
|
2396
2397
|
segs.forEach((s, i) => {
|
|
@@ -2433,7 +2434,7 @@ function renderSwimlane(spec) {
|
|
|
2433
2434
|
const W = spec.width || 680, H = spec.height || topY + HEAD + nl * LANEH + 16;
|
|
2434
2435
|
const plotLeft = PAD + LABELW, colW = (W - PAD - plotLeft) / np, gridTop = topY + HEAD;
|
|
2435
2436
|
const p = [];
|
|
2436
|
-
p.push(
|
|
2437
|
+
p.push(svgOpen(W, H, font));
|
|
2437
2438
|
if (!transparent) p.push(`<rect x="0" y="0" width="${W}" height="${H}" fill="${bg}"/>`);
|
|
2438
2439
|
if (title) p.push(`<text x="${r(W / 2)}" y="33" text-anchor="middle" font-size="${fs + 5}" ${wAttr(spec, 700)} fill="${titleCol}">${esc(title)}</text>`);
|
|
2439
2440
|
phases.forEach((ph, ci) => p.push(`<text x="${r(plotLeft + ci * colW + colW / 2)}" y="${r(topY + HEAD - 7)}" text-anchor="middle" font-size="${fs - 1}" ${wAttr(spec, 600)} fill="${labelCol}">${esc(ph)}</text>`));
|
|
@@ -2470,25 +2471,48 @@ function renderTierList(spec) {
|
|
|
2470
2471
|
const faint = isDark ? "#475569" : "#94a3b8";
|
|
2471
2472
|
const PAD = 16, LABELW = 56, ROWH = 48, GAP = 6;
|
|
2472
2473
|
const topY = title ? 50 : 14;
|
|
2473
|
-
const W = spec.width || 560
|
|
2474
|
+
const W = spec.width || 560;
|
|
2475
|
+
const chipH = 26, chipGapX = 8, chipGapY = 6;
|
|
2476
|
+
const chipX0 = PAD + LABELW + 10, maxLineW = W - PAD - chipX0;
|
|
2477
|
+
const layout = tiers.map((t) => {
|
|
2478
|
+
const lines = [[]];
|
|
2479
|
+
(Array.isArray(t.items) ? t.items : []).forEach((it) => {
|
|
2480
|
+
let label = typeof it === "string" ? it : it && it.label || "";
|
|
2481
|
+
let cw = Math.round(label.length * (fs * 0.6) + 18);
|
|
2482
|
+
if (cw > maxLineW) {
|
|
2483
|
+
const maxChars = Math.max(1, Math.floor((maxLineW - 18) / (fs * 0.6)) - 1);
|
|
2484
|
+
label = label.slice(0, maxChars) + "\u2026";
|
|
2485
|
+
cw = Math.min(Math.round((maxChars + 1) * (fs * 0.6) + 18), maxLineW);
|
|
2486
|
+
}
|
|
2487
|
+
const cur = lines[lines.length - 1];
|
|
2488
|
+
const curW = cur.reduce((a, c) => a + c.cw + chipGapX, 0);
|
|
2489
|
+
if (cur.length && curW + cw > maxLineW) lines.push([]);
|
|
2490
|
+
lines[lines.length - 1].push({ label, cw });
|
|
2491
|
+
});
|
|
2492
|
+
return { lines, rowH: lines.length * chipH + (lines.length - 1) * chipGapY + 22 };
|
|
2493
|
+
});
|
|
2494
|
+
const H = spec.height || topY + Math.max(layout.reduce((a, l) => a + l.rowH + GAP, 0), ROWH + GAP) + 12;
|
|
2474
2495
|
const p = [];
|
|
2475
|
-
p.push(
|
|
2496
|
+
p.push(svgOpen(W, H, font));
|
|
2476
2497
|
if (!transparent) p.push(`<rect x="0" y="0" width="${W}" height="${H}" fill="${bg}"/>`);
|
|
2477
2498
|
if (title) p.push(`<text x="${PAD}" y="31" font-size="${fs + 5}" ${wAttr(spec, 700)} fill="${titleCol}">${esc(title)}</text>`);
|
|
2499
|
+
let ty = topY;
|
|
2478
2500
|
tiers.forEach((t, i) => {
|
|
2479
|
-
const y =
|
|
2501
|
+
const y = ty;
|
|
2502
|
+
const { lines, rowH } = layout[i];
|
|
2480
2503
|
const col = t.color || palette[i % palette.length];
|
|
2481
|
-
p.push(`<rect x="${PAD}" y="${r(y)}" width="${LABELW}" height="${
|
|
2482
|
-
p.push(`<text x="${r(PAD + LABELW / 2)}" y="${r(y +
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
const
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2504
|
+
p.push(`<rect x="${PAD}" y="${r(y)}" width="${LABELW}" height="${rowH}" rx="6" fill="${col}"/>`);
|
|
2505
|
+
p.push(`<text x="${r(PAD + LABELW / 2)}" y="${r(y + rowH / 2 + 6)}" text-anchor="middle" font-size="${fs + 4}" ${wAttr(spec, 800)} fill="${contrastColor(col)}">${esc(t.label || "")}</text>`);
|
|
2506
|
+
lines.forEach((line, j) => {
|
|
2507
|
+
let cx = chipX0;
|
|
2508
|
+
const cy = y + 11 + j * (chipH + chipGapY);
|
|
2509
|
+
line.forEach(({ label, cw }) => {
|
|
2510
|
+
p.push(`<rect x="${r(cx)}" y="${r(cy)}" width="${cw}" height="26" rx="6" fill="${chipBg}"/>`);
|
|
2511
|
+
p.push(`<text x="${r(cx + cw / 2)}" y="${r(cy + 18)}" text-anchor="middle" font-size="${fs}"${wOpt(spec)} fill="${chipTx}">${esc(label)}</text>`);
|
|
2512
|
+
cx += cw + chipGapX;
|
|
2513
|
+
});
|
|
2491
2514
|
});
|
|
2515
|
+
ty += rowH + GAP;
|
|
2492
2516
|
});
|
|
2493
2517
|
if (watermark) p.push(`<text x="${W - 10}" y="${H - 9}" text-anchor="end" font-size="9" fill="${faint}" opacity="0.6">slickfast.com</text>`);
|
|
2494
2518
|
p.push(`</svg>`);
|
|
@@ -2512,7 +2536,7 @@ function renderSwot(spec) {
|
|
|
2512
2536
|
const W = spec.width || 640, H = spec.height || 440;
|
|
2513
2537
|
const cellW = (W - PAD * 2 - GAP) / 2, cellH = (H - topY - PAD - GAP) / 2;
|
|
2514
2538
|
const p = [];
|
|
2515
|
-
p.push(
|
|
2539
|
+
p.push(svgOpen(W, H, font));
|
|
2516
2540
|
if (!transparent) p.push(`<rect x="0" y="0" width="${W}" height="${H}" fill="${bg}"/>`);
|
|
2517
2541
|
if (title) p.push(`<text x="${r(W / 2)}" y="33" text-anchor="middle" font-size="${fs + 5}" ${wAttr(spec, 700)} fill="${titleCol}">${esc(title)}</text>`);
|
|
2518
2542
|
for (let i = 0; i < 4; i++) {
|
|
@@ -2584,7 +2608,7 @@ function renderDashboard(spec) {
|
|
|
2584
2608
|
const cellW = (W - PAD * 2 - gap * (cols - 1)) / cols;
|
|
2585
2609
|
const cellH = (H - PAD * 2 - titleH - gap * (totalRows - 1)) / totalRows;
|
|
2586
2610
|
const p = [];
|
|
2587
|
-
p.push(
|
|
2611
|
+
p.push(svgOpen(W, H, font));
|
|
2588
2612
|
if (!transparent) p.push(`<rect x="0" y="0" width="${W}" height="${H}" fill="${bg}"/>`);
|
|
2589
2613
|
if (title) p.push(`<text x="${r(W / 2)}" y="33" text-anchor="middle" font-size="${fs + 7}" ${wAttr(spec, 700)} fill="${titleCol}">${esc(title)}</text>`);
|
|
2590
2614
|
if (!n) {
|
|
@@ -3292,9 +3316,11 @@ server.registerTool("report_issue", {
|
|
|
3292
3316
|
"_Reported via the SlickFast report_issue tool._"
|
|
3293
3317
|
].filter(Boolean).join("\n\n");
|
|
3294
3318
|
const mailto = `mailto:${FEEDBACK_EMAIL}?subject=${encodeURIComponent("SlickFast bug: " + summary)}&body=${encodeURIComponent(body)}`;
|
|
3295
|
-
const
|
|
3319
|
+
const ghIssue = `https://github.com/SlickFast/slickfast/issues/new?labels=bug&title=${encodeURIComponent("[bug] " + summary)}&body=${encodeURIComponent(body)}`;
|
|
3320
|
+
const text = `Bug report ready \u2014 **nothing has been sent.** To submit, pick either:
|
|
3296
3321
|
|
|
3297
|
-
${
|
|
3322
|
+
- **GitHub issue** (preferred \u2014 public, trackable): ${ghIssue}
|
|
3323
|
+
- **Email** (opens your mail client): ${mailto}
|
|
3298
3324
|
|
|
3299
3325
|
Or copy the report below into an email to ${FEEDBACK_EMAIL}:
|
|
3300
3326
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@slickfast/mcp",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.7",
|
|
4
|
+
"mcpName": "com.slickfast/mcp",
|
|
5
|
+
"homepage": "https://slickfast.com",
|
|
6
|
+
"repository": { "type": "git", "url": "git+https://github.com/SlickFast/slickfast.git", "directory": "apps/mcp" },
|
|
7
|
+
"bugs": { "url": "https://github.com/SlickFast/slickfast/issues", "email": "feedback@slickfast.com" },
|
|
4
8
|
"description": "SlickFast — render 47 chart & info-design types (bar, line, pie, KPI, cards, funnel, matrix, gauge, calendar…) plus multi-chart dashboards tiled into one image, as SVG/PNG via MCP. Local and deterministic; nothing leaves your machine.",
|
|
5
9
|
"keywords": ["mcp", "model-context-protocol", "chart", "charts", "dashboard", "data-visualization", "svg", "png", "ai", "agent", "claude", "llm", "deterministic"],
|
|
6
10
|
"license": "AGPL-3.0-only",
|