Skip to main content
Glama

powerbi-mcp-local banner

powerbi-mcp-local

MCP server for local Power BI Desktop automation

It exposes tools for semantic model changes, DAX, Power Query, Excel, and report layouts to MCP clients.

Python 3.11+ Protocol MCP License MIT Tools 141

CI

Related MCP server: Power BI Report MCP Server

What It Does

  • Connects AI tools to a running local Power BI Desktop engine.

  • Automates tables, columns, measures, relationships, DAX, refreshes, and Power Query.

  • Reads/writes Excel files for local BI workflows.

  • Extracts, patches, validates, and compiles report layouts through pbi-tools.

No Power BI Pro license is required for the local Desktop workflow.

Architecture

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.

Requirements

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.

Quick Start

git clone https://github.com/inerthel-agi/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

Multiple clients can run simultaneously. Each server uses a dedicated worker for blocking operations; writes to the same model or file are serialized across MCP processes. A conflicting write returns resource_busy after 10 seconds. pbi_system_health reports the active operation while it runs.

If a write returns write_outcome_unknown, check system health and inspect the target before retrying. A client timeout does not release the resource lock while native work is still running.

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

MCP Client Setup

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:

Example Prompts

  • 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.

Tool Catalog

141 MCP tools are grouped into these areas. Visual creation is consolidated behind pbi_add_visual (28 chart types via visual_type) and pbi_add_visual_from_intent:

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)

Automation Flow

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

Troubleshooting

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

Security

Built-in safeguards include:

  • local path restrictions and traversal protection

  • DAX/DMV unsafe-query guards

  • Power Query SSRF protections (cloud DW + SaaS + reflection blocked by default)

  • export redaction controls

  • zip safety checks (Excel + PBIX)

  • response-size cap (16 MiB by default) + per-minute rate limit (600/min)

  • SSE Bearer auth + DNS-rebinding host/origin allowlist

  • tool-call auditing

Details: SECURITY.md

Environment variables (quick reference)

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_CONNECT_TIMEOUT_SECONDS

10

Native connection deadline (s)

PBI_MCP_OPERATION_TIMEOUT_SECONDS

300

Shared tool deadline, including probes and reconnects (s); explicit tool timeouts take priority

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)

Development

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.

Repository Layout

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

License

MIT

Available Tools

141 tools
excel_auto_widthD

Inputs: sheet (required), file_path (required). Returns: result

excel_create_sheetD

Inputs: name (required), file_path (required), position. Returns: result

excel_create_workbookD

Inputs: file_path (required), sheets. Returns: result

excel_delete_sheetD

Inputs: name (required), file_path (required). Returns: result

excel_format_rangeD

Inputs: range (required), sheet (required), format (required), file_path (required). Returns: result

excel_list_sheetsD

Inputs: file_path (required). Returns: result

excel_read_cellD

Inputs: cell (required), sheet (required), file_path (required). Returns: result

excel_read_sheetD

Inputs: sheet (required), file_path (required), limit, range. Returns: result

excel_to_pbi_checkD

Inputs: file_path (required). Returns: result

excel_workbook_infoD

Inputs: file_path (required). Returns: result

excel_write_cellD

Inputs: cell (required), sheet (required), value (required), file_path (required), +1 more. Returns: result

excel_write_rangeD

Inputs: data (required), sheet (required), file_path (required), start_cell (required). Returns: result

pbi_add_conditional_formattingB

Add table / matrix conditional formatting (data bar, colour scale, icon set).

Power BI stores conditional formatting on table-style visuals under singleVisual.objects.values (or columnFormatting on older builds), keyed by a queryRef that matches the column / measure bound to the visual's Values role. We write the canonical shape Power BI Desktop emits in current builds.

Parameters

column_name: The display name (Property) of the measure / column inside the Values projection. Matched case-insensitively against the prototypeQuery Select entries. format_type: One of dataBar, colorScale, iconSet. min_color, mid_color, max_color: Colour scale endpoints (and optional midpoint). Hex strings. bar_color: Data bar fill colour. icon_set: One of threeArrows, threeArrowsGray, threeTrafficLights, threeSymbols, threeFlags, fiveArrows.

Inputs: page (required), visual_id (required), column_name (required), format_type (required), +6 more. Returns: result

pbi_add_role_memberA

Add (or update) a member (user/group) on a role.

member_type: 'external' (default, ExternalModelRoleMember — recommended for Power BI service) 'windows' (WindowsModelRoleMember — domain principal SID)

With overwrite=False (default), raises PowerBIDuplicateError if a member with the same member_name already exists on the role. With overwrite=True the existing member's identity_provider is refreshed (when external) and the response carries action="updated".

Inputs: role (required), member_name (required), overwrite, member_type, +1 more. Returns: result

pbi_add_visualA

Add any visual to a report page — the single entry point for visual creation.

visual_type: card, labelled_card, multi_row_card, bar_chart, stacked_bar_chart, stacked_column_chart, clustered_column_chart, hundred_percent_stacked_bar_chart, hundred_percent_stacked_column_chart, ribbon_chart, line_chart, area_chart, stacked_area_chart, hundred_percent_stacked_area_chart, donut, pie_chart, treemap, funnel, table, waterfall, scatter_chart, combo_chart, slicer, gauge, kpi, matrix, map, text_box. config: per-type keys — categorical charts: category_column + value_measure (+ legend_column); axis charts: axis_column + value_measures (list); card/gauge: measure; table: columns (list); matrix: rows + values (lists); scatter: category_column + x_measure + y_measure; combo: category_column + bar_measures + line_measures; kpi: indicator_measure + trend_column; slicer: column (+ slicer_type); map: location (+ measure); text_box: text. Error messages name any missing key. Prefer pbi_add_visual_from_intent when you have a business intent rather than an exact type. dry_run: when True, run all validation and binding logic but skip the layout disk write — response carries dry_run=True and a write_log.

Inputs: x (required), y (required), page (required), visual_type (required), +6 more. Returns: result

pbi_add_visual_from_intentB

Plan a visual from a business-intent spec, then build it.

Same intent schema as pbi_plan_visual_tool. The chosen visual is created through the generic dispatcher, so all field validation and binding logic applies. dry_run=True plans and validates without writing the layout.

Inputs: x (required), y (required), page (required), intent (required), +5 more. Returns: result

pbi_apply_designD

Inputs: extract_folder (required), preset, style_cards, page_background. Returns: result

pbi_apply_format_presetA

Apply a named format preset to a list of measures or columns.

Wraps pbi_set_format_tool so callers don't need to memorise the raw format string. Supported preset families: currency_eur(_k|_m), currency_usd, percent (0dp/1dp/2dp/4dp), thousands, millions, decimal_2, integer, integer_no_sep, date_iso, date_short_fr, date_short_us, date_long_fr, datetime_iso. Use pbi_list_format_presets_tool to inspect them.

Inputs: names (required), table (required), preset (required), object_type. Returns: result

pbi_apply_style_presetB

Apply a one-shot visual style preset to an existing .pbix.

wallpaper_fit (optional) overrides the preset's default canvas image scaling. Must be one of Fit, Fill, Normal, Stretch (Power BI's enum). When omitted the preset's page.wallpaper.fit is used.

See module docstring for the full flow.

Inputs: preset (required), pbix_path (required), pages, custom_spec, +3 more. Returns: result

pbi_apply_themeD

Inputs: extract_folder (required), theme_json_path (required). Returns: result

pbi_audit_modelC

Detect missing, ambiguous, bidirectional, and orphaned model structures.

Inputs: include_hidden. Returns: result

pbi_auto_grid_layoutC

Compute non-overlapping (x, y, width, height) for visual specs on a column-based grid. Pure offline calculation.

Inputs: specs (required), gap, cols, start_x, +5 more. Returns: result

pbi_build_dashboardD

Inputs: page (required), layout (required), extract_folder (required). Returns: result

pbi_bulk_import_excelC

Bulk-create Excel import queries for multiple tables.

Inputs: excel_path (required), refresh_after, promote_headers, sheet_table_map. Returns: result

pbi_check_scaffold_spec_dbcc_risksA

Pre-build DBCC risk analysis on a tables spec.

Mirrors the input shape of pbi_create_persistent_report_tool and pbi_scaffold_pbix_tool. Pass the same tables list before calling the builder to catch empty-String-column risks up front.

Inputs: tables (required). Returns: result

pbi_compare_report_versionsC

Compare two extracted report versions by pages, visuals, and layout score.

Inputs: extract_folder_a (required), extract_folder_b (required), label_a, label_b. Returns: result

pbi_compile_reportD

Inputs: output_path (required), extract_folder (required), force. Returns: result

pbi_connectC

Connect to Power BI Desktop and report the active instance.

Inputs: preferred_port, force_reconnect. Returns: result

pbi_convert_visual_typeC

Migrate an existing visual to a different type while preserving compatible field bindings.

Inputs: page (required), new_type (required), visual_id (required), extract_folder (required). Returns: result

pbi_create_calc_groupB

Create a calculation group table with its Name column and optional calculation items.

items: list of {name, expression, format_string_expression?, ordinal?}.

Inputs: table_name (required), items, overwrite, precedence, +1 more. Returns: result

pbi_create_columnC

Create or update a calculated column.

Inputs: name (required), table (required), expression (required), data_type, +4 more. Returns: result

pbi_create_contribution_measureB

Create a %-of-total contribution measure.

DAX template:

DIVIDE([{base}], CALCULATE([{base}], ALL({scope_columns})))

scope_columns defines the denominator scope — typically the dimension columns whose total you want each row to be a percentage of (e.g. ["Categorie.Nom catégorie"] for "this category's % of all categories").

Inputs: table (required), base_measure (required), scope_columns (required), overwrite, +3 more. Returns: result

pbi_create_csv_import_queryC

Generate and inject a CSV import Power Query for a table.

Inputs: table (required), csv_path (required), encoding, delimiter, +4 more. Returns: result

pbi_create_folder_import_queryC

Generate and inject a folder import Power Query for a table.

Inputs: table (required), folder_path (required), refresh_after, partition_name, +2 more. Returns: result

pbi_create_import_queryC

Generate and inject an Excel import Power Query for a table.

Inputs: table (required), excel_path (required), sheet_name (required), refresh_after, +2 more. Returns: result

pbi_create_measureC

Create or update a DAX measure.

Inputs: name (required), table (required), expression (required), is_hidden, +4 more. Returns: result

pbi_create_measuresA

Batch-create or update multiple DAX measures with a single SaveChanges call.

Each item in measures must have at minimum: name, expression. Optional keys: format_string, description, display_folder, is_hidden.

With dry_run=True, every measure is name + expression validated and the per-item planned_action is reported (would_create/would_update/ would_fail), but no model mutation happens — SaveChanges is never called. Use this for an LLM preflight before committing the batch.

Inputs: table (required), measures (required), dry_run, overwrite, +1 more. Returns: result

pbi_create_pageD

Inputs: display_name (required), extract_folder (required), width, height. Returns: result

pbi_create_persistent_reportB

Create a persistent PBIX with DataModel, DAX measures, relationships, pages, and native visuals.

prime_string_store (default True) injects a one-row sentinel into every empty Import table whose schema declares a String column, so Vertipaq's DBCC string-store consistency check passes on reopen. Disable only when you know the table will be populated by Power BI Desktop's first refresh (e.g. via a DirectQuery binding or a Power Query expression set after the PBIX is created).

style_preset (optional) names a built-in styling preset (glassmorph_dark, glassmorph_light, neon_cyber, minimal_corporate, dark_pro, or custom). When set, the matching preset is applied to the file via pbi_apply_style_preset_tool right after save, so the produced PBIX opens with wallpaper, card chrome, accent colours, and the preset's theme already wired up. style_wallpaper_path overrides the preset's default wallpaper; style_custom_spec is required when style_preset='custom'.

Inputs: tables (required), output_path (required), pages, measures, +6 more. Returns: result

pbi_create_relationshipA

Create or update a single-column relationship.

With overwrite=False (default), raises PowerBIDuplicateError when a relationship between the same endpoint columns already exists. With overwrite=True the existing relationship is updated in place (cardinality / direction / is_active refreshed) and the response carries action="updated".

Inputs: to_table (required), to_column (required), from_table (required), from_column (required), +5 more. Returns: result

pbi_create_roleC

Create or update a model role.

Inputs: name (required), overwrite, permission, description. Returns: result

pbi_create_rolling_average_measureB

Create a trailing rolling average measure.

DAX template:

AVERAGEX(DATESINPERIOD({date_table}[{date_column}], LASTDATE({date_table}[{date_column}]), -{window}, {granularity}), [{base}])

Inputs: table (required), window (required), date_table (required), date_column (required), +6 more. Returns: result

pbi_create_tableC

Create or update a calculated table.

Inputs: name (required), expression (required), is_hidden, overwrite, +1 more. Returns: result

pbi_create_time_intelligence_packA

Create a family of time-intelligence measures from a base measure.

Default patterns: YTD, MTD, QTD, SPY, YOY, YOY %, MA3. Each generated measure is named "{base_measure} {suffix}" (e.g. "Sales YTD") and lives on table (typically the same fact table as the base measure). Dependency-aware: requesting YOY% automatically adds YOY and SPY so the whole family resolves.

With format_inherit=True, each measure inherits base_measure's format string (best-effort lookup on the live model). format_string overrides that when supplied. Patterns that prescribe their own format (e.g. YOY % → "0.00%") win unless format_string is explicit.

With dry_run=True no model mutation occurs — the response carries a plan listing every measure that would be created/updated/skipped.

Inputs: table (required), date_table (required), date_column (required), base_measure (required), +6 more. Returns: result

pbi_create_topn_measureB

Create a Top-N filter measure.

DAX template:

IF(RANKX(ALL({dim_table}[{dim_column}]), [{rank_measure}], , DESC) <= {N}, [{base}], BLANK())

Use as the value of a chart visual to surface only the top N members of a dimension. rank_measure defaults to base_measure.

Inputs: n (required), table (required), base_measure (required), dimension_table (required), +6 more. Returns: result

pbi_create_variance_measureB

Create a period-over-period variance measure.

DAX template (parametrised by granularity ∈ {year, month, quarter}):

[{base}] - CALCULATE([{base}], DATEADD({date_table}[{date_column}], {offset}, {granularity}))

Default compare_period_offset = -1 ⇒ "current period vs previous one".

Inputs: table (required), date_table (required), date_column (required), base_measure (required), +6 more. Returns: result

pbi_delete_calc_groupC

Delete a calculation group table.

Inputs: table_name (required). Returns: result

pbi_delete_columnC

Delete a column from a table.

Inputs: name (required), table (required). Returns: result

pbi_delete_measureC

Delete a DAX measure.

Inputs: name (required), table (required). Returns: result

pbi_delete_pageD

Inputs: page (required), extract_folder (required). Returns: result

pbi_delete_relationshipA

Delete a relationship by name or by endpoint columns.

Inputs: name, to_table, to_column, from_table, +1 more. Returns: result

pbi_delete_roleC

Delete a model role.

Inputs: name (required). Returns: result

pbi_delete_tableB

Delete a table. Removes associated relationships and measures.

Inputs: name (required). Returns: result

pbi_describe_pageC

Return a structured, LLM-friendly snapshot of a report page.

One entry per visual with id/type/position, role-keyed bindings, formatting (title + axis titles + label_display_units), and a binding_health rollup (ok | missing_field | wrong_role) based on live-model validation when manager is supplied.

Inputs: page (required), extract_folder (required). Returns: result

pbi_detect_circular_dependenciesA

Detect cycles in the measure dependency graph.

Builds a graph of measure → referenced measures (parsed from DAX [Name] tokens that match a known measure name) and runs a DFS to find strongly connected cycles. Self-references are reported separately.

Inputs: include_hidden. Returns: result

pbi_detect_dirty_datesC

Detect text columns that look like dirty dates.

Inputs: table, max_samples, scan_all_text_columns, min_parse_success_rate. Returns: result

pbi_detect_empty_visualsC

Execute lightweight DAX probes to detect visuals with no data.

Inputs: extract_folder (required), page, max_rows, include_slicers, +1 more. Returns: result

pbi_detect_missing_visualsB

Detect required visuals that are absent from a page.

Each requirement entry is a dict with at least visual_type and optionally count (default 1), contains_field (a reference like Date.Year that must appear in the visual's prototypeQuery), and label (free-form name surfaced in the report).

Inputs: page (required), requirements (required), extract_folder (required). Returns: result

pbi_detect_name_collisionsC

Detect table, column, and measure name collisions before writes.

Inputs: include_hidden. Returns: result

pbi_diagnose_pbix_dbccA

Statically diagnose DBCC string-store risks on a built .pbix.

Returns a list of issues (each with a type and a short message) and a valid flag. Use this before opening the file in Power BI Desktop to avoid the round-trip through the modal repair dialog.

Inputs: pbix_path (required). Returns: result

pbi_diagnose_render_risksA

Aggregate render-risk diagnostic for visuals on disk.

Read-only. Walks the extracted layout and reports conditions known to make Power BI Desktop refuse to render a visual or surface an opaque internal error:

  • Constant Y measure on a cartesian chart (line / combo / area / waterfall). The bug-0.92 family: a measure whose DAX expression has no column or measure reference (or returns BLANK() unconditionally) collapses every Y point to the same scalar with no axis dependency, which some PBI builds reject.

  • Unresolved measure home table — the binding falls back to the synthetic $Measures entity which PBI refuses to plot.

  • Missing column / measure / wrong reference kind in the live model (only when manager is supplied).

  • Query-ref mismatch between projections and prototypeQuery Select entries.

A live manager enables DAX-expression heuristics for the constant-measure check; without it the call still reports unresolved home tables and binding-shape issues that don't need the live model.

Pass visual_id to narrow to a single visual; pass page to narrow to a specific page; both omitted scans the whole report.

Inputs: extract_folder (required), page, visual_id, include_hidden. Returns: result

pbi_disable_card_autoscaleB

Disable the auto K/M/B unit-scaling on card visuals (sets labelDisplayUnits=1 plus an explicit labelPrecision).

Inputs: extract_folder (required), page, visual_ids, label_precision. Returns: result

pbi_excel_import_workflowC

Plan or run an Excel workbook import into Power BI.

Inputs: excel_path (required), apply, refresh_after, promote_headers, +1 more. Returns: result

pbi_execute_daxC

Execute a DAX or DMV query.

Inputs: query (required), max_rows, timeout_seconds. Returns: result

pbi_execute_dax_as_roleB

Execute a DAX query under a specific role context.

Inputs: role (required), query (required), username. Returns: result

pbi_export_active_themeA

Export the currently active theme JSON from an extracted report.

Writes a copy of the theme referenced by activeTheme (or the last entry in themeCollection) to output_path. Useful for capturing the baseline theme before customising it.

Inputs: output_path (required), extract_folder (required). Returns: result

pbi_export_correction_reportB

Generate a Markdown correction report aggregating all analysis tools.

Output sections: model overview, star-schema verdict, circular dependency scan, optional rubric scoring, audit issues. Writes to output_path and returns the path plus an inline summary.

Inputs: output_path (required), extract_folder, rubric_criteria, fact_table_hints. Returns: result

pbi_export_modelC

Export the full model as JSON, optionally writing it to disk.

Inputs: path, include_hidden, include_row_counts. Returns: result

pbi_export_validation_reportC

Export model, DAX, layout, binding, and score validation as JSON.

Inputs: output_path (required), extract_folder, include_hidden, include_measure_tests, +2 more. Returns: result

pbi_extract_reportD

Inputs: pbix_path (required), extract_folder. Returns: result

pbi_generate_dax_context_promptA

Render a compact markdown snapshot of the model — tables, columns, measures, relationships — ready to paste into an LLM system prompt so the LLM can author DAX with full schema context in one round-trip.

Sections:

  • # Model: <name>

  • one ## <Table> per table with Columns: and (optionally) DAX Measures: lines

  • ## Relationships with A[X] → B[Y] (cardinality, active) rows

Output is truncated to max_chars (default 12 000) with a trailing note when truncation kicks in. Set include_dax=False to omit the measure expressions and stay terse.

Inputs: max_chars, include_dax, include_hidden, include_relationships. Returns: result

pbi_generate_measure_testsC

Generate and execute smoke tests for DAX measures.

Inputs: measures, max_measures, include_hidden. Returns: result

pbi_get_pageD

Inputs: page (required), extract_folder (required). Returns: result

pbi_get_power_queryC

Read the M expression for a specific table partition.

Inputs: table (required), partition_name. Returns: result

pbi_import_dax_fileC

Parse a .dax file and bulk-create measures.

Inputs: path (required), table, overwrite, stop_on_error, +2 more. Returns: result

pbi_import_excel_workbookC

Import an Excel workbook into Power BI tables in one call.

Inputs: excel_path (required), refresh_after, promote_headers, sheet_table_map. Returns: result

pbi_lint_daxC

Validate measures, formats, and measure/column name collisions.

Inputs: include_hidden, validate_expressions. Returns: result

pbi_lint_report_layoutA

Detect overlaps, excessive whitespace, tiny visuals, and missing titles.

Optional knobs to silence noise on intentionally dense pages:

  • ignore_warnings: list of warning types to drop (e.g. ["too_many_visuals", "visual_too_small", "missing_title", "excessive_whitespace", "layout_overloaded"]). Issues are never ignored — only warnings.

  • only_pages: restrict the scan to a list of page display names / internal names. Use to lint just the new pages an LLM produced.

  • max_visuals_per_page: override the default too_many_visuals threshold (defaults to MAX_VISUALS_PER_PAGE). Set high to effectively disable the warning without ignoring it.

Inputs: extract_folder (required), page, only_pages, ignore_warnings, +1 more. Returns: result

pbi_list_calc_groupsB

List calculation groups and their items.

Inputs: no named parameters. Returns: result

pbi_list_format_presetsA

Return the catalogue of format-string presets.

Optional filter_substring is matched (case-insensitive) against each preset's name to narrow the result — useful when an LLM only needs the "percent_*" or "currency_*" subset.

Inputs: filter_substring. Returns: result

pbi_list_instancesA

List discovered Power BI Desktop instances without forcing a connection.

Inputs: no named parameters. Returns: result

pbi_list_measuresB

List all model measures.

Inputs: include_hidden. Returns: result

pbi_list_pagesD

Inputs: extract_folder (required). Returns: result

pbi_list_power_queriesC

List table partitions and their current source expressions.

Inputs: include_hidden. Returns: result

pbi_list_relationshipsB

List model relationships.

Inputs: no named parameters. Returns: result

pbi_list_repairable_errorsA

Return the repairable-error vocabulary used by pbi_repair_loop.

Each entry maps a stable error code to its severity, whether the loop can fix it automatically, and the spec-level repair instruction an LLM should apply when it cannot.

Inputs: no named parameters. Returns: result

pbi_list_rolesA

List all model roles with their members and table filters.

Inputs: no named parameters. Returns: result

pbi_list_scaffold_templatesA

List the available scaffold templates with summary metrics.

Inputs: no named parameters. Returns: result

pbi_list_style_presetsA

Return the catalogue of built-in presets with summary metadata.

Inputs: no named parameters. Returns: result

pbi_list_tablesC

List model tables and columns.

Inputs: include_hidden, include_row_counts. Returns: result

pbi_list_tmdl_filesC

List TMDL files in a Power BI Project semantic model definition folder.

Inputs: project_path (required). Returns: result

pbi_measure_dependenciesC

Return calc-dependency graph rows (source → referenced object) from DISCOVER_CALC_DEPENDENCY.

Inputs: table, measure. Returns: result

pbi_measure_workflowD

Plan or run validated batch measure creation.

Inputs: table (required), measures (required), apply, overwrite. Returns: result

pbi_model_audit_workflowC

Run a compact model audit with recommendations for an agent.

Inputs: include_hidden, include_row_counts. Returns: result

pbi_model_infoC

Return a full model snapshot in a single call.

Inputs: include_hidden, include_row_counts. Returns: result

pbi_move_visualD

Inputs: x (required), y (required), page (required), visual_id (required), +3 more. Returns: result

pbi_operation_historyA

Return the most-recent N tool operations recorded by the connection manager (newest first).

Each entry: {ts, op, kind: "read"|"write", duration_ms, ok} plus error_type/error_code/error_message when ok is False. Use this to self-diagnose what just happened — e.g. an LLM can pull the last 5 calls after a failure to see which writes already landed.

Inputs: last_n. Returns: result

pbi_parameterize_data_sourceB

Make data sources portable via a Power Query parameter.

Creates (or updates) an M parameter named parameter_name whose default value is file_path. Then rewrites every M partition that string-matches file_path so it calls File.Contents(<parameter_name>) instead of the hardcoded path. After the parameter is wired in, a collaborator can change the file location once via Power BI Desktop's Transform data → Manage parameters dialog without editing each query.

partitions: optional explicit list of "Table[/Partition]" to rewrite. By default every M-source partition that references file_path is rewritten.

Inputs: file_path (required), parameter_name (required), dry_run, partitions, +1 more. Returns: result

pbi_patch_layoutA

Patch the modified Report/Layout back into the PBIX archive.

When force=True the call closes (and if necessary kills) Power BI Desktop so the PBIX can be overwritten. save_before_close (default True) sends Ctrl+S to every running PBI Desktop window via PostMessage before the kill, then waits up to 10 seconds for the PBIX mtime to change. This flushes any in-memory TOM mutations (measures, columns, role filters) that have not yet been persisted — without this, those changes are lost when the process is killed.

The save attempt is best-effort: it never raises, and the layout patch proceeds regardless of whether the save succeeded. The response includes save_attempt with telemetry so the caller can detect silent loss.

Inputs: pbix_path (required), extract_folder (required), force, include_hidden, +2 more. Returns: result

pbi_patch_tmdl_measureC

Create or replace a measure block in one table TMDL file.

Inputs: expression (required), table_file (required), measure_name (required), project_path (required), +3 more. Returns: result

pbi_persist_nowA

Trigger Power BI Desktop to save the open PBIX (Ctrl+S in the UI).

The Tabular engine has no programmatic save — every TOM mutation lives in memory until the user presses Ctrl+S. This tool drives that key chord through Win32 keyboard injection on the connected Desktop instance's main window.

Hard gates (both required):

  • The server process must have PBI_MCP_ALLOW_UI_AUTOMATION=1 set in its environment before launch.

  • The caller must pass confirm=True per call.

Either gate failing returns a structured error without acting. The automation only sends Ctrl+S; no other key sequences are emitted.

Parameters

pbix_path: Optional. When provided, the call polls the file's modification timestamp for up to timeout_seconds after sending Ctrl+S, and reports the observed delta in the response. Without it the call returns immediately after key injection. confirm: Must be True. Refusing-by-default avoids accidental focus steals from other tools. timeout_seconds: Bound on how long to wait for pbix_path mtime to change. Clamped to [1, 60].

Inputs: confirm, pbix_path, timeout_seconds. Returns: result

pbi_plan_visualA

Plan a visual from a business-intent spec without touching the report.

intent keys (all optional unless a rule needs them): metric / metrics — 'Table.Measure' reference(s) dimension — 'Table.Column' categorical axis time — 'Table.Column' time axis (implies trend) breakdown — 'Table.Column' secondary split (legend/series) target — 'Table.Measure' reference or numeric goal comparison, trend, parts_of_whole, correlation, geographic, detail_table, many_categories, filter_control — boolean hints

Returns the chosen visual_type, the matching pbi_add_visual config, and a rationale explaining the rule that fired.

Inputs: intent (required). Returns: result

pbi_read_tmdl_fileB

Read one TMDL file from a Power BI Project definition folder.

Inputs: project_path (required), relative_file (required). Returns: result

pbi_refreshC

Trigger a model or table refresh.

Inputs: target, refresh_type. Returns: result

pbi_refresh_metadataA

Reload cached TOM schema from the server (cheaper than full reconnect).

Inputs: no named parameters. Returns: result

pbi_relocate_data_sourceA

Bulk-rewrite a hardcoded file or folder path inside every M partition.

Use when a workbook moves and queries break with DataSource.NotFound. Looks for old_path as a substring of each M-source partition expression and replaces it with new_path. Calculated/query-source partitions are skipped. dry_run=True returns the planned changes without writing.

Inputs: new_path (required), old_path (required), dry_run, refresh_after, +1 more. Returns: result

pbi_remove_role_memberB

Remove a member from a role, matching by MemberName (case-insensitive).

Inputs: role (required), member_name (required). Returns: result

pbi_remove_visualD

Inputs: page (required), visual_id (required), extract_folder (required). Returns: result

pbi_rename_columnC

Rename a column. Callers are responsible for updating dependent DAX.

Inputs: name (required), table (required), new_name (required). Returns: result

pbi_rename_measureA

Rename a DAX measure. Callers must update downstream DAX expressions themselves.

Inputs: name (required), table (required), new_name (required). Returns: result

pbi_rename_tableB

Rename a table. Callers are responsible for updating dependent DAX expressions.

Inputs: name (required), new_name (required). Returns: result

pbi_repair_loopA

Detect → classify → auto-repair → re-verify loop for a report extract.

Each round scans visual bindings, applies the deterministic repairs (query-ref mismatches, measure home tables) when apply=True, saves, and rescans — until no repair is applied or max_rounds is reached. Gauge target consistency and double display-unit scaling are then checked on the converged layout; check_empty_visuals=True (requires a live connection) additionally probes every visual's data query.

The response's repairable_errors lists the residual issues in classified form, each with an llm_action telling the calling model how to fix its spec.

Inputs: extract_folder (required), page, apply, max_rounds, +2 more. Returns: result

pbi_repair_report_fieldsD

Inputs: extract_folder (required), page, apply, include_hidden. Returns: result

pbi_run_scenarioC

Run a complete QA scenario against the active model and optional extracted layout.

Inputs: scenario (required), extract_folder, include_hidden. Returns: result

pbi_scaffold_pbixA

Create a starter PBIX from a named template.

Templates: blank (date table only), finance, sales, analytics. Optional theme_json_path is validated against the Power BI theme schema (size, key allowlist, colour format, URL-bearing values) and embedded in the generated file. Additional measures supplied via extra_measures are appended after the template's baseline pack.

Inputs: output_path (required), template, extra_measures, theme_json_path, +2 more. Returns: result

pbi_score_dashboardC

Score dashboard quality across model, DAX, layout, and readability.

Inputs: extract_folder, include_hidden. Returns: result

pbi_score_rubricA

Aggregate scoring across multiple validators.

Each criterion is a dict with:

  • id (str): unique identifier

  • label (str): human description

  • check (str): one of star_schema, no_circular_deps, power_query_steps, missing_visuals, measure_exists

  • weight (float, default 1.0)

  • params (dict): check-specific parameters

Returns per-criterion verdicts plus a weighted total score in [0, 1].

Inputs: criteria (required), extract_folder. Returns: result

pbi_set_column_data_typeA

Set the DataType (and optionally FormatString) of an existing column.

Works for any column kind (source, calculated, calculated table column). Use when Power Query type hints (Int64.Type etc.) are overridden by PBI's downstream inference and the column ends up as the wrong type.

data_type accepts standard TOM names (Int64, Decimal, Double, String, DateTime, Boolean, Currency, Binary, Variant).

Inputs: table (required), column (required), data_type (required), format_string. Returns: result

pbi_set_formatA

Batch-apply format strings to measures or columns.

Inputs: names (required), table (required), format_string (required), object_type. Returns: result

pbi_set_page_sizeD

Inputs: page (required), width (required), height (required), extract_folder (required). Returns: result

pbi_set_power_queryC

Write or update an M expression on a table partition.

Inputs: table (required), m_expression (required), refresh_after, partition_name. Returns: result

pbi_set_role_filterB

Set (or remove when filter_expression is None/empty) the RLS DAX filter on a table for a role.

Inputs: role (required), table (required), filter_expression (required). Returns: result

pbi_set_series_colorA

Set the fill colour for a single series of a chart visual.

Power BI stores per-series overrides inside singleVisual.objects.dataPoint as additional list entries with an id selector pinned to the target series' measure or column. The bare defaultColor property only affects series that do not have an explicit override — using it on a multi-series chart paints every series the same colour.

Specify the target series by series_index (0-based across the visual's projections, in role order) or series_name (matches the queryRef or the underlying measure / column Property name). Optional role narrows the lookup to a specific projection role (e.g. "Y", "Values").

Inputs: page (required), color (required), visual_id (required), extract_folder (required), +3 more. Returns: result

pbi_set_visual_format_propertyA

Set / reset formatting properties on a visual's singleVisual.objects[<object_name>][0].properties.

Parameters

extract_folder, page, visual_id, object_name: Identify the target. object_name is the Power BI object key ('title', 'dataPoint', 'labels', 'background', 'general', etc.). properties: {property_name: value} to set. Values are encoded as Power BI literals via the type hint in property_types. property_types: {property_name: type_hint}. Valid hints:

- ``auto`` (default) — infer from Python type
- ``bool`` — booleans
- ``int`` — integers (alias: ``integer``)
- ``decimal`` — floats / numerics (alias: ``float``, ``number``)
- ``text`` — strings (alias: ``string``)
- ``color`` — hex strings ``"#RRGGBB"`` (alias: ``fill``, ``hex``,
  ``rgb``); a previously-encoded ``{"solid": {"color": ...}}``
  object is also accepted and unwrapped automatically
- ``raw`` — pass the value untouched (advanced)

Type names are case-insensitive. Unknown hints raise with the
full list.

reset_properties: Optional list of property names to delete from the visual's property bag. Use this to revert a property to Power BI's default — passing an empty string would leave the key set to a blank value instead.

Examples

Set a title (text):

pbi_set_visual_format_property(
    extract_folder, page, visual_id,
    object_name="title",
    properties={"text": "Sales", "show": True},
    property_types={"text": "text", "show": "bool"},
)

Set a fill color (single-series default):

pbi_set_visual_format_property(
    ...,
    object_name="dataPoint",
    properties={"defaultColor": "#4472C4"},
    property_types={"defaultColor": "color"},
)

Reset a property to Power BI's default:

pbi_set_visual_format_property(
    ...,
    object_name="title",
    reset_properties=["text"],
)

Inputs: page (required), visual_id (required), object_name (required), extract_folder (required), +3 more. Returns: result

pbi_system_healthA

Single-call self-diagnostic for stability and dependency status.

Read-only. Skips any TOM/ADOMD probe gracefully when no connection is active. Use as a preflight from any LLM agent: one call answers "can I talk to Power BI right now?" without juggling connect / list / model_info.

Inputs: no named parameters. Returns: result

pbi_trace_queryC

Execute a DAX query and return result rows with timing diagnostics.

Inputs: query (required), timeout_seconds. Returns: result

pbi_update_relationshipA

Update properties of an existing relationship (cardinality, direction, is_active, name).

Inputs: name, new_name, to_table, direction, +5 more. Returns: result

pbi_update_visual_bindingsA

Update an existing visual's field bindings without removing and recreating it. Modes:

  • projections: full replacement of all roles. dict keyed by role name (e.g. "Category", "Y", "Values"); each value is a list of field references ("Table.Column" for columns or bare measure names).

  • add_to_role / remove_from_role: incremental edits to specific roles. Same value shape. Roles that end up empty after a remove are dropped from projections.

projections is mutually exclusive with the incremental params. Roles are validated against VISUAL_FIELD_ROLES[visual_type]; field references are validated against the live model when manager is supplied. The prototypeQuery is rebuilt from the new reference set so Power BI Desktop renders the visual correctly. dry_run=True runs every check but skips the layout disk write.

Inputs: page (required), visual_id (required), extract_folder (required), dry_run, +4 more. Returns: result

pbi_validate_daxA

Parse-check a DAX expression by running a zero/one-row probe and catching errors.

kind='scalar' wraps the expression with EVALUATE ROW("v", ). kind='table' wraps the expression with EVALUATE TOPN(0, ).

semantic=True additionally checks every Table[Column] / [Measure] reference against the live model index and heuristically flags a percent-shaped format_string on a scalar-money expression — the response then carries a semantic block (see pbi_validate_dax_semantic_tool).

Inputs: expression (required), kind, semantic, format_string, +1 more. Returns: result

pbi_validate_filter_expressionB

Validate a DAX boolean filter expression before visual probes.

Inputs: filter_expression (required). Returns: result

pbi_validate_modelB

Audit the model for common issues: empty expressions, missing format strings, orphan tables, duplicate measure names.

Inputs: include_warnings. Returns: result

pbi_validate_pbix_persistenceC

Validate that a patched PBIX still contains a readable, persistent report layout.

Inputs: pbix_path (required), extract_folder, require_security_bindings_removed. Returns: result

pbi_validate_pbix_reopenB

Open a PBIX in Power BI Desktop and scan for visible repair-error signals.

use_windows_ocr defaults to False because the underlying screenshot captures the entire primary desktop, not just the Power BI window. The OCR helper now returns only the matched signal labels and a length — never raw recognized text — so screen contents from other applications cannot leak through the response.

Inputs: pbix_path (required), close_after, screenshot_path, timeout_seconds, +2 more. Returns: result

pbi_validate_power_query_stepsA

Verify that a Power Query (M) expression contains expected step patterns.

Each entry in expected_steps is treated as a substring (or regex if it starts with re:) that must appear at least once in the M expression. Useful for grading exercises: e.g. checking that a postal-code column has been left-padded to 5 chars, or that rows with null customer ids are filtered out.

Inputs: table (required), expected_steps (required), case_sensitive, partition_name. Returns: result

pbi_validate_relationship_planB

Validate relationship cardinality, direction, duplicates, and ambiguity before creation.

Inputs: to_table (required), to_column (required), from_table (required), from_column (required), +3 more. Returns: result

pbi_validate_report_fieldsD

Inputs: extract_folder (required), page, include_hidden. Returns: result

pbi_validate_star_schemaA

Confirm a model follows star schema topology.

Heuristic: a fact table participates as the many-side in ≥1 relationship, a dimension table participates as the one-side. Tables that are both are flagged as bridge tables. Direct dimension-to-dimension relationships are flagged as snowflake violations. Fact tables wired to other facts are flagged as fact-to-fact (constellation).

Optional fact_table_hints lets callers force-tag tables as facts (matched case-insensitively); useful when a fact table has no incoming relationships yet because the model is being built incrementally.

Inputs: include_hidden, fact_table_hints. Returns: result

pbi_validate_themeA

Dry-run validation of a user-supplied theme JSON file.

Returns the parsed payload size, any schema issues (errors + warnings), and the list of allowed top-level keys for reference. Performs no disk write and does not require an extract folder.

Inputs: theme_json_path (required). Returns: result

pbi_validate_visual_bindingsC

Alias-level visual binding validation with clearer tool naming.

Inputs: extract_folder (required), page, include_hidden. Returns: result

pbi_write_tmdl_fileC

Create or overwrite one TMDL file inside a Power BI Project definition folder.

Inputs: content (required), project_path (required), relative_file (required), create. Returns: result

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    C2.4/5.0

    Scored across 141 tools

    Disambiguation3/5

    With 141 tools, there is significant overlap in functionality, especially around measure creation (multiple specialized tools) and Excel imports. While descriptions help distinguish them, an agent could easily pick the wrong tool.

    Naming Consistency4/5

    Most tools follow a consistent 'pbi_verb_noun' snake_case pattern, but there are minor inconsistencies like mixing 'create' and 'add' for similar operations (e.g., pbi_create_measure vs pbi_add_visual) and plural forms (pbi_create_measures).

    Tool Count1/5

    141 tools is far too many for any server. This creates a bloated surface where many tools are either redundant or overly specialized, making it difficult for an agent to navigate.

    Completeness4/5

    The tool set covers a comprehensive range of Power BI operations, from model management to report layout, DAX, and data source handling. Minor gaps exist (e.g., no direct tool for editing visual types beyond conversion), but the coverage is extensive.

    Maintenance

    ActivitySlowing
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers