AK / PORTFOLIOWork
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 Property and 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_name is always the column name only — never Table.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's open(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.

Command palette

Search and jump to any page.