Skip to main content
Glama

powerbi-desktop-mcp

An MCP server that lets an AI assistant analyze Power BI Desktop files and safely edit copies of Power BI files, one approved change at a time.

Not affiliated with or endorsed by Microsoft. "Power BI", "Microsoft 365 Copilot" and "Copilot Studio" are trademarks of Microsoft Corporation.

What you get (v4.1.0: 65 tools, 12 skills)

  • Analyze an open file (30 read-only tools): live data model, DAX, relationships, row-level security, VertiPaq size, Power Query M with step dependencies, refresh health, report layout and filters, and deep checks for SQL Server, SharePoint and OneDrive sources. audit_dashboard runs 15 checks and returns one ranked list.

  • Work on saved .pbip projects without Desktop (25 tools): read and validate TMDL and PBIR, best-practice scan, DAX lint, dependency graph, unused fields, query diagnosis, generated documentation, project diff, and scaffolding of new projects.

  • Author and edit safely: plan, diff, approve, apply with backup, validation gate and automatic rollback, for measures, columns, relationships, RLS, M sources, renames (rewriting DAX and visuals), pages, visuals, alt text and themes. Edit one measure live in an open file with dry-run tokens and undo. Approved edits of copies of .pbix, .pbit, .pbir, .pbids, .rdl and .tmdl. See docs/EDITING.md.

  • Learns from use (7 tools): remembers fixes for errors it has seen, reports where the tools fail, and syncs and searches your public Power BI Knowledge Base. Local only; proposes improvements for a human to review, never edits itself. See docs/SELF-IMPROVEMENT.md.

  • 12 agent skills for analysis, troubleshooting, performance, DAX, model design, project authoring, documentation, editing rules and file formats (skills/).

  • Works with Claude, Gemini CLI and GitHub Copilot, plus a Microsoft 365 Copilot build in copilot/. See docs/CLIENTS.md.

Related MCP server: Microsoft Fabric MCP Server

Requirements

  • Live analysis: Windows 10/11 (64-bit) with Power BI Desktop (installer or Microsoft Store version). It reads Desktop's local Analysis Services engine, so Desktop must be open with the file loaded.

  • File review and editing (review_file, propose_change, finish_change, get_report_layout): standard-library Python, any OS.

  • Python 3.10 or newer, or let the bundled launcher download a private copy (see docs/INSTALL.md).

Quick start

Pick one. Details, config files and troubleshooting are in docs/INSTALL.md.

A. pip (any MCP client)

pip install powerbi-desktop-mcp          # or: pip install git+https://github.com/robertocruzesparza-dev/powerbi-desktop-mcp
powerbi-desktop-mcp --doctor             # checks this PC can run it

Then register the command powerbi-desktop-mcp as a stdio MCP server in your client (Claude Desktop, Claude Code, VS Code, ...).

B. Claude plugin (nothing to install by hand on Windows) Clone this repo and add it as a local plugin. The first start sets up a private Python environment automatically (about 70 seconds, once).

C. Gemini CLI / GitHub Copilot See docs/CLIENTS.md: a Gemini extension, VS Code and Copilot CLI configs, and scripts/install_skills.py.

D. Microsoft 365 Copilot Needs a public HTTPS tunnel and an Entra app registration. See copilot/README.md.

Try it: open a .pbix in Power BI Desktop and ask "Run a full audit of my open Power BI file."

Editing is off until you turn it on

Set PBI_MCP_EDIT_ROOT to a folder that holds copies of your files. With it unset, no edit tool can write anything. Originals outside that folder are refused, even if the assistant is tricked into asking.

Privacy and safety

The server runs on your machine and reads your Power BI models. What you ask the assistant, and what the tools return, goes to the AI provider your MCP client uses. Dashboards often contain student, employee or customer data: prefer aggregates, and read PRIVACY.md and SECURITY.md before connecting a cloud assistant to real data. The project itself collects no telemetry.

Documentation

docs/INSTALL.md

Install, client config, environment variables, troubleshooting

docs/TOOLS.md

Every tool, and what each can and cannot prove

docs/SELF-IMPROVEMENT.md

The learning loop: what is stored, knowledge-base sync, improvement report

docs/CLIENTS.md

Claude, Gemini CLI and GitHub Copilot setup and verification status

docs/RESEARCH.md

How Power BI Desktop works, capability matrix, known limits

docs/EDITING.md

The change protocol, supported formats, safety rules

copilot/README.md

Microsoft 365 Copilot build

CONTRIBUTING.md

Development setup and rules

CHANGELOG.md

Release notes

License

MIT. Third-party components: THIRD-PARTY-NOTICES.md.

Available Tools

33 tools
analyze_folding_riskA

Static estimate of query folding per query: which steps normally push down to SQL, the first step that stops folding, row filters that run locally AFTER folding ended (the source sent every row first), Table.Buffer, and merges across different sources (joined locally). SharePoint/file sources never fold. It is an estimate from M text, not proof: confirm with 'View Native Query', and use read_mashup_traces for the engine's own folding-failure reports.

Inputs: instance (required), include_steps. Returns: object

analyze_model_sizeA

Get VertiPaq storage engine byte totals per table (exact) and the largest individual storage objects/columns (best-effort labeling) -- the same underlying data DAX Studio's and Tabular Editor's VertiPaq Analyzer use to show what's bloating a model's memory footprint. Use this for efficiency review: a surprisingly large table or column here is a concrete lead (unused high-cardinality column, a text column that should be split or removed, importing more history than needed, etc).

Inputs: instance (required), top_n. Returns: object

analyze_relationshipsA

List relationships with modeling-analysis flags attached: which are many-to-many, which cross-filter bidirectionally, which are inactive, and which visible tables have no relationships at all (isolated from the rest of the model). Use this over list_relationships when the goal is spotting model design issues rather than just reading the raw list.

Inputs: instance (required). Returns: object

analyze_sharepoint_patternsB

SharePoint loading review: which lists/files each query pulls (by ID), lists fetched by several queries, SharePoint.Files enumerating the whole site then filtering by name, queries that never reduce columns or rows, system columns deleted by name, errors swallowed by try ... otherwise null/false (refresh succeeds with missing data), the shared navigation table's blast radius, and hard-coded list IDs.

Inputs: instance (required), include_row_counts. Returns: object

analyze_sql_queriesB

Review every hand-written SQL statement embedded in the M code ([Query=...], Value.NativeQuery): writes/DROP against real tables in a read query, TOP N caps that silently drop rows, SELECT *, hard-coded years / year-named tables, CAST to varchar without length (truncates at 30), NOLOCK, UNION vs UNION ALL, ORDER BY, temp-table batches, cross- database reads, and SQL built by concatenation. Also lists navigation- style SQL queries (which can fold).

Inputs: instance (required). Returns: object

audit_dashboardA

START HERE for a full review. Runs every check (refresh health, DAX validity, relationships, row-level security, connections, embedded SQL, query folding, SharePoint patterns, dependencies, report filters/hidden pages, unused fields, model size, relationship data integrity, and Power BI's own runtime logs) and returns ONE ranked list of findings, each tagged with the tool to call for full detail. A failing check is reported in checks_failed and never hides the rest; not_checked lists what this audit cannot see. pbix_path defaults to the open file's path.

Inputs: instance (required), pbix_path, include_traces, include_data_quality. Returns: object

check_refresh_dependenciesC

The query dependency graph: what feeds what, chain depth, which loaded tables fail if each source (SQL server / SharePoint site) is unreachable, high fan-out staging queries, orphan queries nothing uses, parameters and their consumers, circular references.

Inputs: instance (required). Returns: object

check_relationship_integrityB

For every relationship, count key values with no match on the other side. On many-to-one lookups these are rows that show under (Blank) and vanish from cross-filtered measures -- totals stop matching with no error. One-to-one/many-to-many are flagged only when unmatched from both sides (wrong columns or a data-type/format mismatch).

Inputs: instance (required), max_relationships. Returns: object

classify_connectionsB

Every data source the model reads, classified (on-prem SQL Server, SharePoint site, personal OneDrive, web, local/UNC file, ...) with the problems that matter for SQL / SharePoint / OneDrive dashboards: gateway needed for service refresh, localhost or OneDrive-synced local paths (only work on this PC), http, personal-OneDrive dependence, dev/test names, the same target spelled several ways (separate credentials), and parameters resolved to their values.

Inputs: instance (required). Returns: object

compare_dashboardsA

Diff two dashboards. Each of left/right is an open file's name (from list_open_files) or a .pbix path. With both open: tables, columns, measures (DAX), relationships, RLS roles and Power Query (step-level). Any two .pbix paths: report pages, visuals and filters. Useful for backup vs current, or before/after a change.

Inputs: left (required), right (required). Returns: object

diagnose_power_queryA

Fast health check across every table's Power Query load: which tables errored on last refresh (with the actual error message), and whether a table was refreshed before or after its query was last edited (a stale refresh). This is the live, authoritative signal for "is something broken" -- check this first, then use list_power_query to read the M code (with its step breakdown) for whichever table shows a problem.

Inputs: instance (required). Returns: object

find_pbix_filesA

Scan folders recursively for .pbix / .pbit files on disk (no Power BI Desktop needed). Defaults: Desktop, Documents, Downloads and OneDrive Desktop/Documents (add more folders with POWERBI_MCP_SCAN_DIRS). Pass search_dirs to scan specific folders instead. Returns {files (newest first), count, total_found, truncated, searched, missing_dirs}; if truncated is true there are more files than max_results -- raise it or narrow search_dirs.

Inputs: max_results, search_dirs. Returns: object

find_query_diagnosticsA

Find and read output from Power BI Desktop's built-in Query Diagnostics feature, if you've recorded a session. This plugin can't start/stop Query Diagnostics itself -- it's a manual step in the Power Query Editor's Tools tab (Start Diagnostics -> do the refresh you want traced -> Stop Diagnostics) with no local API to trigger it -- but once you've recorded one, this finds the resulting "Diagnostics" queries by name and, for any that were loaded into the model, returns the actual traced rows (step-by-step timing, data source queries, row counts, whether query folding happened, and so on -- the real intermediate evaluation data list_power_query's step breakdown can't provide). If the diagnostics queries exist but weren't loaded into the model, this tells you that and how to fix it rather than returning nothing.

Inputs: instance (required), max_rows. Returns: object

find_unused_fieldsA

Find visible columns and measures that no visual on any report page actually uses (cross-references the live model against the offline report layout). Get pbix_path from list_open_files()'s file_path, or find_pbix_files(). A lead worth investigating for model cleanup, not a guaranteed-safe-to-delete list -- see the tool's note field for what it can't see (DAX-only references, tooltips, drillthrough, Q&A).

Inputs: instance (required), pbix_path (required). Returns: object

finish_changeA
Destructive

STEP 2 (apply) or UNDO (rollback) for a change from propose_change. Call ONLY after the user has explicitly approved THAT change and read you the 6-digit approval code shown in the server's console window; never guess a code or infer approval. With an empty approval_code it issues a fresh code and applies nothing. apply backs up the whole file, applies only the approved edit, re-verifies, and restores the original if verification fails. rollback restores the backup (needs its own code). Works only on COPIES inside the folder named by PBI_MCP_EDIT_ROOT (off when unset).

Inputs: action (required), change_id (required), approval_code. Returns: object

get_model_summaryA

Get an overview of an open Power BI file's data model: table count, measure count, relationship count, plus the full table/measure/ relationship lists. instance is the file's display name (or port) returned by list_open_files.

Inputs: instance (required). Returns: object

get_report_layoutA

Parse a .pbix file's report layout offline (no need for Power BI Desktop to be running): pages, and per page the visuals with their type, title, and the fields each visual uses. Does not include the data model itself -- use the live-model tools for tables/measures/DAX.

Inputs: pbix_path (required). Returns: object

get_table_row_countC

Get the number of rows currently loaded in a table.

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

list_all_dax_expressionsA

Get every piece of DAX in the model in one place: measures, calculated columns, and calculated tables, each with its full expression. Useful for an overall DAX audit/review rather than pulling measures and columns separately.

Inputs: instance (required). Returns: object

list_columnsA

List columns across the model, or for one table if table is given. Includes data type, hidden flag, and calculated-column DAX expression where applicable.

Inputs: instance (required), table. Returns: object

list_data_sourcesA

Inventory every data source the model actually connects to (SQL servers, SharePoint sites, web APIs, files, etc.), by scanning every table and shared query's M code for known connector function calls. Returns each distinct connection plus which queries use it. Useful for "what does this dashboard actually talk to" -- spotting a dev/test server left in a query bound for production, an unexpected data source, or confirming everything points where it should.

Inputs: instance (required). Returns: object

list_hierarchiesB

List user-defined hierarchies (e.g. Year > Quarter > Month, or Region > State > City) and their ordered levels, for a complete picture of the model's structure beyond flat tables/columns.

Inputs: instance (required). Returns: object

list_measuresB

List DAX measures across the model, or for one table if table is given. Includes each measure's DAX expression and format string.

Inputs: instance (required), table. Returns: object

list_open_filesA

List every Power BI Desktop file currently open on this machine, with the identifier ('instance') to pass to the other model tools. Each entry has model_loaded: false means Power BI Desktop is running but no report is loaded in it (closed, still opening, or a dialog is open) and every model tool will fail until the file is loaded -- ask the user to open it. Returns an empty list if Power BI Desktop isn't running.

Inputs: no named parameters. Returns: result

list_power_queryA

Get the Power Query (M) code behind the model: each loaded table's query steps (the let/in M code, including its transformation step names), plus shared queries/functions/staging steps that feed other queries but don't load into a visible table themselves (Enable Load = Off). Pass table to filter table_queries to one table; shared queries aren't table-scoped so they're always returned in full.

With include_steps (default True), each query also comes broken into its named steps in source order, each with the sub-step names it references -- e.g. step "Changed Type" references step "Source". This is a structural breakdown of the M source text, not a live evaluation: there's no local API into the mashup engine that actually executes these steps, only into the already-loaded tabular model, so this can't show you the data at each step the way Power Query Editor's step preview does -- it can only show you the steps and how they chain together, which is usually enough to spot which step to look at. Each table_queries entry also includes the partition's live refresh state and error_message; use diagnose_power_query for just that, across all tables, without the M text.

Inputs: instance (required), table, include_steps. Returns: object

list_relationshipsB

List relationships between tables: from/to table and column, cardinality, active flag, cross-filter direction.

Inputs: instance (required). Returns: object

list_security_rolesA

List row-level security (RLS) roles and each one's per-table filter expression. Flags, per role, which tables it can access with no row filter at all (unfiltered_tables -- sees every row of that table). RLS is invisible unless specifically checked, so this is exactly the kind of thing a "who can see what" or security-focused review needs.

Inputs: instance (required). Returns: object

list_tablesC

List every table in the data model, with visibility/description metadata where available.

Inputs: instance (required). Returns: object

profile_tableA

Data-quality profile of one loaded table: per column blanks, distinct count, min/max, longest text; flags entirely-blank / constant columns, text at a truncation limit (30/255...), placeholder dates (1900/9999). key_columns checks duplicates and blank keys; expected_row_count (e.g. COUNT(*) you ran on the source) flags a mismatch.

Inputs: table (required), instance (required), columns, key_columns, +2 more. Returns: object

propose_changeA
Destructive

STEP 1 of any edit. Proposes replacing exact old_text with new_text in a file (.pbix .pbit .pbip .pbir .pbids .rdl .tmdl or PBIR .json). Changes NOTHING: validates the result and returns a diff, the affected objects and the risk. For .pbix/.pbit pass member. old_text must match exactly expected_count places. Show the user the diff verbatim and wait for explicit approval. Editing works only on COPIES inside the folder named by the PBI_MCP_EDIT_ROOT environment variable (off when unset); originals elsewhere are refused.

Inputs: path (required), reason (required), new_text (required), old_text (required), +2 more. Returns: object

read_mashup_tracesA

Read Power BI Desktop's own mashup-engine trace logs (%USERPROFILE%\Microsoft\Power BI Desktop Store App\Traces): the ONLY source of connector-level runtime evidence. Per SharePoint list: request counts, failures with HTTP status (e.g. 500/400), durations, the navigation table re-fetched per evaluation; per SQL command: the text sent, seconds, rows read; the engine's own folding-failure reports; container crashes. Pass instance to match SharePoint list IDs to this model's queries (the logs cover every file opened in the window). Request bodies aren't logged, so paging can't be proven.

Inputs: instance, since_minutes. Returns: object

review_fileA
Read-only

Read one Power BI file's TEXT without changing anything: .pbip .pbir .pbids .rdl .tmdl, a PBIR .json inside a *.Report folder, or a part of a .pbix/.pbit. For a .pbix/.pbit call it WITHOUT member to list the parts, then with member (for example Report/Layout) to read one; the compressed model (DataModel) and Power Query container (DataMashup) are never read. Passwords and tokens appear as [REDACTED].

Inputs: path (required), member, offset, max_chars. Returns: object

run_dax_queryA

Run an arbitrary DAX query (an EVALUATE statement, optionally with DEFINE MEASURE/VAR blocks above it) against an open Power BI file. Use this for anything the higher-level tools (list_tables, list_measures, etc.) don't cover -- ad hoc aggregations, filtering, top-N, DISTINCT lists, testing a candidate measure definition, and so on.

Returns {"rows": [...], "row_count": n, "truncated": bool}. Results are capped at max_rows; when truncated is true there were MORE rows than were returned, so never draw a "this is everything" conclusion from a truncated result -- aggregate in DAX (or raise max_rows) instead.

Inputs: instance (required), dax_query (required), max_rows. Returns: object

Tool Schema Changelog

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

Loading tool schema changelog…

    TDQS

    A3.5/5.0

    Scored across 33 tools

    Disambiguation4/5

    Tools are largely distinct and deliberately layered: `audit_dashboard` is a documented orchestrator that points to detail tools, and `analyze_relationships` explicitly says to use it over `list_relationships` for design review. A few boundaries remain fuzzy — `list_data_sources` vs `classify_connections`, `get_model_summary` vs the individual `list_*` calls, and the cluster of Power Query tools (`diagnose_power_query`, `analyze_folding_risk`, `find_query_diagnostics`, `read_mashup_traces`, `list_power_query`) — but the descriptions do most of the disambiguating work.

    Naming Consistency5/5

    Every name is snake_case, verb-first, and noun-scoped (list_tables, analyze_model_size, check_relationship_integrity, propose_change). Verb choice varies (list/get/find/analyze/check/audit/read/profile/compare/run) but semantically rather than arbitrarily, so the pattern stays predictable. The propose_change/finish_change pair reads as an intentional two-step workflow rather than a naming break.

    Tool Count3/5

    33 tools is heavy for an agent to navigate, even though each one maps to a real, non-duplicated audit or inspection task. The audit_dashboard aggregator and clear START HERE guidance offset some of the load, but the surface is well past the comfortable 3-15 range for a single server.

    Completeness4/5

    Coverage is unusually broad: model metadata, DAX, Power Query/M, folding, RLS, data sources, runtime traces, offline report layout, file scanning, diffing, profiling, and a guarded propose/apply/rollback edit flow. Gaps are mostly acknowledged hard limits — the compressed DataModel/DataMashup in .pbix is never read or edited (so in-place .pbix measure edits are impossible), and the server cannot trigger refreshes or start/stop Query Diagnostics.

    Maintenance

    ActivityMaintained
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      C
      maintenance
      Enables AI assistants to interact with Power BI datasets through natural language, allowing users to query data, generate DAX, and get insights without leaving their AI assistant.
      126
      MIT
    • A
      license
      B
      quality
      D
      maintenance
      Enables AI assistants to interact with Microsoft Fabric and Power BI services through the Model Context Protocol. Users can manage workspaces, execute DAX queries, refresh datasets, and create Fabric notebooks using natural language.
      6
      9 npm
      2
      MIT