@bicharts/chart-mcp 0.6.90 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bicharts/chart-mcp",
3
- "version": "0.6.90",
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.90",
51
- "@bicharts/shape-core": "^0.6.90",
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 colour
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 catalogue, so it cannot drift from what
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.33 or later; every name they import
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 colour or filter by). |
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. A few are worth one question when they matter and the user
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); // honours <base href> on a sub-path host
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 colour
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 colours in CSS
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 catalogue. The catalogue goes past the template's own charts, and these are the ones people describe
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 favourite), and `token()` for a link.
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 behaviour,
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.