Local Model Context Protocol server that lets an AI assistant inspect and safely mutate Power BI / Fabric semantic models via the XMLA endpoint.
Power BI Modeling MCP
A local Model Context Protocol server that
lets an AI assistant inspect and safely mutate Power BI / Fabric semantic
models via the XMLA endpoint. Authenticates with whatever identity
Azure.Identity.DefaultAzureCredential finds — your az login user
locally, and a Managed Identity in production.
Status: read tools, MCP resources, and write tools (with audit log, TMSL backup, dry-run preview, and confirm-on-destroy) are all in. Release engineering is the next milestone — see
ROADMAP.mdfor what's coming next andSPEC.mdfor the public contract.
Why
Power BI's tabular object model (TOM) is .NET-only. This server bridges TOM to MCP so any compliant client (Claude Desktop, etc.) can drive structural model changes — add measures, list tables, etc. — through an LLM, with the safety rails (audit log, TMSL backups, dry-run, confirm-on-destroy) you'd want in production.
Ships as a single self-contained binary. No JS or Python runtime to install.
The agent supplies the workspace and dataset on every call (the server
holds no "current connection"), and identity comes from az login
(locally) or Managed Identity (in Azure).
Quickstart
1. Prerequisites
- .NET 8 SDK (only for building from source)
- Premium / Fabric workspace (XMLA write requires it; shared capacity won't work)
- An identity with Member or Admin access to the target workspace
(Contributor isn't enough). Either:
- You, signed in via
az login— the local-dev path. - A Managed Identity (in production on Azure), with the tenant
setting Allow service principals (and managed identities) to use
Power BI APIs enabled and the MI added to the workspace via
Add-PowerBIWorkspaceUser ... -PrincipalType App.
- You, signed in via
- Tenant: Allow XMLA endpoints — Read Write turned on.
2. Run
dotnet run --project src/PbiModelingMcp
Logs go to stderr and to ~/.pbi-modeling-mcp/logs/server-YYYYMMDD.log.
Stdout is reserved for MCP JSON-RPC.
Wire the resulting binary into any MCP client (Claude Desktop, VS Code,
etc.). The agent calls list_workspaces, list_datasets, then passes
workspace + dataset to every model-touching tool.
HTTP transport (optional)
For remote MCP clients, switch to the Streamable HTTP transport:
export PBI_MCP__Transport=http
export PBI_MCP__HttpAuthToken=$(openssl rand -base64 32) # ≥ 32 chars, single-tenant
dotnet run --project src/PbiModelingMcp
Loopback (127.0.0.1:5000) by default. Binding to a non-loopback host
requires either TLS-terminated reverse-proxy fronting or
PBI_MCP__HttpAllowInsecure=true (don't). Every authenticated caller
acts as the server's Power BI identity — single-tenant deployments only.
See SECURITY.md for the trust-model writeup.
3. Optional advanced settings
All of these have sensible defaults; set them only if you need to deviate.
| Variable | Default | Purpose |
|---|---|---|
| PBI_MCP__AuditDir | ~/.pbi-modeling-mcp | Audit log + backup root |
| PBI_MCP__RequireConfirmDelete | true | Require confirm on delete_* tools |
| PBI_MCP__LogLevel | Information | Verbose/Debug/Information/Warning/Error |
| PBI_MCP__Actor | {user}@{host} | Identity recorded in audit events |
| PBI_MCP__Transport | stdio | stdio or http |
| PBI_MCP__HttpHost | 127.0.0.1 | Bind IP when Transport=http |
| PBI_MCP__HttpPort | 5000 | Bind port when Transport=http |
| PBI_MCP__HttpAuthToken | (none) | Required when Transport=http. Min 32 chars; sentinel placeholders rejected. |
| PBI_MCP__HttpAllowInsecure | false | Required to bind non-loopback without TLS |
A gitignored .env next to the binary is also read if present.
Tools
| Tool | Description |
|---|---|
| list_workspaces() | Power BI REST: workspaces visible to the resolved identity. |
| list_datasets(workspace) | Power BI REST: datasets in a workspace. |
| list_tables(workspace, dataset) | Tables in a model. |
| list_measures(workspace, dataset, table) | Measures on a table. |
| get_measure(workspace, dataset, table, name) | Full detail of one measure. |
| add_measure(workspace, dataset, table, name, dax, ...) | Add a measure (dryRun to preview). |
| update_measure(workspace, dataset, table, name, ...) | Partial update; null=unchanged, ""=clear. |
| delete_measure(workspace, dataset, table, name, ...) | Delete a measure (confirms by default). |
Full contract is in SPEC.md.
Project Layout
custom-pbi-modeling-mcp/
├── README.md you are here
├── SPEC.md public contract (tools, env, wire formats)
├── ROADMAP.md what's next
├── docs/
│ ├── architecture.md implementation notes (non-normative)
│ ├── audit-schema.md versioned audit-log schema
│ └── decisions.md append-only log of non-obvious calls
├── src/
│ └── PbiModelingMcp/ the MCP server
│ ├── Auth/ ITokenProvider + DefaultAzureCredentialTokenProvider
│ ├── Configuration/ ServerOptions, DotEnv loader
│ ├── Connection/ ConnectionManager + TomServerFactory
│ ├── Modeling/ IModelingService — TOM ops live here
│ ├── Tools/ MCP tool methods (attribute-based)
│ ├── Http/ HTTP-transport host: bearer auth, /healthz, startup checks
│ ├── PowerBi/ PowerBiRestClient (workspace/dataset listing)
│ ├── Audit/ AuditLogger + BackupWriter
│ └── Program.cs host + MCP wiring (stdio + HTTP branches)
└── tests/
├── PbiModelingMcp.Tests/ xUnit unit tests
└── PbiModelingMcp.IntegrationTests/ in-process HTTP host tests
Development
dotnet build # solution-wide build, warnings-as-errors
dotnet test # run unit tests
dotnet format # apply code style
Engineering hygiene is enforced via Directory.Build.props
(nullable, analyzers, warnings-as-errors), Directory.Packages.props
(central package management), and .editorconfig.
Troubleshooting
The specified Power BI workspace ('...') is not found.
The XMLA endpoint requires the workspace name, not the GUID. Also confirm
the SP is a Member or Admin of that workspace.
OptionsValidationException at startup
A PBI_MCP__* setting (e.g. LogLevel) is malformed. The server validates
on start — the message names the offending field.
Extension host crashes / OOM in the dev container
Allocate at least 4 GB to your dev container VM. The bundled
.devcontainer/devcontainer.json requests
8 GB.
Contributing
See CONTRIBUTING.md. Issues and PRs welcome.