powerbi-desktop-mcp
This MCP server lets an AI assistant analyze open Power BI Desktop models, review saved Power BI files, run DAX queries, audit dashboard health, and safely edit approved copies.
List open Power BI files, model summary, tables, columns, measures, relationships, hierarchies, and row counts.
Inspect DAX, Power Query M, step dependencies, refresh health, query diagnostics, data sources/connections, embedded SQL, query folding, SharePoint patterns, refresh dependencies, and mashup traces.
Audit model design, RLS security, unused fields, VertiPaq size, relationship integrity, table data quality, and full dashboards.
Compare dashboards and run arbitrary DAX queries with capped/truncated results.
Find and review saved .pbix/.pbit/.pbip/.pbir/.pbids/.rdl/.tmdl files and report layouts without Desktop.
Propose exact text edits with diffs and risk, then apply or roll back with approval codes, backups, validation, and rollback safety.
Editing only works on copies inside PBI_MCP_EDIT_ROOT; originals are refused and editing is off when unset.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@powerbi-desktop-mcp Run a full audit of my open Power BI file."
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
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_dashboardruns 15 checks and returns one ranked list.Work on saved
.pbipprojects 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,.rdland.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 itThen 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
Install, client config, environment variables, troubleshooting | |
Every tool, and what each can and cannot prove | |
The learning loop: what is stored, knowledge-base sync, improvement report | |
Claude, Gemini CLI and GitHub Copilot setup and verification status | |
How Power BI Desktop works, capability matrix, known limits | |
The change protocol, supported formats, safety rules | |
Microsoft 365 Copilot build | |
Development setup and rules | |
Release notes |
License
MIT. Third-party components: THIRD-PARTY-NOTICES.md.
Available Tools
33 toolsanalyze_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_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_changeADestructive
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_changeADestructive
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_fileARead-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
Scored across 33 tools
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.
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.
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.
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
Related MCP Connectors
Forge designed Power BI projects (.pbip) from your agent, bound to your own semantic model.
Connect AI assistants to your approved computer for files, commands, browser and desktop tasks.
- DNS-DATA RemoteDeprecatedcom.dns-data
Connect AI assistants to authorized Windows and macOS devices for files, commands, and management.
Read-only AI coding tools for change verification, release readiness, capacity, and guidance.
Related MCP Servers
- AlicenseNot gradedqualityCmaintenanceEnables 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.126MIT
- AlicenseBqualityDmaintenanceEnables 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.69 npm2MIT
- FlicenseNot gradedqualityDmaintenanceEnables controlling Power BI with AI through natural language commands for creating DAX measures, explaining metrics, suggesting KPIs, and organizing models.-
- FlicenseNot gradedqualityDmaintenanceEnables AI assistants to discover and interact with local Power BI Desktop instances, including inspecting table/column schema and executing DAX queries.2-