@bicharts/chart-mcp 0.6.89 → 0.6.91
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 +3 -1
- package/dist/check-chart.mjs +94 -94
- package/dist/index.mjs +85 -85
- package/package.json +3 -3
- package/skills/bic-charts/SKILL.md +26 -15
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bicharts/chart-mcp",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.91",
|
|
4
4
|
"description": "MCP (Model Context Protocol) stdio server for BIC AI charts: profile a dataset locally, then generate chart code that has passed the BIC backend's render gates. Works with any MCP-capable client (Claude Code, Claude Desktop, Cursor, Copilot Studio). An account pays to author charts; the code it writes is yours and runs with no licence.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mcp",
|
|
@@ -47,8 +47,8 @@
|
|
|
47
47
|
},
|
|
48
48
|
"// dependencies": "NONE, deliberately. dist/ bundles every module the server uses, so a runtime dependency installs nothing it needs - it only makes npx fetch that package (and its tree) before the first start. Everything the bundle carries is a devDependency; scripts/verify-package.mjs fails the release if a dependency appears or a shipped file imports anything but Node's built-ins.",
|
|
49
49
|
"devDependencies": {
|
|
50
|
-
"@bicharts/chart-host": "^0.6.
|
|
51
|
-
"@bicharts/shape-core": "^0.6.
|
|
50
|
+
"@bicharts/chart-host": "^0.6.91",
|
|
51
|
+
"@bicharts/shape-core": "^0.6.91",
|
|
52
52
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
53
53
|
"@types/node": "^20.0.0",
|
|
54
54
|
"@types/papaparse": "^5.3.14",
|
|
@@ -42,7 +42,7 @@ those which defaulted, paying attention to credit score"* wants `LoanId`, `Defau
|
|
|
42
42
|
`CreditScore`, filtered to six months.
|
|
43
43
|
|
|
44
44
|
**A highlight is a COLUMN, not a styling note.** "Highlighting those which defaulted" means
|
|
45
|
-
`Defaulted` travels as its own categorical column. There is no instruction channel that can
|
|
45
|
+
`Defaulted` travels as its own categorical column. There is no instruction channel that can color
|
|
46
46
|
rows the data does not distinguish.
|
|
47
47
|
|
|
48
48
|
**GRAIN is a choice, and the default is usually wrong for these charts.** Almost every BI query
|
|
@@ -59,7 +59,7 @@ question, and nothing downstream will flag it. Ask yourself which you meant:
|
|
|
59
59
|
|
|
60
60
|
**Aim with `CHART-SHAPES.md`** (beside this file). It lists every chart the engine can build and
|
|
61
61
|
what each one needs — minimum columns by role, category ceilings, draw caps, viewport floors,
|
|
62
|
-
which renderers can draw it. It is GENERATED from the chart
|
|
62
|
+
which renderers can draw it. It is GENERATED from the chart catalog, so it cannot drift from what
|
|
63
63
|
the engine will actually accept. Read the LIMITS column carefully: **the units differ** — a sankey
|
|
64
64
|
is capped on EDGES and a chord on NODES, so `<=80 edges` is the tighter constraint on the same
|
|
65
65
|
picture, not the looser one.
|
|
@@ -118,7 +118,7 @@ npm install -D esbuild
|
|
|
118
118
|
`npm init -y` line again before retrying.
|
|
119
119
|
- **`global.json`** pins the build to the newest installed .NET 10 SDK. Without it a machine that
|
|
120
120
|
also has a newer or preview SDK builds with that one.
|
|
121
|
-
- These blocks are written against `@bicharts/chart-host` 0.6.
|
|
121
|
+
- These blocks are written against `@bicharts/chart-host` 0.6.86 or later; every name they import
|
|
122
122
|
is exported there.
|
|
123
123
|
- npm 11 may warn that esbuild's postinstall is "not yet covered by allowScripts". The build doesn't
|
|
124
124
|
need it: esbuild's platform binary arrives as an ordinary dependency, and the bundling target runs.
|
|
@@ -169,7 +169,7 @@ correction:
|
|
|
169
169
|
| Parameter | Use it when |
|
|
170
170
|
| --- | --- |
|
|
171
171
|
| `rows_policy: "all"` | every source row must appear — a row-per-entity table, a point per location. Without it the engine may legitimately show a Top-N with an "Other" rollup. |
|
|
172
|
-
| `required_columns: [...]` | a field must be visible because your app's interactions depend on it (e.g. the column you
|
|
172
|
+
| `required_columns: [...]` | a field must be visible because your app's interactions depend on it (e.g. the column you color or filter by). |
|
|
173
173
|
| `forbid_derived_columns: true` | your app owns the analytics; the chart is only the display. Stops per-capita / share-of-total / rank columns being added. |
|
|
174
174
|
|
|
175
175
|
Asking for these in `prompt` prose instead is the most expensive mistake available here: it
|
|
@@ -189,7 +189,7 @@ engine picked, what was dropped), for a client that shows only the text. `engine
|
|
|
189
189
|
the server reported what it picked; when it is `unavailable` the engine may have picked where a row
|
|
190
190
|
reads `default`, so never tell the user it picked nothing. Most settings are
|
|
191
191
|
passed only when the user has said them in their own words: a title, data labels, a legend position,
|
|
192
|
-
a limit, a date grain, an overlay, a font.
|
|
192
|
+
a limit, a date grain, an overlay, a font. Three are worth one question whenever the user
|
|
193
193
|
hasn't said: `chart_style` (which family of charts to consider: the familiar forms or the other types), `level_of_detail` (how much of
|
|
194
194
|
the data they want to see: every observation, a distribution, or one mark per category) and
|
|
195
195
|
`culture` (their language and region), and, once the chart is drawn, the live options
|
|
@@ -226,10 +226,13 @@ Import the generated code as raw text and the sample payload as JSON:
|
|
|
226
226
|
```tsx
|
|
227
227
|
import * as d3 from "d3";
|
|
228
228
|
import { BicChart, BicChartGroup } from "@bicharts/chart-host/react";
|
|
229
|
+
import { createLegendFitPass } from "@bicharts/chart-host/legend-fit"; // chart-host 0.6.86 or later
|
|
229
230
|
import mapCode from "./charts/map/chart.js?raw";
|
|
230
231
|
import tableCode from "./charts/table/chart.js?raw";
|
|
231
232
|
import payload from "./charts/map/data.sample.json";
|
|
232
233
|
|
|
234
|
+
const LAYOUT_PASS = createLegendFitPass(); // once, at module scope: a new function on each render rebuilds the host
|
|
235
|
+
|
|
233
236
|
// data.sample.json rows are POSITIONAL arrays; the group wants row OBJECTS.
|
|
234
237
|
const columns = payload.columns;
|
|
235
238
|
const rows = payload.rows.map(r =>
|
|
@@ -240,8 +243,8 @@ const rows = payload.rows.map(r =>
|
|
|
240
243
|
|
|
241
244
|
```tsx
|
|
242
245
|
<BicChartGroup columns={columns} rows={rows} point={{ city: "City", state: "StateOrProvince" }}>
|
|
243
|
-
<BicChart id="map" code={mapCode} d3={d3} geoKind="north-america" respondsWith="highlight" />
|
|
244
|
-
<BicChart id="table" code={tableCode} d3={d3} />
|
|
246
|
+
<BicChart id="map" code={mapCode} d3={d3} geoKind="north-america" respondsWith="highlight" layoutPass={LAYOUT_PASS} />
|
|
247
|
+
<BicChart id="table" code={tableCode} d3={d3} layoutPass={LAYOUT_PASS} />
|
|
245
248
|
</BicChartGroup>
|
|
246
249
|
```
|
|
247
250
|
|
|
@@ -277,9 +280,10 @@ Ctrl/Cmd/Shift-click adds or removes one mark, and a click on empty canvas clear
|
|
|
277
280
|
```js
|
|
278
281
|
import * as d3base from "d3";
|
|
279
282
|
import { assembleD3, createChartGroup, createChartHost, loadGeo } from "@bicharts/chart-host";
|
|
283
|
+
import { createLegendFitPass } from "@bicharts/chart-host/legend-fit";
|
|
280
284
|
|
|
281
285
|
const d3 = assembleD3(d3base); // + any plugins a chart's contract names
|
|
282
|
-
const at = (path) => new URL(path, document.baseURI); //
|
|
286
|
+
const at = (path) => new URL(path, document.baseURI); // honors <base href> on a sub-path host
|
|
283
287
|
async function text(path) {
|
|
284
288
|
const r = await fetch(at(path));
|
|
285
289
|
if (!r.ok) throw new Error(`${path}: HTTP ${r.status}`);
|
|
@@ -318,6 +322,7 @@ export async function createGroup(sampleUrl, dotnet) {
|
|
|
318
322
|
code, d3, geoKind: spec.geoKind || undefined,
|
|
319
323
|
data: group.memberPayload(spec.id, opts).payload,
|
|
320
324
|
options: { width: Math.floor(el.getBoundingClientRect().width), height: spec.height, ...theme(el) },
|
|
325
|
+
layoutPass: createLegendFitPass(), // after every render: a legend slid clear of the plot, text brought inside the frame
|
|
321
326
|
});
|
|
322
327
|
host.render();
|
|
323
328
|
const member = group.attach(spec.id, host, opts); // its clicks publish SOURCE rows
|
|
@@ -475,7 +480,7 @@ Add `@using MyCharts.Components` to `_Imports.razor`, then on a page:
|
|
|
475
480
|
the tool resolved.
|
|
476
481
|
- **Restyle** - a chart honours only the options its code reads, so a live restyle control has to
|
|
477
482
|
set one of those. The generate result's `optionsRead` lists them for that chart, and its
|
|
478
|
-
`integrationContract` says what each does. The common ones: `palette` (a categorical
|
|
483
|
+
`integrationContract` says what each does. The common ones: `palette` (a categorical color
|
|
479
484
|
list), `colorScaleLow`/`colorScaleHigh` (a sequential ramp's ends, what a choropleth reads),
|
|
480
485
|
`maxMapPoints` (a point map's cap). An option not in `optionsRead` does nothing on that chart.
|
|
481
486
|
- **Hover drop lines** - a 3D scatter can draw, on hover, a line from a point to each back wall of
|
|
@@ -498,7 +503,7 @@ Add `@using MyCharts.Components` to `_Imports.razor`, then on a page:
|
|
|
498
503
|
`.bic-chart` its width in CSS (a block element in a sized column); the height is the
|
|
499
504
|
`Height` parameter. A `display:none` container measures 0 and draws nothing.
|
|
500
505
|
- **Light and dark** - `theme(el)` reads the background the chart sits on, so the chart follows the
|
|
501
|
-
PAGE. For a page that follows the reader, switch the page's own
|
|
506
|
+
PAGE. For a page that follows the reader, switch the page's own colors in CSS
|
|
502
507
|
(`@media (prefers-color-scheme: dark) { ... }` on `body` or the chart's card) and the charts follow
|
|
503
508
|
on the next `change` of the media query.
|
|
504
509
|
|
|
@@ -559,7 +564,7 @@ chart's grain.
|
|
|
559
564
|
**4. Ask before choosing, then choose what answers the question.** `list_eligible_charts` on that
|
|
560
565
|
data. Pass `chart_type` to `generate_chart` only for a type the person named; when they only asked for a chart, leave it
|
|
561
566
|
unset and the engine chooses from what fits. A type the person described without naming it counts as named once you have
|
|
562
|
-
matched it to the
|
|
567
|
+
matched it to the catalog. The catalog goes past the template's own charts, and these are the ones people describe
|
|
563
568
|
without naming:
|
|
564
569
|
|
|
565
570
|
| the request sounds like | ask for |
|
|
@@ -581,7 +586,7 @@ slider sets the rate anyway.
|
|
|
581
586
|
queries at run time (`useSemanticModelQuery`), so the payload is rebuilt from live rows every time -
|
|
582
587
|
never hand-built. **With `out_dir`, a D3 chart arrives with `chart.component.tsx` beside `chart.js`: a component that
|
|
583
588
|
already does all of this** - the columns, the binding (`geo`, `point`, `destination`), the geometry kind, the
|
|
584
|
-
d3 plugins the chart calls, and `rowsFromTable`, which maps a DAX result's `Table[Column]` and `[Measure]`
|
|
589
|
+
d3 plugins the chart calls, the host's legend and frame fit (`layoutPass`), and `rowsFromTable`, which maps a DAX result's `Table[Column]` and `[Measure]`
|
|
585
590
|
headers onto the chart's plain names. Use it as it is:
|
|
586
591
|
|
|
587
592
|
```tsx
|
|
@@ -608,6 +613,7 @@ host by hand instead - `generate_chart` returns `binding`; pass all three:
|
|
|
608
613
|
```tsx
|
|
609
614
|
import * as d3 from "d3";
|
|
610
615
|
import { BicChart, BicChartGroup } from "@bicharts/chart-host/react";
|
|
616
|
+
import { createLegendFitPass } from "@bicharts/chart-host/legend-fit";
|
|
611
617
|
import code from "@/charts/occupancy-map/chart.js?raw";
|
|
612
618
|
// Saved from the generate_chart result: columns = data.columns, binding = binding, geoKind = geo.kind.
|
|
613
619
|
import { columns, binding, geoKind } from "@/charts/occupancy-map/meta";
|
|
@@ -616,6 +622,8 @@ import { columns, binding, geoKind } from "@/charts/occupancy-map/meta";
|
|
|
616
622
|
const RENAME = { "Geography[CountryIso3]": "CountryIso3", "Geography[CountryName]": "CountryName",
|
|
617
623
|
"[Average Daily Rate]": "AverageDailyRate", "[Occupancy %]": "Occupancy" };
|
|
618
624
|
|
|
625
|
+
const LAYOUT_PASS = createLegendFitPass(); // once, at module scope: a new function on each render rebuilds the host
|
|
626
|
+
|
|
619
627
|
function CountryMap({ table, onPick }) { // table = the live query result
|
|
620
628
|
const rows = table.rows.map(r => Object.fromEntries(
|
|
621
629
|
table.columns.map((c, i) => [RENAME[c.name] ?? c.name, r[i]])));
|
|
@@ -623,7 +631,7 @@ function CountryMap({ table, onPick }) { // table = the live query resu
|
|
|
623
631
|
<BicChartGroup columns={columns} rows={rows}
|
|
624
632
|
geo={binding.geo} point={binding.point} destination={binding.destination}>
|
|
625
633
|
<BicChart id="map" code={code} d3={d3} geoKind={geoKind}
|
|
626
|
-
options={{ width, height }}
|
|
634
|
+
options={{ width, height }} layoutPass={LAYOUT_PASS}
|
|
627
635
|
onSelect={idxs => onPick(idxs.length ? rows[idxs[0]].CountryIso3 : null)} />
|
|
628
636
|
</BicChartGroup>
|
|
629
637
|
);
|
|
@@ -636,6 +644,9 @@ function CountryMap({ table, onPick }) { // table = the live query resu
|
|
|
636
644
|
basemap but no join, so its `binding.geo` is null.
|
|
637
645
|
- **`<BicChartGroup destination>` needs `@bicharts/chart-host` 0.6.50 or later.** Before it, a flow
|
|
638
646
|
map's far end is not built.
|
|
647
|
+
- **`LAYOUT_PASS` is built once, at module scope, and needs `@bicharts/chart-host` 0.6.86 or later.** After
|
|
648
|
+
every render it slides a legend clear of the plot and brings text that runs past the frame back inside; the
|
|
649
|
+
chart component `generate_chart` writes does the same, and a new function on each render would rebuild the host.
|
|
639
650
|
|
|
640
651
|
**Layout: a chart is never clipped or squashed.** Charts sit in cards, and a card is where they get cut off.
|
|
641
652
|
- Render every chart inside `BicChartPanel`. It sizes the chart from its cell, never from the chart.
|
|
@@ -708,7 +719,7 @@ Anything saved against a mark - a note, a flag - is keyed the same way, never by
|
|
|
708
719
|
More the page gets from the same filters, each one line:
|
|
709
720
|
- A link that opens the page as it was: `useBicUrlState()` in the page component (after its `useBicFilter` /
|
|
710
721
|
`useBicControls` calls) keeps every filter and every chart's controls in `?view=`. `useBicView()` gives `save()` /
|
|
711
|
-
`restore(view)` for a saved view of your own (a user's
|
|
722
|
+
`restore(view)` for a saved view of your own (a user's favorite), and `token()` for a link.
|
|
712
723
|
- Linked hover: `useBicFilter("CountryIso3", { hover: true })` - hovering a mark lights the same key's marks on every
|
|
713
724
|
chart bound to that filter (a glow, never a selection).
|
|
714
725
|
- A filter that narrows a query to nothing: `<BicChartPanel ... onClearFilters={country.clear}>` says so with a Clear,
|
|
@@ -835,7 +846,7 @@ chart in a `BicChartPanel`), draws every chart together, and compares the stack
|
|
|
835
846
|
the line and the call to use instead; fix the ERRORs in the app's own files before deploying.
|
|
836
847
|
You don't need to deploy to check a chart. The template's `validate:visual` checks `VegaVisual` factories; a
|
|
837
848
|
RegiaBI chart's check is `check_chart` - don't list it there. `check_chart` and `check_app` check the charts and how
|
|
838
|
-
the app uses them; the template's app-validation in the portal embed still applies. Then `npx tsc -b` and the tests. To test the page's
|
|
849
|
+
the app uses them; the template's app-validation in the portal embed still applies. Then `npx tsc -b` and the tests. To test the page's behavior,
|
|
839
850
|
`@bicharts/chart-host/testing` has a reader's gestures - `clickMark`, `clickEmpty`, `selectedRows` - for the checks
|
|
840
851
|
worth keeping: a second click on a mark clears its filter, and a page Clear clears the chart's marks. Deploying (`npx rayfin up`), on Rayfin 1.36.1:
|
|
841
852
|
- Signed in? `npx rayfin login status` (a subcommand, not a `--status` flag); `npx rayfin login` if not.
|