@bicharts/chart-mcp
v0.3.5
Published
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). Requi
Maintainers
Readme
@bicharts/chart-mcp (experimental)
An MCP (Model Context Protocol) stdio server that lets any MCP-capable AI client (Claude Code, Claude Desktop, Copilot Studio, Cursor, …) use the BIC chart engine:
assess_data_shape— profiles a CSV locally with the same measurement engine the Power BI visual uses (@bicharts/shape-core). No backend call, no credentials, no data leaves the machine. The free-teaser tool.list_eligible_charts— the authoritative list of chart types that can render this data (server-side policy, not a local guess; zero-credit, no LLM). By default it auto-detects the project's language (JS/TS vs Python from the working dir) and returns the charts for THAT language — scoped to its renderers and ranked by the picker's chart-type×renderer weights (best first), each tagged with its source language (JavaScript for D3/Vega, Python for Plotly/matplotlib), its renderer, and a 0–100 score. Passlanguage: "javascript"|"python"to force it,renderer: "D3"/"PLOTLY"/…to rank one specific renderer, orrenderer: ""for the full renderer-agnostic eligibility across all renderers (unranked — the wide "what can render" net).top: Ntrims to the best N. If the language can't be auto-detected and none is given, the tool asks you to specify one. Credentials optional (a license only unlocks draft types).generate_chart— profiles locally, then calls the BIC backend, which picks an eligible chart type for the measured shape (or honorschart_type) and returns code that has passed the backend's gates/QC. Renderer selection is the same language-aware intersection: with no explicitrenderer, the detected/languagenarrows the backend's auto renderer pick to that language (JS → D3/Vega, Python → Plotly/matplotlib); an explicitrendereralways wins. For D3 output, passingpreview_html: truealongsideout_diralso writes a standalonepreview.htmlyou can open with no build step. It is off by default — the code is meant to be merged into your app, not run as an index.html.
generate_chart's machine-readable result
generate_chart answers on two channels. The text block is for a human (and for hosts that
only render text); structuredContent is for code, so nothing has to string-parse
--- code --- out of prose:
| field | |
| --- | --- |
| code | the render() source (omitted when include_code: false) |
| chartName, language, version, creditCost | what was produced, and what it cost |
| correlationId | the handle into the server-side log — quote it in any support request |
| requiredD3Plugins | packages the code calls, e.g. ["d3-sankey"] — install and Object.assign them onto the same d3 you pass the host, or the chart throws mid-render |
| geo / geoPoint | region/basemap wiring, and how many points actually placed |
| data or files | the render payload inline, or the paths written — never both |
| hostContract, build | the contract the code targets, and the bundle that served the call |
data carries the appended __geoIso__ / __geoLat__ / __geoLon__ columns, which a caller
cannot reconstruct from the source data — so a geo chart needs either it or files.data.
Language & renderer
Both list/generate tools speak language (what your project is written in) as well as
renderer (a specific chart library). The server maps javascript/typescript → D3 +
Vega and python → Plotly + matplotlib, and the picker's weights rank across that
language's renderers. Precedence is explicit renderer > language > auto-detected
language. The MCP sniffs the working directory (or project_dir) for language markers
(package.json/tsconfig.json → JS, pyproject.toml/requirements.txt → Python) plus a
bounded source-file count; when the signal is genuinely mixed or absent it asks you to
pick rather than silently assuming one.
Data inputs — CSV or a pre-typed table
Both tools accept, as alternatives:
csv_path/csv_text— a CSV; column types and measure/dimension roles are inferred (numeric-non-identifier = measure), and you can steer them with themeasures/dimensions/formats/descriptionslevers.data: { columns, rows }— a pre-typed table, the shape a Fabric DAX Execute-Queries result, a dataframe, or an arrow table already has.columnscarrynameand (optionally)dataType,isMeasure,format,description;rowsare positional arrays or objects keyed by column name. When a caller suppliesisMeasure+format+descriptionfrom a semantic model, the shape matches Power BI fidelity with no heuristic guessing — the profiler engine is the same one the visual runs; CSV was only ever one adapter over it.
Example (a semantic-model query result):
{
"data": {
"columns": [
{ "name": "Region", "dataType": "String", "isMeasure": false },
{ "name": "Revenue", "dataType": "Decimal", "isMeasure": true,
"format": "$#,##0", "description": "Net booked revenue" }
],
"rows": [ { "Region": "West", "Revenue": 12000.5 }, { "Region": "East", "Revenue": 9000 } ]
}
}Build
cd mcp
npm install
npm run build # bundles to dist/index.mjs (single file)Credentials (generate_chart only)
A trial or paid BIC account is required — no freemium. Use the same
License Key, Licensee, and Secret Key from your BIC account (the ones
the Power BI visual's license settings use). assess_data_shape needs no
credentials at all (it runs entirely locally).
Two ways to supply them — env wins over the file:
1. Env vars (via the MCP server's env block — see registration below):
| Var | Meaning |
| --- | --- |
| BIC_LICENSE_KEY | required |
| BIC_LICENSEE | required (account name) |
| BIC_SECRET_KEY | optional |
| BIC_URL | override backend (default https://bizintelligencechampions.com) |
| BIC_MCP_REASONING | OPTIONAL override of the default reasoning mode (else "" = leave-to-visual, same as the PBI visual) |
| BIC_MCP_MODEL | OPTIONAL override of the default model (else "" = the account's IsDefault model, same as the visual) |
Defaults match the Power BI visual. Unset, the MCP sends model="" (server
resolves the IsDefault model), reasoning_mode="" (leave-to-visual heuristic), and
privacy_level="20" (detailed stats, no sample rows — see below). The two env
vars above are escape hatches only; leave them unset for visual parity.
Response timeouts (HTTP 499 / dropped connection)
The backend completes long multi-pass generations (the visual regularly runs several minutes on rich charts). A dropped connection is therefore a delivery timeout in the calling path — the MCP host's tool-call timeout or an intermediary proxy — not a server ceiling, and not a generation failure (the chart likely generated and may have been billed). The parity-preserving fix is to let the call take as long as it needs:
- Built in: the server sends MCP progress notifications every 10s during a generation; spec-compliant hosts (including Claude Code) reset their per-call timeout on progress, so the call stays alive as long as the generation needs — no user configuration required.
- Fallback for hosts that don't reset on progress: raise the host's per-tool
timeout (Claude Code:
MCP_TOOL_TIMEOUTin ms, set in the environment Claude Code itself runs in — e.g. theenvblock ofsettings.json). - (Planned) retrieve an already-generated result by
correlationIdso a dropped connection recovers the billed code instead of re-generating.
BIC_MCP_REASONING=CP/1P would finish faster, but that trades away visual parity
(no validator) — prefer raising the timeout.
2. A credentials file — so secrets never sit in a project-committed
.mcp.json. Default ~/.bic/credentials.json (override with
BIC_CREDENTIALS_FILE):
{ "licenseKey": "…", "licensee": "…" }(secretKey optional.)
Prefer the file, or a user-scoped claude mcp add (stored in your user
config), over putting secrets in a repo-committed .mcp.json.
Note: this package deliberately does NOT contain the freemium key-mint secret — consistent with trial/paid-only access, and safe if the folder is ever published.
Credit charges
Each generate_chart call spends credits on your account, exactly like the Power BI
visual (the MCP hits the same billed endpoint):
- Hosted (our models) → token-based — you pay for the tokens the generation actually used.
- BYO (your own API key) → a flat operating fee per request. The MCP/SDK
surface has its own dial (
LLMSdkByoOperatingCost, default 3 credits), separate from the visual's fee, so it can be priced independently.
The response header line shows credits: N so you can see what each call cost.
The agent skill (recommended)
This package ships skills/bic-charts/SKILL.md — the golden path from empty directory to a
working cross-filtered dashboard: the scaffold command block, when to use the authoritative
contract parameters instead of prompt prose, the coordinated-dashboard recipe, and the
handful of places builds measurably lose time. Copy it where your agent looks for skills:
mkdir -Force .claude\skills
Copy-Item -Recurse (npm root)\@bicharts\chart-mcp\skills\bic-charts .claude\skills\An agent that reads it first does in a few calls what otherwise takes thirty — most of the difference is not knowing the API, it is not knowing the shape of the answer.
Register with Claude Code
claude mcp add --scope user bic-chart -- npx -y @bicharts/chart-mcp--scope user stores it in your user config so it works in every project. With
credentials in ~/.bic/credentials.json you need no -e flags at all; add them only
if you prefer env vars:
claude mcp add --scope user bic-chart -e BIC_LICENSE_KEY=... -e BIC_LICENSEE=... -- npx -y @bicharts/chart-mcpor in a project .mcp.json:
{
"mcpServers": {
"bic-chart": {
"command": "npx",
"args": ["-y", "@bicharts/chart-mcp"]
}
}
}Claude Desktop uses the same command/args/env block in
claude_desktop_config.json under mcpServers.
Developing on the server itself? Point at your build instead, so you are not running whatever npx last cached:
claude mcp add --scope user bic-chart-dev -- node c:/Source/PowerBI_Controls/llmchart/mcp/dist/index.mjsThe ready banner prints the build stamp, which is how you tell the two apart.
Try: "Assess the shape of testharness/datasets/divisional_revenue_by_year.csv, then generate a chart for it and write the preview to ./out."
Wire notes
Wire-compatible with the visual/harness envelope (see
testharness/harness/client.py, the reference implementation): gzip→base64→.
body, FNV X-Signature, clientVersion >= 2.0.0.0, plus host: "SDK" so prompts
resolve to the generic host profile once server rev 144 + tokenized config are
live (older servers ignore the field — deployment-order safe).
Publishing (when the experiment proves out — NOT yet)
- Keep
"private": trueuntil the conscious decision to release. - Publish built artifacts only (
files: ["dist"]already enforces this — the tarball carries the bundleddist/index.mjs, no source, no history). npm publish --access publicunder the org scope; users then configure"command": "npx", "args": ["-y", "@bicharts/chart-mcp"]— zero-install.- List in MCP registries/directories for discoverability.
- shape-core rides inside the bundle minified (shared, not shown); the open-source decision for readable source is separate and deferred.
