loading…
Search for a command to run...
loading…
Local-first MCP server for Power BI Desktop automation. Automate semantic model changes, DAX, Power Query, Excel, and report layout from MCP-capable AI clients.
Local-first MCP server for Power BI Desktop automation. Automate semantic model changes, DAX, Power Query, Excel, and report layout from MCP-capable AI clients.

Local-first MCP server for Power BI Desktop automation
Automate semantic model changes, DAX, Power Query, Excel, and report layout from MCP-capable AI clients.
| Start | Setup | Tools | Security |
|---|---|---|---|
| Quick start | MCP client setup | Tool catalog | Security |
pbi-tools.No Power BI Pro license is required for the local Desktop workflow.
MCP Client --(stdio or sse)--> src/server.py
|
+-- src/mcp_core.py FastMCP instance, _run, lifecycle
+-- src/wrappers/ 14 thin wrappers/<domain>.py
+-- src/tools/ business logic (the *_tool fns)
+-- TOM/.NET ─────────> Power BI Desktop local SSAS
+-- ADOMD ────────────> DAX query execution
+-- openpyxl ─────────> Excel read/write/format
+-- pbi-tools ────────> report extract/compile + visuals
+-- src/security.py path, DAX, payload safeguards
Full module layering and the visuals/ submodule fan-out: see ARCHITECTURE.md.
| Requirement | Install |
|---|---|
| Windows | Power BI Desktop local engine is Windows-only |
| Power BI Desktop | winget install Microsoft.PowerBIDesktop |
| Python 3.11+ | winget install Python.Python.3.11 |
| .NET 6+ Runtime | winget install Microsoft.DotNet.Runtime.6 |
| pbi-tools | winget install pbi-tools or dotnet tool install -g pbi-tools |
ADOMD.NET ships with Power BI Desktop. If pbi-tools is not on PATH, set PBI_TOOLS_PATH.
git clone https://github.com/imnotStealthy/powerbi-mcp-local.git
cd powerbi-mcp-local
pip install -r requirements.txt
Open Power BI Desktop with a .pbix file, then verify connectivity:
python tests/test_connection.py
Start the MCP server:
python src/server.py
Useful launch modes:
python src/server.py --transport sse --port 8765
python src/server.py --readonly
python src/server.py --profile readonly # ~56 read tools
python src/server.py --profile write # readonly + writes (no destructive)
python src/server.py --profile grading # 25-tool surface for evaluation workflows
python src/server.py --profile all # default — every tool
For SSE auth:
$env:PBI_MCP_AUTH_TOKEN = "your-secret-token"
python src/server.py --transport sse --port 8765
Clients must send:
Authorization: Bearer your-secret-token
Standard stdio config:
{
"mcpServers": {
"powerbi": {
"command": "python",
"args": ["C:\\path\\to\\powerbi-mcp-local\\src\\server.py"]
}
}
}
Google Antigravity (Gemini-based IDE) ships a stricter MCP client that
drops the connection at resources/list against the default FastMCP
1.27.x stdio server. Since v0.12.1 the project ships a dedicated entry
point — src/server_antigravity.py — that strips the capability
advertisement to tools only, forces UTF-8 / \n stdio, and routes
every log to stderr at ERROR level. The default src/server.py is left
untouched for Claude Desktop / Cursor / Anthropic CLI.
The PowerShell wrapper at tools/antigravity_mcp_launcher.ps1 pins the
working directory and exports PYTHONUTF8=1 / PYTHONIOENCODING=utf-8
before invoking the venv Python on server_antigravity.py, so the
encoding stays stable regardless of how Antigravity spawns the process.
%USERPROFILE%\.gemini\antigravity\mcp_config.json:
{
"mcpServers": {
"powerbi": {
"command": "powershell.exe",
"args": [
"-NoProfile",
"-ExecutionPolicy",
"Bypass",
"-File",
"D:\\Users\\stealthy\\Documents\\GitHub\\powerbi-mcp-local\\tools\\antigravity_mcp_launcher.ps1",
"--profile",
"readonly"
]
}
}
}
Adjust the -File path to your clone. --profile readonly keeps
tools/list small while validating the client connection — switch to
--profile all (or omit the flag) once Antigravity lists tools
successfully. --readonly and --profile {readonly,write,all,grading}
are all forwarded through the wrapper to the entry point with the same
semantics as src/server.py.
Local compatibility probe:
$init = @(
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"antigravity-probe","version":"1.0"}}}',
'{"jsonrpc":"2.0","method":"notifications/initialized","params":{}}'
)
($init + '{"jsonrpc":"2.0","id":2,"method":"resources/list","params":{}}') -join "`n" |
powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\tools\antigravity_mcp_launcher.ps1 --profile readonly
($init + '{"jsonrpc":"2.0","id":3,"method":"tools/list","params":{}}') -join "`n" |
powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\tools\antigravity_mcp_launcher.ps1 --profile readonly
SSE endpoint:
http://localhost:8765/sse
Setup guides:
Connect to Power BI and list all tables with columns.Create a measure called Total Sales in table Sales.Run this DAX query and show top 20 rows.Extract report, add a new page, place 3 visuals, then compile.162 MCP tools are grouped into these areas:
| Area | Coverage |
|---|---|
| Model discovery | instances, tables, measures, relationships, metadata, validation |
| Model mutations | measures, columns, tables, relationships, formats, role-based DAX |
| Query and import | DAX execution, traces, validation, refresh, model export |
| Power Query (M) | read, write, create, import, bulk Excel/folder sources |
| PBIP/TMDL | list, read, write, and patch TMDL project files |
| Workflows | model audit, Excel import, measure workflow automation |
| Quality gates | DAX linting, visual checks, persistence, scenarios, report validation |
| RLS and calc groups | roles, filters, members, calculation groups |
| Excel | workbook, sheet, cell/range, formatting, search, Power BI import checks |
| Reports and visuals | extract, compile, pages, cards, charts, slicers, themes, dashboards |
Unified visual creation is available through:
pbi_add_visual(visual_type, config)
In-place binding edits (no remove + recreate) via:
pbi_update_visual_bindings(extract_folder, page, visual_id,
projections={"Y": ["Total Sales"]})
Force Power BI Desktop to flush in-memory TOM mutations to the PBIX file (opt-in UI automation):
$env:PBI_MCP_ALLOW_UI_AUTOMATION = "1" # set before launching the server
pbi_persist_now(pbix_path="C:\\reports\\sales.pbix", confirm=true)
Excel input -> Power Query -> model updates -> measures -> validation -> report layout -> compile PBIX
Common tool chain:
excel_write_range
pbi_create_import_query
pbi_create_relationship
pbi_create_measure
pbi_refresh
pbi_execute_dax
pbi_extract_report
pbi_build_dashboard
pbi_compile_report
| Symptom | Fix |
|---|---|
No module named 'clr' |
Install .NET 6+ runtime, then restart terminal |
No running PBI Desktop instance found |
Open a .pbix in Power BI Desktop first |
pbi-tools not found |
Add it to PATH or set PBI_TOOLS_PATH |
PermissionError on .xlsx |
Close Excel; workbook files are locked while open |
| Path blocked by policy | Configure PBI_MCP_ALLOWED_DIRS |
Built-in safeguards include:
Details: SECURITY.md
| Variable | Default | Purpose |
|---|---|---|
PBI_MCP_AUTH_TOKEN |
unset | SSE Bearer token (≥32 chars when set) |
PBI_MCP_ALLOWED_ORIGINS |
unset | Extra Host/Origin allowlist for SSE |
PBI_MCP_ALLOW_UNAUTHENTICATED_SSE |
0 |
Acknowledge non-loopback SSE without auth |
PBI_MCP_READONLY |
0 |
Block all write/destructive tools |
PBI_MCP_ALLOW_DMV |
0 |
Allow $SYSTEM.* / DISCOVER_* DAX |
PBI_MCP_ALLOW_EXTERNAL_M |
0 |
Bypass the M function blocklist |
PBI_MCP_ALLOW_UI_AUTOMATION |
0 |
Required for pbi_persist_now (Ctrl+S) |
PBI_MCP_PERSIST_USE_SENDINPUT |
0 |
Fall back from PostMessage to SendInput |
PBI_MCP_PBI_TOOLS_TIMEOUT |
300 |
pbi-tools subprocess timeout (s) |
PBI_MCP_ALLOWED_DIRS |
cwd | ;-separated filesystem roots |
PBI_MCP_SECURITY_POLICY |
unset | Path or inline JSON for security_policy.json |
PBI_MCP_AUDIT / PBI_MCP_STRICT_REGISTRY |
0 |
Tool registry audit (CI) |
pip install -e ".[dev]"
pytest -q
ruff check src tests
ruff format --check src tests
CI runs pytest --cov=src --cov-fail-under=54 on Windows + an offline subset on Ubuntu, plus ruff lint + format check, on every PR. Strict registry audit (PBI_MCP_STRICT_REGISTRY=1) ensures every public pbi_*_tool has a matching @mcp.tool() wrapper.
powerbi-mcp-local/
├── src/
│ ├── server.py CLI + transport launcher + @mcp.resource/@mcp.prompt (~320 L)
│ ├── mcp_core.py FastMCP instance + CONNECTION_MANAGER + lifecycle (~250 L)
│ ├── pbi_connection.py TOM + ADOMD bring-up, write helpers, op history
│ ├── security.py path / DAX / payload guards + tool category sets
│ ├── wrappers/ 14 domain modules — `register_tool(pbi_*_tool)` calls
│ └── tools/ business logic (*_tool functions)
│ └── visuals/ 17 focused submodules (layout, bindings, containers, charts, …)
├── tests/ 167 offline unit tests (live-only scripts gitignored)
├── .github/workflows/ci.yml pytest + coverage + ruff on Windows + Ubuntu, py3.11/3.12
├── docs/, specs/
├── ARCHITECTURE.md module layering, visuals/ tree, profiles, registry audit
├── CHANGELOG.md active changelog (last 3 releases)
├── CHANGELOG-archive.md historical changelog
├── SECURITY.md
├── pyproject.toml ruff, pytest, coverage config + dev deps
└── requirements.txt
MIT
Run in your terminal:
claude mcp add powerbi-mcp-local -- npx Security
Low riskAutomated heuristic from public metadata — not a security guarantee.