---
name: chartcraft
description: Forge designed Power BI report projects (.pbip) with the ChartCraft MCP server, from a brief or bound to the user's own semantic model. Use when the user asks for a Power BI report, dashboard or .pbip, wants a designed report on their existing model, or mentions ChartCraft, Forge, palettes or KPI cards.
license: Proprietary. Free to use with a ChartCraft account.
metadata:
  version: "1.0"
  server: https://chartcraft.io/api/mcp
  docs: https://chartcraft.io/docs/mcp
---

# ChartCraft: forge Power BI reports from an agent

ChartCraft is a remote MCP server (Streamable HTTP, stateless) that compiles a complete Power BI project: PBIR report plus TMDL model, or a report-only project bound to the user's own model. The output is a zip with a signed download link valid 24 hours, and every forge is kept in the user's history for 30 days.

## Setup

The server is `https://chartcraft.io/api/mcp`.

- **Hosted agents (claude.ai, ChatGPT, Claude Desktop)**: add the URL as a custom connector. OAuth takes over: the user signs in to ChartCraft once and allows the agent. No key.
- **Claude Code**: `claude mcp add --transport http chartcraft https://chartcraft.io/api/mcp` then `/mcp` to connect through OAuth. With a key instead: add `--header "Authorization: Bearer $CHARTCRAFT_API_KEY"`.
- **Cursor, VS Code, Codex**: a key from Settings, API & agents, kept in an environment variable or a masked prompt. Never in a file that goes to git, never pasted in a chat.

A 401 means no valid token: reconnect the connector, or check the key. Do not retry blindly.

## Tools

| Tool | Cost | Use it for |
| --- | --- | --- |
| `get_balance` | free | Plan, tokens left, reports that buys. Call it first. |
| `list_forge_options` | free | The catalogue: `palettes`, `layouts`, `industries`, `options`, `charts`, `binding`, `costs`. Ask for one section. |
| `suggest_binding` | free | Drafts a binding from the declarations of the user's model and checks every name. |
| `forge_report` | 30 tokens, `dry_run` free | Compiles the project. Returns `downloadUrl`, `runId`, `warnings`, `tokensLeft`. |
| `list_my_reports` | free | The forge history with fresh download links; `from_run` forges one again. |

## Workflow A: a report from a brief

1. `get_balance`. Stop and tell the user if `reportsLeft` is 0 (Free: 120 tokens a month, Pro: 1200).
2. `list_forge_options` with `section: "palettes"`, then `"layouts"` and `"industries"`, to pick valid ids. Never invent an id.
3. `forge_report` with `dry_run: true`: read the page plan and the warnings, show the user what will be drawn.
4. `forge_report` for real with the same arguments. Give the user the download link and the `runId`.
5. Tell the user: unzip, open the `.pbip` in Power BI Desktop (free), click **Refresh** once to load the sample data.

Minimal arguments: `name`, `palette`, `industry`. Useful ones: `layout`, `pageCount` (or `pages`), `cardStyle`, `charts`, `filters`, `mode` (light or dark). Premium palettes and Custom Studio materials need a Pro account: a `pro_required` error means pick a free palette.

## Workflow B: a report bound to the user's model

The user's project must be saved as a Power BI project (`.pbip`), not a `.pbix`: the model lives in `<Model>.SemanticModel/definition/`. If they only have a `.pbix`, point them to File, Save as, Power BI project files in Desktop (the docs page, section "Save as .pbip", has the steps).

1. Read locally `<Model>.SemanticModel/definition/tables/*.tmdl` and `definition/relationships.tmdl`.
2. Build the declarations: for each table its name, `isHidden`, `dataCategory: Time`, and for each column and measure the name, data type, `formatString`, `sortByColumn`, `isHidden`. Relationships as `{ fromTable, toTable }` pairs. **Names and properties only: never a DAX expression, never a partition or M query, never a data row.**
3. If a report already exists on that model, also read `<Name>.Report/definition/pages/*/visuals/*/visual.json` and send, per visual, the fields it binds (table and name), its type and its page. Names only, never filter values.
4. `suggest_binding` with `modelFolder`, `tables`, `relationships` and, if any, `report`. Read `issues` (what is missing), `industry` (use it), `warnings`, `unknownNames`.
5. Adjust the binding with the user if needed (which measure is the headline KPI, which column is the period), then `suggest_binding` again with `binding` to check the names.
6. `forge_report` with `dry_run: true`, `binding`, the guessed `industry`, and a palette. Read the warnings (glossary below).
7. `forge_report` for real. Pass `remember_binding: true` only if the user agrees to keep their table and measure names in their ChartCraft history (otherwise only the model name is kept and the zip lives 24 hours).
8. Tell the user to extract the zip **into the folder that already contains their `.SemanticModel`**, so the new `.Report` sits next to it, then open the forged `.pbip`. Their own report folder is untouched.

Optional checks, when the tools exist on the machine: `npx -y @microsoft/powerbi-report-authoring-cli validate <Name>.Report` for the PBIR grammar; Power BI Desktop for the rendering, the only judge.

## Rules

- Names only. ChartCraft never receives data, DAX, M, or filter values. Keep it that way: do not paste model contents into arguments beyond the declarations above.
- Dry run first, always. It is free and shows the warnings before any token is spent.
- Never paste an API key or a token into a chat, a commit or an issue.
- A period axis on a multi-year calendar must be a year-month column (`YearMonth`, "2026-01"). A month-name column adds up every January of every year.
- A KPI card reads one table: its measure, delta, target and last-period measure share it.
- Give the segment its values (2 to 8 exact names) for sized tiles and one colour per value.
- Do not forge twice to "see": use `from_run` with only what changes, the look is kept.

## Warnings glossary

`forge_report` and `suggest_binding` name what they degrade; a warning is not an error, the report still compiles.

| Code | Meaning | What to do |
| --- | --- | --- |
| `industry-mismatch` | The model does not look like the industry passed. | Use the `industry` suggest_binding guessed. |
| `trend-partial`, `trend-degraded` | Some cards lack a variation or last-period measure; the trend chart loses detail. | Add `delta` / `lastMonth` measures if the model has them. |
| `few-kpis`, `kpi-repeated` | Fewer KPIs than cards, or a KPI used twice. | List more measures in `kpis`. |
| `few-chart-measures` | Charts have fewer than 3 measures. | Add `chartMeasures`. |
| `no-segment-values` | Segment tiles cannot be sized. | Give `segment.values`. |
| `period-ambiguous` | Several date columns could be the period. | Name the year-month column explicitly. |
| `filters-degraded` | The range slicer has no relationship to the facts. | Give `range.gate` (a measure such as Is In Selected Period) or drop the range. |
| `charts-forced-native` | A chart id needs Deneb; it was drawn native. | Accept, or pick a native chart id from `list_forge_options charts`. |
| `no-header-measure` | No subtitle measure. | Optional: `header.subtitleMeasure`. |
| `name-suffixed` | The report name clashed with the existing report. | Nothing: the forged report never takes the original name. |
| `visuals-dropped` | Maps or custom visuals of the existing report are not redrawn. | Tell the user they stay in their own report. |

## Errors

Tool results flagged as errors carry a stable `_meta.error_code`:

- `insufficient_tokens`: tell the user the cost (30) and their balance; do not retry.
- `pro_required`: premium palette or Custom Studio material; pick a free palette or let the user upgrade.
- `rate_limited`: 3 forges a minute, 30 read calls a minute; wait, do not loop.
- `binding_required`: `from_run` on a bound report whose names were not kept; send `binding` again.
- `run_not_found`, `asset_not_found`: check the id with `list_my_reports` or `list_forge_options`.
- `compile_failed`, `unavailable`: transient; one retry after a pause, then report to the user.
- HTTP 401: token missing, expired or revoked. Reconnect or fix the key. HTTP 429 on 401s: too many unauthenticated attempts from this IP.

## Checklist before you call forge_report for real

- [ ] Balance checked, cost announced.
- [ ] Ids come from `list_forge_options`.
- [ ] Dry run read, warnings explained to the user.
- [ ] Bound mode: declarations only, names checked, `industry` from suggest_binding, extraction folder explained.
- [ ] No key or token in the conversation.
