@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.
Files changed (3) hide show
  1. package/README.md +43 -22
  2. package/dist/index.js +80 -54
  3. package/package.json +5 -1
package/README.md CHANGED
@@ -1,30 +1,43 @@
1
1
  # SlickFast — charts & dashboards for AI agents
2
2
 
3
- SlickFast turns a small JSON spec into a polished, retina-quality chart **47 chart and
4
- information-design types** (bar, line, pie, KPI, cards, funnel, gauge, heatmap, calendar,
5
- gantt, waterfall…), plus **multiple charts tiled into a single dashboard image in one call**.
6
- It runs as an [MCP](https://modelcontextprotocol.io) tool, so an AI agent hands it a spec and
7
- gets back a finished PNG (or SVG). Everything renders **locally** nothing leaves your machine.
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 it's built for agents
15
-
16
- - **Deterministicsame spec, same bytes, forever.** No randomness, no timestamps, no
17
- headless-browser drift. Outputs are **cacheable, testable, and reproducible**, with zero
18
- flaky pixel diffs. Almost no charting tool can promise this and it's exactly what a
19
- tool-calling agent needs.
20
- - **Never throws garbage at the model.** Empty data, a bad tile, a filtered-to-nothing series →
21
- a clean, graceful frame, not a crash or a stack trace. Reliability is a feature when the
22
- caller is an agent, not a human who can eyeball the error.
23
- - **Fails loud on real mistakes.** An unknown palette or enum is *rejected* with a clear error
24
- listing the valid options so the agent fixes it immediately instead of silently shipping the
25
- wrong-looking chart.
26
- - **Good-looking by default.** A spec of just `{type, data}` renders a complete, well-designed
27
- chart; fonts, colors, palettes, and size are optional overrides.
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. Reach out for commercial terms.
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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
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(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${W} ${H}" width="${W}" height="${H}" font-family="${esc(font)}">`);
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(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${W} ${H}" width="${W}" height="${H}" font-family="${esc(font)}">`);
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(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${W} ${H}" width="${W}" height="${H}" font-family="${esc(font)}">`);
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(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${W} ${H}" width="${W}" height="${H}" font-family="${esc(font)}">`);
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(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${W} ${H}" width="${W}" height="${H}" font-family="${esc(font)}">`);
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(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${W} ${H}" width="${W}" height="${H}" font-family="${esc(font)}">`);
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(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${W} ${H}" width="${W}" height="${H}" font-family="${esc(font)}">`);
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(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${W} ${H}" width="${W}" height="${H}" font-family="${esc(font)}">`);
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(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${W} ${H}" width="${W}" height="${H}" font-family="${esc(font)}">`);
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(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${W} ${H}" width="${W}" height="${H}" font-family="${esc(font)}">`);
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(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${W} ${H}" width="${W}" height="${H}" font-family="${esc(font)}">`);
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(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${W} ${H}" width="${W}" height="${H}" font-family="${esc(font)}">`);
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(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${W} ${H}" width="${W}" height="${H}" font-family="${esc(font)}">`);
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(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${W} ${H}" width="${W}" height="${H}" font-family="${esc(font)}">`);
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(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${W} ${H}" width="${W}" height="${H}" font-family="${esc(font)}">`);
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(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${W} ${H}" width="${W}" height="${H}" font-family="${esc(font)}">`);
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(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${W} ${H}" width="${W}" height="${H}" font-family="${esc(font)}">`);
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(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${W} ${H}" width="${W}" height="${H}" font-family="${esc(font)}">`);
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(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${W} ${H}" width="${W}" height="${H}" font-family="${esc(font)}">`);
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(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${W} ${H}" width="${W}" height="${H}" font-family="${esc(font)}">`);
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(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${W} ${H}" width="${W}" height="${H}" font-family="${esc(font)}">`);
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(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${W} ${H}" width="${W}" height="${H}" font-family="${esc(font)}">`);
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(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${W} ${H}" width="${W}" height="${H}" font-family="${esc(font)}">`);
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(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${W} ${H}" width="${W}" height="${H}" font-family="${esc(font)}">`);
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(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${W} ${H}" width="${W}" height="${H}" font-family="${esc(font)}">`);
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(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${W} ${H}" width="${W}" height="${H}" font-family="${esc(font)}">`);
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(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${W} ${H}" width="${W}" height="${H}" font-family="${esc(font)}">`);
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(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${W} ${H}" width="${W}" height="${H}" font-family="${esc(font)}">`);
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(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${W} ${H}" width="${W}" height="${H}" font-family="${esc(font)}">`);
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(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${W} ${H}" width="${W}" height="${H}" font-family="${esc(font)}">`);
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(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${W} ${H}" width="${W}" height="${H}" font-family="${esc(font)}">`);
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(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${W} ${H}" width="${W}" height="${H}" font-family="${esc(font)}">`);
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(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${W} ${H}" width="${W}" height="${H}" font-family="${esc(font)}">`);
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(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${W} ${H}" width="${W}" height="${H}" font-family="${esc(font)}">`);
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(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${W} ${H}" width="${W}" height="${H}" font-family="${esc(font)}">`);
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(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${W} ${H}" width="${W}" height="${H}" font-family="${esc(font)}">`);
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(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${W} ${H}" width="${W}" height="${H}" font-family="${esc(font)}">`);
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, H = spec.height || topY + n * (ROWH + GAP) + 12;
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(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${W} ${H}" width="${W}" height="${H}" font-family="${esc(font)}">`);
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 = topY + i * (ROWH + GAP);
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="${ROWH}" rx="6" fill="${col}"/>`);
2482
- 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>`);
2483
- let cx = PAD + LABELW + 10;
2484
- (Array.isArray(t.items) ? t.items : []).forEach((it) => {
2485
- const label = typeof it === "string" ? it : it && it.label || "";
2486
- const cw = Math.round(label.length * (fs * 0.6) + 18);
2487
- if (cx + cw > W - PAD) return;
2488
- p.push(`<rect x="${r(cx)}" y="${r(y + ROWH / 2 - 13)}" width="${cw}" height="26" rx="6" fill="${chipBg}"/>`);
2489
- p.push(`<text x="${r(cx + cw / 2)}" y="${r(y + ROWH / 2 + 5)}" text-anchor="middle" font-size="${fs}"${wOpt(spec)} fill="${chipTx}">${esc(label)}</text>`);
2490
- cx += cw + 8;
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(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${W} ${H}" width="${W}" height="${H}" font-family="${esc(font)}">`);
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(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${W} ${H}" width="${W}" height="${H}" font-family="${esc(font)}">`);
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 text = `Bug report ready \u2014 **nothing has been sent.** To submit, click this link (it opens your mail client):
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
- ${mailto}
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.5",
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",