@bicharts/chart-mcp 0.5.54 → 0.5.55

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 (2) hide show
  1. package/dist/index.mjs +1 -1
  2. package/package.json +3 -3
package/dist/index.mjs CHANGED
@@ -139,7 +139,7 @@ try {
139
139
  `);let n=a.code||"",r=N=>N.test(n),o=!!a.geoKind||r(/options\.geo\b|__geoIso__/),i=r(/__geoIso__/),s=n.includes(A7)||r(/animPlaySpeedMs|animMaxIdealFrames|animTimelineStyle|animAutoPlay/),l=n.includes(R8),u=n.includes(H4),c=r(/\boptions\.onSelect\b|\bonSelect\b/),d=r(/colorScaleLow|colorScaleHigh/),p=r(/options\.aggregation\b/),m=r(/themeFg|themeAccent/),h=r(/options\.pageSize\b|llmPager/),g=r(/__geoLat__|__geoLon__/),y=[{re:/d3\.sankey|sankeyLinkHorizontal|sankeyJustify|sankeyCenter/,spec:"d3-sankey@0.12.3",named:"sankey, sankeyLinkHorizontal, sankeyLeft, sankeyRight, sankeyCenter, sankeyJustify"},{re:/d3\.hexbin|hexbin\(/,spec:"d3-hexbin@0.2.2",named:"hexbin"},{re:/weightedVoronoi/,spec:"d3-weighted-voronoi@1.1.3",named:"weightedVoronoi"},{re:/voronoiMap\b/,spec:"d3-voronoi-map@2.1.1",named:"voronoiMap",deps:["d3-weighted-voronoi@1.1.3"]},{re:/voronoiTreemap/,spec:"d3-voronoi-treemap@1.1.2",named:"voronoiTreemap",deps:["d3-weighted-voronoi@1.1.3","d3-voronoi-map@2.1.1"]}],b=y.filter(N=>r(N.re)),S=Array.from(new Set(b.flatMap(N=>[...N.deps??[],N.spec]))),k=S.map(N=>{let A=N.split("@")[0],X=y.find(B=>B.spec===N)?.named??A.replace(/^d3-/,"").replace(/-([a-z])/g,(B,j)=>j.toUpperCase());return{pkg:A,named:X}}),w=r(/maxMapPoints/),x=r(/geoLandColor|geoNoDataColor/),M=[`INTEGRATION CONTRACT (D3 \u2014 a portable function, merge it anywhere) [host contract v${I8}]`,""," BEFORE YOU HAND-ROLL: @bicharts/chart-host is the reference host implementation."," It does everything in this document \u2014 compiles the code, applies the option"," defaults, resolves mark clicks (including through tooltip overlays), owns the"," selection affordance, translates row indices between cross-filtered charts, and"," handles teardown. React bindings: <BicChart> / <BicChartGroup>.",nv," Everything below is the FULL HAND-ROLL SPEC \u2014 what to build if you are not using"," it, and what it is doing for you if you are.",""," Entry point: function render(container, data, options)"," \u2022 container \u2014 a DOM element to draw into (the function owns its contents)."," It MUST have a resolved width/height BEFORE you call render: the chart draws"," once at whatever size it is handed and does not re-measure. A container inside"," a display:none tab, or measured before layout, yields a 0px chart forever."," \u2022 Requires D3 v7. The code reads a bare `d3` identifier, so it needs d3 either as"," a global (`window.d3 = d3`) OR injected as a parameter by whatever compiles it"," \u2014 @bicharts/chart-host's compileRenderFn injects it, so in a bundled app there is"," NO global and you pass your imported d3 in.",' npm install d3@7 // then: import * as d3 from "d3";',...b.length?[` \u2022 D3 PLUGINS \u2014 this chart calls ${k.map(N=>N.pkg).join(", ")}.`," These are SEPARATE packages that attach onto the d3 object; the generated"," code just calls d3.<plugin>(). Install them, then augment the same d3 you"," hand to the chart:",` npm install ${S.join(" ")}`,...b.some(N=>N.deps?.length)?[" (install order above is also the ATTACH order \u2014 the dependency packages"," must augment d3 before the one that builds on them)"]:[],' import * as d3 from "d3";',...k.map(N=>` import { ${N.named} } from "${N.pkg}";`),` Object.assign(d3, { ${k.map(N=>N.named).join(", ")} });`]:[]," \u2022 It is a plain function \u2014 drop it into any app; call render() yourself.",""," data = { columns, rows } (build it in THIS exact shape from YOUR data):"," \u2022 columns: array of { name, dataType, isMeasure } in fixed order:",t," (trailing __rowIdx__ = HOST METADATA, the row's index"+(i?"; __geoIso__ = the ISO/USPS join key this tool computed, null = unmatched).":g?"; __geoLat__/__geoLon__ = coordinates this tool resolved, null = unplaceable).":").")," \u2022 rows: array of POSITIONAL arrays aligned to columns:",i?" [ v0, v1, \u2026, vN, rowIndex, isoCode ] // last two = __rowIdx__, __geoIso__":g?" [ v0, v1, \u2026, vN, rowIndex, lat, lon ] // last three = __rowIdx__, __geoLat__, __geoLon__":" [ v0, v1, \u2026, vN, rowIndex ] // last cell = that row's __rowIdx__"," DateTime cells are ISO-8601 strings. Missing values are null.",""," options = {"," width, height, // px"," palette: string[], // hex colors; [] = the code's own defaults"," backgroundColor, cultureCode, // '' acceptable"];return m&&M.push(" themeFg, themeAccent, // foreground / accent hex (label + play-position color)"),M.push(" allowTooltips: boolean,"),d&&M.push(" colorScaleLow, colorScaleHigh, // force the sequential scale endpoints (live restyle)"),p&&M.push(" aggregation, // '' | sum|average|median|min|max|count|distinct (live restyle)"),h&&M.push(" pageSize: number, // rows shown at a time (live restyle). 0 or absent ="," // NO PAGING: every row is drawn and a body taller"," // than the frame scrolls. This chart's rows are a"," // LIST, so a long one is normal, not an error."),i&&M.push(" geo: FeatureCollection, // REQUIRED \u2014 the geometry (feature.id = join code);"," // supplied by this tool (data.geo.json / preview)"," geoUnmatched: { count, examples }, // drives the 'N regions unmatched' annotation"),g&&M.push(" geo: FeatureCollection, // REQUIRED \u2014 the BASEMAP (context land, no join);"," // supplied by this tool (data.geo.json / preview)"," geoPoint: { // what the geocoding actually achieved \u2014 pass it"," precision, // 'latlon'|'zip'|'city'|'state': COARSEST tier used"," unplaced, unplacedExamples, // rows with no resolvable coordinate"," ambiguousRows }, // placed by a largest-match tie-break"," // The chart annotates these; omitting it makes a"," // sparse/approximate map LOOK exact."),x&&M.push(" geoLandColor, geoNoDataColor, // basemap fills ('' = the code's own defaults)"),w&&M.push(" maxMapPoints, // cap on plotted points (default 1000). Over it, the"," // chart keeps the LARGEST N and says so on-chart."),s&&M.push(` animMaxIdealFrames, // ${Y0}..${Q0} (default ${P7}): target keyframe count \u2014 the`," // code re-grains dates to the finest grain <= this,"," // LIVE (see 'Animation' below)",` animPlaySpeedMs, // ${J0}..${q0} (default ${w7}): ms per period`," animTimelineStyle, // '' auto | 'line' | 'boxes'"," animAutoPlay, animStopAtEnd, filtersDuringPlay, // booleans"),M.push(" uiState, setUiState, // optional persisted view-state; {} + no-op is fine"," }",""),(u||l||c)&&(M.push(" Interactivity / cross-filter (what the bridge must do \u2014 chart-host does it):"),u&&M.push(" \u2022 Every selectable mark carries a data-row-idx attribute = the comma-joined"," __rowIdx__ values it represents. On a mark click, read data-row-idx, map the"," indices back to your rows, and update your filter/state, then re-render other"," panels with the filtered rows."," \u2022 ROW INDEX HAZARD \u2014 the one thing hand-rolled integrations get wrong."," __rowIdx__ is a position WITHIN THE PAYLOAD THAT CHART RECEIVED, not an id in"," your source table. The moment you re-render a chart with filtered rows, its"," payload renumbers from zero \u2014 so the SAME integer now denotes a DIFFERENT"," record, and comparing indices across two charts silently selects the wrong"," rows. It does not throw; it just quietly filters to the wrong thing."," Keep ONE source table plus a payload-row -> source-row map per chart, and"," translate on every hop. (<BicChartGroup> exists precisely to own this.)"," \u2022 Marks are MANY-TO-MANY with rows: a legend swatch, an aggregated bar or an"," axis-group label carries every row it covers, so treat a mark as selected"," when ANY of its rows is selected \u2014 that is what makes a legend click light up"," its swatch and its bars together."," \u2022 Clicks can be SWALLOWED by the chart's own tooltip/crosshair overlay: an"," overlay painted over the marks with a transparent (not `none`) fill still"," hit-tests, so event.target is the overlay and a naive closest('[data-row-idx]')"," finds nothing and reads as 'empty canvas'. Resolve a miss through"," document.elementsFromPoint before concluding the user clicked nothing."),l&&M.push(` \u2022 This chart also DISPATCHES a bubbling \`${R8}\` CustomEvent on the`," container when its selection changes (e.g. a scrubber period). Listen for it:",` container.addEventListener('${R8}', e => {`," if (e.detail.clear) { /* selection cleared */ }",` else { const idx = e.detail.mark.getAttribute('${H4}'); /* filter */ }`," });"," \u2022 Lifecycle slots the code sets on the container (call/read them from your host):",` container.${Z0}() // call to clear the chart's own selection`,` container.${V0} // a mark to apply as the initial filter (may be set)`,` container.${A7}() // CALL THIS before unmount / before re-rendering`),c?M.push(" \u2022 options.onSelect(rowIdxs:number[]) is also invoked when provided \u2014 wire it to"," your cross-filter if you prefer a callback over the DOM event."):M.push(" \u2022 NOTE: this code does NOT call options.onSelect \u2014 use data-row-idx"+(l?" + the event above.":".")),M.push("")),s&&M.push(" Animation (keyframe playback + LIVE re-grain):"," \u2022 The chart draws its own play/pause control + period scrubber. It reads the anim"," options above; sensible defaults apply if you omit them."," \u2022 DYNAMIC KEYFRAMES: the code chooses the time grain AT RENDER from"," options.animMaxIdealFrames \u2014 it truncates dates to the finest calendar grain"," (day/week/month/quarter/year/decade) whose bucket count <= that value and"," aggregates rows within each bucket. To change the granularity LIVE, just call"," render(container, data, {...options, animMaxIdealFrames: N}) again \u2014 NO new"," generation needed. (This is what the Power BI 'Max Ideal Frames' slider does.)",` \u2022 Call container.${A7}() before you re-render or unmount, to stop its timer.`,""),g&&M.push(" Geography (POINT MAP):"," \u2022 Coordinates are HOST-RESOLVED. This tool geocoded your place columns"," (city / state / zip, or lat+lon when present) and appended __geoLat__ and"," __geoLon__ to every row \u2014 the chart plots those columns directly and does NOT"," geocode anything itself. Build them the same way if you re-derive the data, or"," the map draws nothing: the code bails when it can't find the two columns."," \u2022 options.geo is the BASEMAP (grey context land), NOT a join target \u2014 there is no"," __geoIso__ and no feature.id lookup. Without it you get points on a blank field."," \u2022 Pass options.geoPoint through. It is how the chart tells the truth about"," precision (city-centroid vs exact) and about rows it could not place.",""),i&&M.push(" Geography:"," \u2022 This is a choropleth: it REQUIRES options.geo (a GeoJSON FeatureCollection whose"," feature.id is the join code) and the __geoIso__ column in data (both provided by"," this tool \u2014 see data.geo.json when out_dir is set). Without options.geo it renders"," an empty map. Do NOT fetch or invent geometry; pass what the tool supplied.",""),M.push(" HOST PAGE PREREQUISITES (@bicharts/chart-host does all of these for you):"," \u2022 SIZE THE CONTAINER FIRST. Measure synchronously (getBoundingClientRect in a"," layout effect) and pass width/height explicitly \u2014 they have NO defaults, and a"," chart handed undefined/0 draws once at nothing and never recovers. Use a"," ResizeObserver for LATER resizes only: gating the FIRST paint on it costs"," seconds in a headless/background tab, where frames are barely produced."," \u2022 PIN TYPOGRAPHY AT THE BOUNDARY. Charts set font-size but almost never"," line-height or letter-spacing, so they inherit YOURS. A page with"," `:root { font: 18px/145% }` \u2014 the stock Vite template does exactly this \u2014 hands"," an 11px chart label a 26px line box and slices it through the middle, on a chart"," that renders perfectly elsewhere. Set `line-height: normal; letter-spacing:"," normal` on the chart container (a chart that sets its own still wins)."," \u2022 SELECTION STYLING IS YOURS. Generated code emits marks and data-row-idx; it"," never styles 'selected', because only the host knows what is selected. Add"," `lch-has-selection` to the container and `lch-mark-selected` to selected marks,"," and dim the rest via the `--lch-dim-opacity` custom property (default 0.25)."," Re-apply after EVERY render or a restyle silently drops the highlight. A chart"," that rests its marks below full opacity by design stamps `d3-lift-selected` on"," their group: paint a SELECTED mark inside that group at full opacity, or it reads"," as dimmed."," \u2022 TEAR DOWN ON UNMOUNT. Call container.__llmAnimStop__() if present before you"," re-render or unmount. Under React StrictMode effects double-mount, so without"," this every animated chart leaks a running timer per mount, on every hot reload.",""," To re-render with filtered/updated data or changed options: call"," render(container, newData, newOptions) again (it repaints the container)."),M.join(`
140
140
  `)}function Sc(a,e){return e?on(a):[]}var sn={chartName:_.string().describe("The chart type the backend produced."),language:_.string().describe("Renderer/language of the code (D3, PYTHON, \u2026)."),version:_.number().describe("Server-side version number of this generation."),creditCost:_.number().nullable().describe("Credits billed, when the server reported them."),correlationId:_.string().describe("Ties this generation to the server-side log \u2014 quote it in any support request."),codeSummary:_.string().optional().describe("The backend's own one-line description of what it built."),warningMessage:_.string().optional().describe("Present when the chart drawn is NOT the chart requested: names the requested type, the reason it was overruled, and what was drawn instead. Surface it to the user verbatim \u2014 never suppress it because the chart rendered fine."),code:_.string().optional().describe("The render() source. Omitted when include_code is false."),requiredD3Plugins:_.array(_.string()).describe('D3 plugin PACKAGES this code calls (e.g. ["d3-sankey"]) \u2014 install and Object.assign onto the same d3 you pass the host, or the chart throws mid-render. Empty for core-only d3 and for non-D3 languages.'),geo:_.object({column:_.string().nullable().describe("Column joined to the geometry; null for a basemap-only point map."),kind:_.string().describe("Geometry asset kind \u2014 pass the matching geometry as options.geo."),hasGeometry:_.boolean().describe("False means no bundled geometry matched this kind: the map will draw no land."),unmatched:_.number().describe("Regions in the data with no geometry match."),unmatchedExamples:_.array(_.string())}).nullable().describe("Region/basemap wiring; null for non-geo charts."),geoPoint:_.object({placed:_.number(),total:_.number(),precision:_.string().nullable(),unplaced:_.number(),unplacedExamples:_.array(_.string()),ambiguousRows:_.number().describe("Rows placed by a largest-match tie-break rather than an exact one.")}).nullable().describe("Point-geocoding outcome; null when the chart geocodes no points."),data:_.any().optional().describe("The render payload {columns, rows, \u2026} to hand render() \u2014 INCLUDING any __geoIso__/__geoLat__/__geoLon__ columns the caller cannot reconstruct. Present only when out_dir was NOT given; otherwise read files.data."),files:_.object({code:_.string().optional(),data:_.string().optional(),geo:_.string().optional(),preview:_.string().optional()}).optional().describe("Absolute paths written, when out_dir was given."),hostContract:_.string().describe("Host contract version the code targets; a host on a different MAJOR may not interoperate."),build:_.string().describe("MCP server build stamp \u2014 the bundle actually serving this call.")},QR=_.object(sn);function bc(a){return`${a}
141
141
  export { render };
142
- `}function kc(a){let e=a.renderData.geoPoint,t=a.renderData.geoUnmatched;return{chartName:a.res.finalChartName||"",language:a.res.language,version:a.res.version,creditCost:a.res.creditCost??null,correlationId:a.res.correlationId,...a.res.codeSummary?{codeSummary:a.res.codeSummary}:{},...a.res.warningMessage?{warningMessage:a.res.warningMessage}:{},...a.withCode?{code:a.code}:{},requiredD3Plugins:a.plugins,geo:a.res.geoKind?{column:a.res.geoColumn||null,kind:a.res.geoKind,hasGeometry:a.hasGeometry,unmatched:t?.count??0,unmatchedExamples:t?.examples??[]}:null,geoPoint:e?{placed:a.renderData.rows.length-(e.unplaced??0),total:a.renderData.rows.length,precision:e.precision??null,unplaced:e.unplaced??0,unplacedExamples:e.unplacedExamples??[],ambiguousRows:e.ambiguousRows??0}:null,...a.files?{files:a.files}:{data:a.renderData},hostContract:a.hostContract,build:a.build}}Cc();Mc();Ac();/*! @bicharts/chart-host — Apache-2.0. Bundled reference data: GeoNames (https://www.geonames.org/) CC BY 4.0; Natural Earth and US Census/TIGER (public domain). Full text: NOTICE in this package. */function wc(a){if(!a)return;let e=a.toLowerCase();if(/^country/.test(e))return Ae;if(e==="us-state-code"||e==="us-state-name")return vc;if(e==="us-zip5")return Uc;if(e==="north-america")return ov();if(e==="world")return rv()}var ln;function rv(){return ln||(ln={type:"FeatureCollection",features:Ae.features.filter(a=>a?.geometry&&a.geometry.type!=="Point")}),ln}var un;function ov(){return un||(un={type:"FeatureCollection",features:Ae.features.filter(a=>ec.has(a?.id)&&a?.geometry&&a.geometry.type!=="Point").map(a=>tc(a,ac)).filter(a=>a!==null)}),un}import{readdirSync as iv,existsSync as sv,statSync as Pc}from"node:fs";import{join as cn}from"node:path";var lv=["package.json","tsconfig.json","jsconfig.json","deno.json","deno.jsonc","angular.json","svelte.config.js","vite.config.ts","vite.config.js","pnpm-lock.yaml","yarn.lock","package-lock.json"],uv=["pyproject.toml","requirements.txt","setup.py","setup.cfg","Pipfile","poetry.lock","environment.yml","environment.yaml","conda.yaml","tox.ini"],cv=new Set([".js",".jsx",".mjs",".cjs",".ts",".tsx"]),dv=new Set([".py",".pyi"]);function zc(a,e){return e.some(t=>sv(cn(a,t)))}function pv(a){let e=0,t=0,n=o=>{let i;try{i=iv(o)}catch{return}for(let s of i){if(s==="node_modules"||s===".git"||s.startsWith("."))continue;let l=cn(o,s),u=!1;try{u=Pc(l).isFile()}catch{continue}if(!u)continue;let c=s.lastIndexOf(".");if(c<0)continue;let d=s.slice(c).toLowerCase();cv.has(d)?e++:dv.has(d)&&t++}};n(a);let r=cn(a,"src");try{Pc(r).isDirectory()&&n(r)}catch{}return{js:e,py:t}}function dn(a=process.cwd()){let e=zc(a,lv),t=zc(a,uv);if(e&&!t)return{language:"javascript",reason:`found a JS/TS project marker in ${a}`};if(t&&!e)return{language:"python",reason:`found a Python project marker in ${a}`};let{js:n,py:r}=pv(a);return n>0&&r===0?{language:"javascript",reason:`found ${n} JS/TS source file(s) and no Python in ${a}`}:r>0&&n===0?{language:"python",reason:`found ${r} Python source file(s) and no JS/TS in ${a}`}:n>0&&r>0?n>=r*3?{language:"javascript",reason:`JS/TS files dominate (${n} vs ${r} Python) in ${a}`}:r>=n*3?{language:"python",reason:`Python files dominate (${r} vs ${n} JS/TS) in ${a}`}:{language:null,reason:`mixed JS/TS (${n}) and Python (${r}) in ${a}`}:e&&t?{language:null,reason:`both JS and Python project markers present in ${a}`}:{language:null,reason:`no JS or Python markers or source files found in ${a}`}}var gv="0.5.54",ze=new Ne({name:"bic-chart",version:gv}),hn="2026-09-03T14:18:47Z src:e45d51efeac8 (8 files)",yv=_.object({columns:_.array(_.object({name:_.string(),dataType:_.enum(["String","Integer","Decimal","DateTime"]).optional().describe("Known type (DAX/semantic-model callers have it); omit to infer from values."),isMeasure:_.boolean().optional().describe("Known measure/dimension role; omit to apply the numeric-non-identifier heuristic. A semantic-model caller should set this."),format:_.string().optional().describe('Format string from the model (e.g. "$#,##0") \u2192 modelFormat.'),description:_.string().optional().describe("Column description from the model \u2192 modelDesc.")})).min(1),rows:_.array(_.union([_.array(_.any()),_.record(_.string(),_.any())])).min(1).describe("Rows as positional arrays aligned to columns, OR objects keyed by column name.")}).describe("A pre-typed {columns, rows} table (DAX query results, dataframe, arrow). Use INSTEAD of csv_path/csv_text."),mn={csv_path:_.string().optional().describe("Path to a CSV file (header row required)."),csv_text:_.string().optional().describe("Inline CSV text (alternative to csv_path)."),data:yv.optional().describe("A pre-typed {columns, rows} table \u2014 the shape a Fabric DAX query / dataframe already has. Alternative to csv_path/csv_text; when columns carry isMeasure/format/description the shape matches PBI fidelity with no guessing."),measures:_.array(_.string()).optional().describe("Columns to treat as MEASURES (the PBI 'Values' well \u2014 fields the chart aggregates). Numeric non-identifier columns are measures by default."),dimensions:_.array(_.string()).optional().describe("Columns to treat as DIMENSIONS (a PBI category/axis well \u2014 fields to group/break by). Use for numeric codes/years that should NOT be aggregated."),formats:_.record(_.string(),_.string()).optional().describe(`Per-column number/date FORMAT STRING (e.g. {"Revenue":"$#,##0","Margin":"0.0%"}). PBI supplies these from its model; a CSV can't, so provide them to steer number formatting.`),descriptions:_.record(_.string(),_.string()).optional().describe("Per-column human DESCRIPTION \u2014 gives the model semantic context the bare column name lacks."),group_by:_.array(_.string()).optional().describe("Columns that are the PRIMARY series/breakdown (the PBI 'Group by' well); the backend leads the visualization with these."),non_additive:_.array(_.string()).optional().describe("MEASURE columns that must NOT be summed (a ratio/average/rate \u2014 use mean, never stack). Usually auto-detected; this is an authoritative override."),privacy_level:_.enum(["0","10","20","30"]).optional().describe("How much detail to compute/share: 0 names only, 10 +counts, 20 +detailed stats (default), 30 +obfuscated sample rows.")};function pn(a){return{measures:a.measures,dimensions:a.dimensions,formats:a.formats,descriptions:a.descriptions,groupBy:a.group_by,nonAdditive:a.non_additive,privacyLevel:a.privacy_level??"20"}}function gn(a){if(a.data)return Xu(a.data.columns,a.data.rows,pn(a));if(a.csv_text)return T0(a.csv_text,pn(a));if(a.csv_path)return T0(hv(Ic(a.csv_path),"utf-8"),pn(a));throw new Error("Provide one of: data ({columns, rows}), csv_text, or csv_path.")}ze.registerTool("assess_data_shape",{title:"Assess data shape",description:"Profile a dataset locally (no data leaves the machine): per-column types, cardinality, ranges, measure/dimension roles, temporal/ordinal detection, and cross-column signals (nesting, shared values, association). This is the same measurement engine the BIC Power BI visual uses before generating a chart.",inputSchema:mn},async a=>{let e=gn(a),t={totalRows:e.totalRows,leafCardinality:e.index.getLeafCardinality(),columns:e.shape};return{content:[{type:"text",text:JSON.stringify(t,null,2)}]}});ze.registerTool("list_eligible_charts",{title:"List eligible chart types",description:"The AUTHORITATIVE list of chart types that can render this dataset \u2014 the server's own eligibility engine (ChartFilter.GetCharts), NOT a local guess. Profiles the data locally, then sends that profile to the backend. NO LLM in the loop, but this is a NETWORK call and it is METERED \u2014 see the licensing note below. By default the tool DETECTS the project's language (JS/TS vs Python from the working dir) and returns the charts for THAT language, SCOPED + RANKED by the picker's chart-type\xD7renderer weights (best first), each tagged with its source language and renderer. Pass `language` ('javascript'|'python') to force it, or `renderer` ('D3'/'PLOTLY'/\u2026) to rank one specific renderer, or renderer '' for the full renderer-agnostic eligibility across ALL renderers (also ranked, by chart type alone, with no renderer/language tag). If the language can't be auto-detected and none is given, the tool asks you to specify one. REQUIRES an active license (BIC_LICENSEE + BIC_LICENSE_KEY): eligibility IS the engine, so programmatic access to it is licensed. Free within a generous hourly allowance; beyond it a call may draw usage credits, so avoid calling it in a tight loop. The refusal names what to set.",inputSchema:{...mn,language:_.enum(["javascript","typescript","python",""]).optional().describe("Project source language \u2192 the server ranks across that language's renderers (javascript/typescript = D3/Vega, python = Plotly/matplotlib). Omit to AUTO-DETECT from the working dir; `renderer` (if given) overrides this."),project_dir:_.string().optional().describe("Directory to auto-detect the project language from (default: the MCP server's working directory)."),renderer:_.enum(["D3","PLOTLY","PYMATPLOT","VEGA",""]).optional().describe("Force a specific renderer (overrides `language`): a concrete renderer ranks that renderer's charts; '' = the full renderer-agnostic eligibility (all renderers, ranked by chart type alone). Omit to use `language`/auto-detection."),level_of_detail:_.enum(["High","Medium","Low",""]).optional().describe("How much of the data the user wants to SEE \u2014 ASK THEM, do not infer. 'High' = every observation as its own mark, 'Medium' = every row shown as a distribution, 'Low' = one mark per category. Ranks this list the same way a generation would, so what you see here is what you will get. Omit if they have expressed no preference."),top:_.number().optional().describe("Show only the top-N ranked results. Omit for all."),width:_.number().optional().describe("Viewport width px (default 800) \u2014 affects small-viewport / facet-budget gates."),height:_.number().optional().describe("Viewport height px (default 600).")}},async a=>{let e=gn(a),t="",n="",r="";if(a.renderer!==void 0)t=a.renderer;else if(a.language)n=a.language;else{let d=dn(a.project_dir);if(d.language)n=d.language,r=` (auto-detected language: ${d.language} \u2014 ${d.reason})`;else return{content:[{type:"text",text:`Couldn't determine the project language: ${d.reason}. Re-run with language: "javascript" or "python" (or renderer: "D3"/"PLOTLY"/\u2026 for a specific renderer, or renderer: "" for the full renderer-agnostic list).`}]}}let o=await Qu({shape:e.shape,totalRows:e.totalRows,leafCardinality:e.index.getLeafCardinality(),width:a.width,height:a.height,renderer:t,language:n,levelOfDetail:a.level_of_detail||void 0});if(!o.ok)return{isError:!0,content:[{type:"text",text:`Could not list eligible charts: ${o.errorMessage}`}]};let i=o.charts.length>0&&o.charts[0].rank!==void 0,s=n?`${n}`:t||"all renderers",l=o.charts,u;i?(a.top&&a.top>0&&(l=l.slice(0,a.top)),u=`Top ${l.length} chart type(s) for this data in ${s}, ranked by the picker's weights (authoritative \u2014 server ChartFilter + ScoreChartTypes, no credits)${r}:`):u=`${l.length} chart type(s) can render this data \u2014 renderer-agnostic eligibility (authoritative \u2014 ChartFilter.GetCharts, no credits):`;let c=l.map(d=>{if(d.rank!==void 0){let p=d.language?` [${d.language}]`:"",m=d.score!==void 0?` (${d.score})`:"";return`${d.rank}. ${d.name}${p}${m}${d.description?` \u2014 ${d.description}`:""}`}return`\u2022 ${d.name}${d.description?` \u2014 ${d.description}`:""}`});if(l.length===0){let d=o.refused.filter(y=>y.reason),p=[...d.filter(y=>y.isVeto),...d.filter(y=>!y.isVeto)].slice(0,5),m=o.noFitSummary?["",o.noFitSummary]:[],h=o.noFitSummary?`${o.refused.length} chart type(s) were checked, and every one was refused for this canvas. Changing the fields will not help until it is bigger.`:`${o.refused.length} chart type(s) were checked. Add or bind the field(s) named above and ask again.`,g=p.length>0?[...m,"","Why nothing fit (the server's own words, for a few of the types it checked):",...p.map(y=>`\u2022 ${y.reason}`),"",h]:m.length>0?[...m,"",h]:["","The server did not say which requirements failed - it names one only when it can prove it from the declared structure."];return{content:[{type:"text",text:[u,...g].join(`
142
+ `}function kc(a){let e=a.renderData.geoPoint,t=a.renderData.geoUnmatched;return{chartName:a.res.finalChartName||"",language:a.res.language,version:a.res.version,creditCost:a.res.creditCost??null,correlationId:a.res.correlationId,...a.res.codeSummary?{codeSummary:a.res.codeSummary}:{},...a.res.warningMessage?{warningMessage:a.res.warningMessage}:{},...a.withCode?{code:a.code}:{},requiredD3Plugins:a.plugins,geo:a.res.geoKind?{column:a.res.geoColumn||null,kind:a.res.geoKind,hasGeometry:a.hasGeometry,unmatched:t?.count??0,unmatchedExamples:t?.examples??[]}:null,geoPoint:e?{placed:a.renderData.rows.length-(e.unplaced??0),total:a.renderData.rows.length,precision:e.precision??null,unplaced:e.unplaced??0,unplacedExamples:e.unplacedExamples??[],ambiguousRows:e.ambiguousRows??0}:null,...a.files?{files:a.files}:{data:a.renderData},hostContract:a.hostContract,build:a.build}}Cc();Mc();Ac();/*! @bicharts/chart-host — Apache-2.0. Bundled reference data: GeoNames (https://www.geonames.org/) CC BY 4.0; Natural Earth and US Census/TIGER (public domain). Full text: NOTICE in this package. */function wc(a){if(!a)return;let e=a.toLowerCase();if(/^country/.test(e))return Ae;if(e==="us-state-code"||e==="us-state-name")return vc;if(e==="us-zip5")return Uc;if(e==="north-america")return ov();if(e==="world")return rv()}var ln;function rv(){return ln||(ln={type:"FeatureCollection",features:Ae.features.filter(a=>a?.geometry&&a.geometry.type!=="Point")}),ln}var un;function ov(){return un||(un={type:"FeatureCollection",features:Ae.features.filter(a=>ec.has(a?.id)&&a?.geometry&&a.geometry.type!=="Point").map(a=>tc(a,ac)).filter(a=>a!==null)}),un}import{readdirSync as iv,existsSync as sv,statSync as Pc}from"node:fs";import{join as cn}from"node:path";var lv=["package.json","tsconfig.json","jsconfig.json","deno.json","deno.jsonc","angular.json","svelte.config.js","vite.config.ts","vite.config.js","pnpm-lock.yaml","yarn.lock","package-lock.json"],uv=["pyproject.toml","requirements.txt","setup.py","setup.cfg","Pipfile","poetry.lock","environment.yml","environment.yaml","conda.yaml","tox.ini"],cv=new Set([".js",".jsx",".mjs",".cjs",".ts",".tsx"]),dv=new Set([".py",".pyi"]);function zc(a,e){return e.some(t=>sv(cn(a,t)))}function pv(a){let e=0,t=0,n=o=>{let i;try{i=iv(o)}catch{return}for(let s of i){if(s==="node_modules"||s===".git"||s.startsWith("."))continue;let l=cn(o,s),u=!1;try{u=Pc(l).isFile()}catch{continue}if(!u)continue;let c=s.lastIndexOf(".");if(c<0)continue;let d=s.slice(c).toLowerCase();cv.has(d)?e++:dv.has(d)&&t++}};n(a);let r=cn(a,"src");try{Pc(r).isDirectory()&&n(r)}catch{}return{js:e,py:t}}function dn(a=process.cwd()){let e=zc(a,lv),t=zc(a,uv);if(e&&!t)return{language:"javascript",reason:`found a JS/TS project marker in ${a}`};if(t&&!e)return{language:"python",reason:`found a Python project marker in ${a}`};let{js:n,py:r}=pv(a);return n>0&&r===0?{language:"javascript",reason:`found ${n} JS/TS source file(s) and no Python in ${a}`}:r>0&&n===0?{language:"python",reason:`found ${r} Python source file(s) and no JS/TS in ${a}`}:n>0&&r>0?n>=r*3?{language:"javascript",reason:`JS/TS files dominate (${n} vs ${r} Python) in ${a}`}:r>=n*3?{language:"python",reason:`Python files dominate (${r} vs ${n} JS/TS) in ${a}`}:{language:null,reason:`mixed JS/TS (${n}) and Python (${r}) in ${a}`}:e&&t?{language:null,reason:`both JS and Python project markers present in ${a}`}:{language:null,reason:`no JS or Python markers or source files found in ${a}`}}var gv="0.5.55",ze=new Ne({name:"bic-chart",version:gv}),hn="2026-09-03T14:32:48Z src:82a7d937fef3 (8 files)",yv=_.object({columns:_.array(_.object({name:_.string(),dataType:_.enum(["String","Integer","Decimal","DateTime"]).optional().describe("Known type (DAX/semantic-model callers have it); omit to infer from values."),isMeasure:_.boolean().optional().describe("Known measure/dimension role; omit to apply the numeric-non-identifier heuristic. A semantic-model caller should set this."),format:_.string().optional().describe('Format string from the model (e.g. "$#,##0") \u2192 modelFormat.'),description:_.string().optional().describe("Column description from the model \u2192 modelDesc.")})).min(1),rows:_.array(_.union([_.array(_.any()),_.record(_.string(),_.any())])).min(1).describe("Rows as positional arrays aligned to columns, OR objects keyed by column name.")}).describe("A pre-typed {columns, rows} table (DAX query results, dataframe, arrow). Use INSTEAD of csv_path/csv_text."),mn={csv_path:_.string().optional().describe("Path to a CSV file (header row required)."),csv_text:_.string().optional().describe("Inline CSV text (alternative to csv_path)."),data:yv.optional().describe("A pre-typed {columns, rows} table \u2014 the shape a Fabric DAX query / dataframe already has. Alternative to csv_path/csv_text; when columns carry isMeasure/format/description the shape matches PBI fidelity with no guessing."),measures:_.array(_.string()).optional().describe("Columns to treat as MEASURES (the PBI 'Values' well \u2014 fields the chart aggregates). Numeric non-identifier columns are measures by default."),dimensions:_.array(_.string()).optional().describe("Columns to treat as DIMENSIONS (a PBI category/axis well \u2014 fields to group/break by). Use for numeric codes/years that should NOT be aggregated."),formats:_.record(_.string(),_.string()).optional().describe(`Per-column number/date FORMAT STRING (e.g. {"Revenue":"$#,##0","Margin":"0.0%"}). PBI supplies these from its model; a CSV can't, so provide them to steer number formatting.`),descriptions:_.record(_.string(),_.string()).optional().describe("Per-column human DESCRIPTION \u2014 gives the model semantic context the bare column name lacks."),group_by:_.array(_.string()).optional().describe("Columns that are the PRIMARY series/breakdown (the PBI 'Group by' well); the backend leads the visualization with these."),non_additive:_.array(_.string()).optional().describe("MEASURE columns that must NOT be summed (a ratio/average/rate \u2014 use mean, never stack). Usually auto-detected; this is an authoritative override."),privacy_level:_.enum(["0","10","20","30"]).optional().describe("How much detail to compute/share: 0 names only, 10 +counts, 20 +detailed stats (default), 30 +obfuscated sample rows.")};function pn(a){return{measures:a.measures,dimensions:a.dimensions,formats:a.formats,descriptions:a.descriptions,groupBy:a.group_by,nonAdditive:a.non_additive,privacyLevel:a.privacy_level??"20"}}function gn(a){if(a.data)return Xu(a.data.columns,a.data.rows,pn(a));if(a.csv_text)return T0(a.csv_text,pn(a));if(a.csv_path)return T0(hv(Ic(a.csv_path),"utf-8"),pn(a));throw new Error("Provide one of: data ({columns, rows}), csv_text, or csv_path.")}ze.registerTool("assess_data_shape",{title:"Assess data shape",description:"Profile a dataset locally (no data leaves the machine): per-column types, cardinality, ranges, measure/dimension roles, temporal/ordinal detection, and cross-column signals (nesting, shared values, association). This is the same measurement engine the BIC Power BI visual uses before generating a chart.",inputSchema:mn},async a=>{let e=gn(a),t={totalRows:e.totalRows,leafCardinality:e.index.getLeafCardinality(),columns:e.shape};return{content:[{type:"text",text:JSON.stringify(t,null,2)}]}});ze.registerTool("list_eligible_charts",{title:"List eligible chart types",description:"The AUTHORITATIVE list of chart types that can render this dataset \u2014 the server's own eligibility engine (ChartFilter.GetCharts), NOT a local guess. Profiles the data locally, then sends that profile to the backend. NO LLM in the loop, but this is a NETWORK call and it is METERED \u2014 see the licensing note below. By default the tool DETECTS the project's language (JS/TS vs Python from the working dir) and returns the charts for THAT language, SCOPED + RANKED by the picker's chart-type\xD7renderer weights (best first), each tagged with its source language and renderer. Pass `language` ('javascript'|'python') to force it, or `renderer` ('D3'/'PLOTLY'/\u2026) to rank one specific renderer, or renderer '' for the full renderer-agnostic eligibility across ALL renderers (also ranked, by chart type alone, with no renderer/language tag). If the language can't be auto-detected and none is given, the tool asks you to specify one. REQUIRES an active license (BIC_LICENSEE + BIC_LICENSE_KEY): eligibility IS the engine, so programmatic access to it is licensed. Free within a generous hourly allowance; beyond it a call may draw usage credits, so avoid calling it in a tight loop. The refusal names what to set.",inputSchema:{...mn,language:_.enum(["javascript","typescript","python",""]).optional().describe("Project source language \u2192 the server ranks across that language's renderers (javascript/typescript = D3/Vega, python = Plotly/matplotlib). Omit to AUTO-DETECT from the working dir; `renderer` (if given) overrides this."),project_dir:_.string().optional().describe("Directory to auto-detect the project language from (default: the MCP server's working directory)."),renderer:_.enum(["D3","PLOTLY","PYMATPLOT","VEGA",""]).optional().describe("Force a specific renderer (overrides `language`): a concrete renderer ranks that renderer's charts; '' = the full renderer-agnostic eligibility (all renderers, ranked by chart type alone). Omit to use `language`/auto-detection."),level_of_detail:_.enum(["High","Medium","Low",""]).optional().describe("How much of the data the user wants to SEE \u2014 ASK THEM, do not infer. 'High' = every observation as its own mark, 'Medium' = every row shown as a distribution, 'Low' = one mark per category. Ranks this list the same way a generation would, so what you see here is what you will get. Omit if they have expressed no preference."),top:_.number().optional().describe("Show only the top-N ranked results. Omit for all."),width:_.number().optional().describe("Viewport width px (default 800) \u2014 affects small-viewport / facet-budget gates."),height:_.number().optional().describe("Viewport height px (default 600).")}},async a=>{let e=gn(a),t="",n="",r="";if(a.renderer!==void 0)t=a.renderer;else if(a.language)n=a.language;else{let d=dn(a.project_dir);if(d.language)n=d.language,r=` (auto-detected language: ${d.language} \u2014 ${d.reason})`;else return{content:[{type:"text",text:`Couldn't determine the project language: ${d.reason}. Re-run with language: "javascript" or "python" (or renderer: "D3"/"PLOTLY"/\u2026 for a specific renderer, or renderer: "" for the full renderer-agnostic list).`}]}}let o=await Qu({shape:e.shape,totalRows:e.totalRows,leafCardinality:e.index.getLeafCardinality(),width:a.width,height:a.height,renderer:t,language:n,levelOfDetail:a.level_of_detail||void 0});if(!o.ok)return{isError:!0,content:[{type:"text",text:`Could not list eligible charts: ${o.errorMessage}`}]};let i=o.charts.length>0&&o.charts[0].rank!==void 0,s=n?`${n}`:t||"all renderers",l=o.charts,u;i?(a.top&&a.top>0&&(l=l.slice(0,a.top)),u=`Top ${l.length} chart type(s) for this data in ${s}, ranked by the picker's weights (authoritative \u2014 server ChartFilter + ScoreChartTypes, no credits)${r}:`):u=`${l.length} chart type(s) can render this data \u2014 renderer-agnostic eligibility (authoritative \u2014 ChartFilter.GetCharts, no credits):`;let c=l.map(d=>{if(d.rank!==void 0){let p=d.language?` [${d.language}]`:"",m=d.score!==void 0?` (${d.score})`:"";return`${d.rank}. ${d.name}${p}${m}${d.description?` \u2014 ${d.description}`:""}`}return`\u2022 ${d.name}${d.description?` \u2014 ${d.description}`:""}`});if(l.length===0){let d=o.refused.filter(y=>y.reason),p=[...d.filter(y=>y.isVeto),...d.filter(y=>!y.isVeto)].slice(0,5),m=o.noFitSummary?["",o.noFitSummary]:[],h=o.noFitSummary?`${o.refused.length} chart type(s) were checked, and every one was refused for this canvas. Changing the fields will not help until it is bigger.`:`${o.refused.length} chart type(s) were checked. Add or bind the field(s) named above and ask again.`,g=p.length>0?[...m,"","Why nothing fit (the server's own words, for a few of the types it checked):",...p.map(y=>`\u2022 ${y.reason}`),"",h]:m.length>0?[...m,"",h]:["","The server did not say which requirements failed - it names one only when it can prove it from the declared structure."];return{content:[{type:"text",text:[u,...g].join(`
143
143
  `)}]}}return{content:[{type:"text",text:[u,"",...c].join(`
144
144
  `)}]}});ze.registerTool("generate_chart",{title:"Generate chart code",description:"Generate ready-to-render chart code for a dataset via the BIC backend: the data shape is profiled locally, the backend picks a suitable chart type (or honors chart_type), and returns code that has passed the backend's eligibility gates and quality checks. Requires a trial or paid BIC account (license env vars / credentials file). The returned D3 code is a portable render(container, data, options) function meant to be MERGED into your own app; an integration contract describes exactly how to call it. A standalone HTML preview and file output are optional.",inputSchema:{...mn,prompt:_.string().optional().describe("Freeform guidance for the generation \u2014 the same 'user commentary' lane the Power BI visual uses. Steer encoding/axes/emphasis in plain language, e.g. 'put Year on the x-axis, color by Division', 'render citations as a chord diagram', 'log scale on revenue', 'annotate the peak'."),chart_type:_.string().optional().describe("Preferred chart type by name (honored unless the data shape disqualifies it)."),rows_policy:_.enum(["auto","all"]).optional().describe("'auto' (default) = the engine decides how to summarize. 'all' = render EXACTLY one mark/row per source row, with no Top-N, no 'Other' rollup and no sampling. Use 'all' when your app depends on every row being present (a row-per-entity table, a point per location)."),required_columns:_.array(_.string()).optional().describe("Fields that MUST be visible in the output, each as its own column/channel. AUTHORITATIVE and VERIFIED: if the generated code never references one, the server regenerates automatically rather than returning a chart that silently dropped it."),forbid_derived_columns:_.boolean().optional().describe("True = show only fields present in the data (plus required_columns) \u2014 no per-capita, share-of-total, index, rank or z-score columns the engine would otherwise add. Use when your app owns the analytics and the chart is only the display."),level_of_detail:_.enum(["High","Medium","Low",""]).optional().describe("How much of the data the user wants to SEE. ASK THEM \u2014 do not infer it from the data or from your own preference; it is a question about what they want, and they can answer it in one word. 'High' = every observation drawn as its own mark (scatter, beeswarm, strip). 'Medium' = every row used, but shown as a distribution (box, violin, histogram). 'Low' = one mark per category (bars, cards, pies). Omit ONLY if the user has genuinely expressed no preference \u2014 omitting it is a proven no-op and the engine decides. This is a strong nudge, not a filter: it reorders charts that already fit, and an explicit chart_type still wins."),font_family:_.string().optional().describe("CSS font family for the chart's own text \u2014 titles, axis labels, tick labels, legends (e.g. 'Segoe UI' or a stack like 'Segoe UI, Arial, sans-serif'). Pass this ONLY when the user has named a font or you are matching a stated house style \u2014 do NOT infer one from the data, the project, or your own preference. Omitting it is a proven no-op: the engine picks. Applies to whichever renderer is chosen (D3, Plotly, matplotlib and Vega each get an equivalent instruction), and an unavailable family falls back to a web-safe stack rather than failing."),renderer:_.enum(["D3","PLOTLY","PYMATPLOT","VISUAL",""]).optional().describe("Force a specific renderer (overrides `language`). Omit to use `language`/auto-detection; 'VISUAL'/'' = let the backend choose."),language:_.enum(["javascript","typescript","python",""]).optional().describe("Project source language: with no concrete `renderer`, the backend auto-picks a renderer WITHIN this language (javascript/typescript \u2192 D3/Vega, python \u2192 Plotly/matplotlib). Omit to AUTO-DETECT from the working dir."),project_dir:_.string().optional().describe("Directory to auto-detect the project language from (default: the MCP server's working directory)."),model:_.string().optional().describe("Model code; empty = backend default."),reasoning_mode:_.enum(["","1P","2P","3P","CP"]).optional().describe("LLM pass structure; empty = backend heuristic."),width:_.number().optional(),height:_.number().optional(),wrap:_.enum(["none","esm"]).optional().describe("Code form: 'none' (default) = the raw render() function to merge; 'esm' = same code plus `export { render };` so the file can be `import`ed. Both forms are accepted everywhere - @bicharts/chart-host strips the export clause before compiling - so this is a convenience, never a compatibility choice."),include_code:_.boolean().optional().describe("Include the full code in the response (default true) \u2014 governs BOTH the text block and structuredContent.code. Set false when you only want it written to out_dir."),preview_html:_.boolean().optional().describe("Also emit a standalone D3 preview (needs out_dir). Default false \u2014 the code is meant to be merged, not run as an index.html."),out_dir:_.string().optional().describe("Optional directory to write chart.js/py, a data.sample.json, and (if preview_html) preview.html.")},outputSchema:sn},async(a,e)=>{let t=a.privacy_level??"20",n=await $u(gn(a),t),r=a.renderer,o=a.language??"",i="";if(a.renderer===void 0&&!o){let N=dn(a.project_dir);N.language&&(o=N.language,i=` (auto-detected language: ${N.language})`)}r===""||r==="VISUAL"?r="VISUAL":r||(r=o?"VISUAL":"D3");let s=e?._meta?.progressToken,l=0,u="",c=N=>{s!==void 0&&e.sendNotification({method:"notifications/progress",params:{progressToken:s,progress:++l,message:N}}).catch(()=>{})},d=s===void 0?null:setInterval(()=>{c(u?`${u}\u2026`:"generating chart (multi-pass \u2014 can take a few minutes)\u2026")},1e4),p;try{p=await Yu({shape:n.shape,totalRows:n.totalRows,leafCardinality:n.index.getLeafCardinality(),sampleCsv:n.sampleCsv,prompt:a.prompt,chartHint:a.chart_type,rowsPolicy:a.rows_policy==="all"?"all":void 0,requiredColumns:a.required_columns,forbidDerivedColumns:a.forbid_derived_columns,levelOfDetail:a.level_of_detail||void 0,fontFamily:a.font_family||void 0,renderer:r,language:o,model:a.model,reasoningMode:a.reasoning_mode,width:a.width,height:a.height,onProgress:N=>{u=N,c(`${N}\u2026`)}})}finally{d&&clearInterval(d)}if(!p.ok)return{isError:!0,content:[{type:"text",text:`Generation failed (correlationId ${p.correlationId}): ${p.errorMessage||"no code returned"}`}]};let m=p.language.toUpperCase()==="D3",h=p.geoColumn&&p.geoKind?{column:p.geoColumn,kind:p.geoKind}:void 0,g=p.geoKind?wc(p.geoKind):void 0,y=p.pointCityColumn||p.pointStateColumn||p.pointZipColumn||p.pointCountryColumn||p.pointLatColumn&&p.pointLonColumn?{city:p.pointCityColumn||void 0,state:p.pointStateColumn||void 0,zip:p.pointZipColumn||void 0,lat:p.pointLatColumn||void 0,lon:p.pointLonColumn||void 0,country:p.pointCountryColumn||void 0,...p.geoKind==="world"?{mapKind:"world"}:{}}:null,b=fc(n.index,n.shape,h,y),S=a.wrap==="esm"&&m?bc(p.code):p.code,k=[`Chart: ${p.finalChartName||"(backend pick)"} | language: ${p.language} | version: ${p.version}`+(p.creditCost!=null?` | credits: ${p.creditCost}`:"")+i];if(p.codeSummary&&k.push(`Summary: ${p.codeSummary}`),p.warningMessage&&k.push(`Warning: ${p.warningMessage}`),h){let N=b.geoUnmatched;k.push(`Geo: joined '${h.column}' as ${h.kind}`+(g?"":" (WARNING: no bundled geometry for this kind)")+(N&&N.count>0?` \u2014 ${N.count} region(s) unmatched: ${N.examples.join(", ")}`:""))}else p.geoKind&&k.push(`Geo: ${p.geoKind} basemap attached as options.geo`+(g?"":" (WARNING: no bundled geometry for this kind \u2014 the map will draw no land)"));if(y){let N=[y.city&&"city",y.state&&"state",y.zip&&"zip",y.lat&&y.lon&&"lat/lon"].filter(Boolean).join("+"),A=b.geoPoint,X=b.rows.length-(A?.unplaced??0);k.push(`Points: geocoded ${X}/${b.rows.length} from ${N}`+(A?.precision?` at ${A.precision} precision`:"")+(A&&A.unplaced>0?` \u2014 ${A.unplaced} unplaced: ${A.unplacedExamples.join(", ")}`:"")+(A&&A.ambiguousRows>0?` \u2014 ${A.ambiguousRows} placed by largest-match tie-break`:""))}k.push("",Wc({language:p.language,data:b,finalChartName:p.finalChartName,code:p.code,geoKind:p.geoKind}));let w={};if(a.out_dir){let N=Ic(a.out_dir);mv(N,{recursive:!0});let A=p.language.toUpperCase()==="PYTHON"?"py":"js",X=Pe(N,`chart.${A}`),B=A==="py"?"#":"//",j=`${B} BIC generated chart \u2014 ${p.finalChartName||"chart"} [${p.language}]
145
145
  ${B} host contract v${I8}. A host implementing a DIFFERENT major
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bicharts/chart-mcp",
3
- "version": "0.5.54",
3
+ "version": "0.5.55",
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). Requires a BIC trial or paid account.",
5
5
  "keywords": [
6
6
  "mcp",
@@ -46,8 +46,8 @@
46
46
  "zod": "^3.23.0"
47
47
  },
48
48
  "devDependencies": {
49
- "@bicharts/chart-host": "^0.5.54",
50
- "@bicharts/shape-core": "^0.5.54",
49
+ "@bicharts/chart-host": "^0.5.55",
50
+ "@bicharts/shape-core": "^0.5.55",
51
51
  "@types/node": "^20.0.0",
52
52
  "@types/papaparse": "^5.3.14",
53
53
  "esbuild": "^0.28.1",