Case [02]2026
A Power BI MCP server that makes report edits scriptable from a chat client.
An MCP surface over Power BI Desktop's report internals — add fields, rename columns, and reshape visuals from Copilot without opening the PBIX by hand.
- Role
- Design + Engineering
- Stack
- PythonMCPPower BI DesktopTMSL / PBIXJSON schemas
- Timeline
- 2026
- Status
- In active use
- Ownership
- MCP surface + mutation safety
- Outcome
- Afternoon edits reduced to a reviewed chat session
Context
Editing Power BI reports at scale means clicking through the same six-step ritual per field per visual — or, more often, giving up and copy-pasting from an old report. When you're maintaining a family of reports that share a schema, the click-through cost becomes the reason you don't refactor. I wanted an MCP server that let a Copilot session drive those edits with the same care a human would.
Constraint
- PBIX files are opaque enough to be dangerous. The report layer is JSON, but the field-reference format is picky and unforgiving; one malformed
Propertyand Power BI Desktop refuses to open the file with a "Something's wrong with one or more fields" dialog. - Encoding matters. Power BI Desktop rejects UTF-8 with BOM on the report's JSON files — a footgun that surfaces only at open-time.
- Human-in-the-loop. The tool had to be scriptable but not autonomous. Every mutation should be small, named, and reversible.
Move
- A narrow set of MCP tools, each doing one thing.
add_data_field,rename_column,reshape_visual— no god-tool that "just edits the report." - Schema-typed inputs via Zod-equivalent validation in Python. Each tool advertises its shape so a Copilot session can call it correctly on the first try.
- Field-reference format normalised at the boundary.
field_nameis always the column name only — neverTable.column— because that's the format Power BI actually stores and mismatches break the report. Documented and enforced in the tool signature. - UTF-8 without BOM enforced on every write. Went with
[System.IO.File]::WriteAllText()+UTF8Encoding($false)from PowerShell tooling; Python'sopen(encoding="utf-8")is safe by default. Both paths tested. - Dry-run mode. Every mutation can render the diff without applying it. Copilot proposes, human confirms.
Result
- Editing a batch of reports that used to be a full afternoon of clicking is now a chat session ending in a diff review.
- Zero "Something's wrong with one or more fields" dialogs since the field-format was tightened at the tool boundary.
- MCP tool set is small enough (six tools) that a new caller can learn the whole surface in one sitting.
What I'd change
- A test corpus of PBIX files with known-broken edge cases would catch regressions before they hit a real report.
- Structured error output instead of the raw exception traces some tools still return. MCP callers deserve better than a stack trace.
- Session-scoped undo. Every mutation is individually reversible, but a "roll back this whole session" affordance would make experimenting cheap.