pbi-webview2
This MCP server drives a live Power BI Desktop report canvas via Chrome DevTools Protocol (CDP), enabling AI agents to automate testing, data reading, and interaction without screenshots or OCR.
Launch & Connect
Launch Power BI Desktop with a CDP debug port on a
.pbipfile, or attach to an already running instanceCheck connection status (build info, title bar, active page, zoom, canvas readiness) and run quick health probes
Navigate Pages
List all report pages and switch between them by exact name, with optional wait-until-ready
Clear selections without blind-clicking the canvas
Read Data
Extract structured data from card visuals and matrix/table grids (including scrolling virtualized grids)
List all visible visuals with title, type, position, size, and error status
Read DAX query editor and TMDL view content from Monaco
Run DAX queries and capture structured results
Interact with Visuals
Click elements by text, aria-label, or selector; set slicer values; fire bookmarks; expand/collapse matrix hierarchies
Type into inputs, search slicer boxes, hover for tooltips, and open/invoke context menus
Test & Analyze
Test cross-filter interactions, scan for broken visuals and console errors
Run Performance Analyzer for per-visual render times and DAX capture
Sweep all pages to record load times, errors, and card fingerprints
Capture and compare value baselines for regression testing
Capture Output
Take screenshots of the full page or clipped to a specific visual
Capture accessibility tree snapshots and deep-inspect DOM or V8 heap usage
Escape Hatches
Evaluate arbitrary JavaScript in the report canvas (
pbi_eval)Run trusted Playwright code with full
page.mouse/page.keyboardaccess (token-guarded)
State & Application Management
Automate Desktop dialogs (read and click buttons, with a safety guard on Save)
Save, close, or reload the report — each guarded by explicit confirmation flags to avoid persisting test state
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., "@pbi-webview2 Open my monthly report and show me the summary table"
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.
pbi-webview2 — MCP server for the live Power BI Desktop report canvas
pbi-webview2 is a Model Context Protocol server
that drives Power BI Desktop's WebView2 report canvas over CDP (Chrome DevTools
Protocol). It turns an AI agent (Claude Code, Claude Desktop, or any MCP client) into
a hands-on tester of your running report: switch pages, click slicers and buttons,
fire bookmarks by name, read cards and matrices as structured data, judge whether a
cross-filter fired, scan for broken visuals, run the Performance Analyzer, capture and
compare value baselines, and screenshot individual visuals.
Why
Power BI Desktop renders its report canvas inside an embedded WebView2 (Chromium). When
Desktop is launched with the WebView2 remote-debugging port enabled, that canvas is a
real DOM you can attach to over CDP. pbi-webview2 exploits this to interact with the
live report — no reload, no re-render, no screenshot-OCR to guess at numbers. Every
tool reads values straight from the DOM, and clicks are trusted OS-level input that
Desktop actually reacts to (synthetic DOM events are ignored by the canvas). Selectors
and behavioural traps were verified against Power BI Desktop 2.155 (July 2026).
Each tool is one full, verified recipe (tag the target element in-page, then act on it
by a data-pw selector) rather than a raw primitive — so the agent gets a clean result
object ({connected:true, ...}) instead of having to rediscover the DOM every time.
Related MCP server: TradingView MCP Jackson
Requirements
Windows — Power BI Desktop is Windows-only, and so is this server (
os: win32).Power BI Desktop installed (as
PBIDesktop.exeor the Microsoft Store version).Node.js ≥ 20.
Optional: Microsoft's
@microsoft/powerbi-desktop-bridge-cli(only if you prefer the bridge CLI over the built-in directPBIDesktop.exelaunch — seepbi_launchbelow).
Install / registration
The server runs straight from npm via npx; nothing to clone.
Claude Code
claude mcp add pbi-webview2 -- npx -y pbi-webview2Claude Desktop (claude_desktop_config.json)
{
"mcpServers": {
"pbi-webview2": {
"command": "npx",
"args": ["-y", "pbi-webview2"]
}
}
}Generic MCP client / from source
Any client that speaks stdio MCP can launch it. To run from a local checkout instead of npm:
{
"mcpServers": {
"pbi-webview2": {
"command": "node",
"args": ["C:/path/to/pbi-webview2/server.js"],
"env": { "PBI_CDP_ENDPOINT": "http://127.0.0.1:9222" }
}
}
}The server connects lazily — it boots fine with Desktop closed, and every tool
returns a structured {connected:false, error, hint} (never a thrown error) until
Desktop is reachable. So you can register it once and launch Desktop later.
Quickstart workflow
The canonical loop is launch → wait → status → drive:
pbi_launch {pbip:"C:/path/to/Report.pbip"}— launches Desktop with the CDP debug port and waits until the port answers. The port only exists when Desktop is started with the right environment variable, and it cannot be attached later — so this must be how Desktop comes up.pbi_wait_for {text:"<a page name>"}— the CDP port answering does not mean the canvas has finished rendering; a heavy report re-queries every visual for seconds-to-minutes. Poll for a known page/visual label to appear first.pbi_status— confirm build, title bar, active page, page count, zoom, andcanvasReady.Drive it —
pbi_pages,pbi_goto_page,pbi_set_slicer,pbi_read_cards,pbi_read_matrix,pbi_cross_filter_test,pbi_visuals,pbi_screenshot, …
If Desktop is already running on the port, pbi_launch reports the existing instance
instead of starting a second one (a second instance competes for Analysis Services
memory and makes both render as if frozen).
Environment variables
Var | Default | Purpose |
|
| CDP endpoint to attach to. Always |
| — | Explicit path to Microsoft's |
| — | Explicit path to |
|
| Where screenshots + baselines are written (never the repo/CWD). |
|
| Per- |
|
| Steady interval between poll probes inside the multi-step tools. Polls ramp adaptively from ~250ms toward this value, so warm/ready pages satisfy fast. |
|
| Post-navigation settle in |
How pbi_launch resolves a launcher
pbi_launch tries three strategies in order and reports which one it used
(launcher: "bridge-env" | "bridge-path" | "direct"):
PBI_DESKTOP_BRIDGE— the explicit bridge CLI path from the env var.powerbi-desktoponPATH— resolved viawhere.exe(the npm-installed bridge CLI shim).Direct
PBIDesktop.exespawn — needs no Microsoft CLI at all. Locates the exe fromPBI_DESKTOP_EXE, then%ProgramFiles%\Microsoft Power BI Desktop\bin\PBIDesktop.exe, then the Store alias%LOCALAPPDATA%\Microsoft\WindowsApps\PBIDesktop.exe.
All three inject WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS=--remote-debugging-port=<port>
into Desktop's own process environment (the only way the port gets enabled). If nothing
resolves, pbi_launch returns {launched:false, error, hint} telling you to
npm i -g @microsoft/powerbi-desktop-bridge-cli or set PBI_DESKTOP_EXE.
Tools
55 tools. Each returns a leading status key: the connected: family reports CDP
reachability of the report canvas ({connected:false, ...} when Desktop is
unreachable); the ok: family covers escape hatches, the separate DAX/TMDL/dialog
CDP targets, and the guarded save/close/reload tools ({ok:false, reason} — or a
{ok:true, <action>:false, reason} refusal — when a surface isn't available or a guard
flag wasn't passed). Tools returning found:false / extracted:false also carry a
structured code REASON (not-found / not-ready / wrong-view / canvas-busy
/ not-extractable) so a loop can branch on the failure without string-matching.
Tool | Status | Key params | What it does |
|
|
| Launch Desktop WITH the CDP port (bridge-env → bridge-path → direct |
|
|
| Connect + report build, title bar, active page, page count, zoom, canvasReady, dirty. |
|
| — | All page tabs |
|
|
| Exact-match page nav; verifies |
|
| — | Clear selection via the neighbour-page-and-back trick (never blind-clicks the canvas). |
|
| — | Batched scorecard (toggles, cards, badges, selectedCount, slicerItemsVisible, …). |
|
| — | Parsed cards |
|
|
| Generic tag-then-act click with overlay-intercept + on-path SVG coordinate fallback. |
|
|
| Click a button/list slicer; returns before/after state. |
|
|
| Fire any bookmark via the View > Bookmarks pane (trusted click); restores chrome. |
|
|
| Full grid as |
|
|
| Expand/collapse a hierarchy row; returns rowsBefore/After. |
|
|
| Click a data point; verdict = highlights rose OR card fingerprint changed; optional restore. |
|
|
| Trusted hover, read tooltip text. |
|
| — | Broken-visual scan + non-benign console errors (+ benignFaviconCount). |
|
|
| Per-visual render ms; optional visual-DAX capture via clipboard (clobbers clipboard); restores pane + ribbon. |
|
|
| HEAVY — iterates pages (each re-queries all its visuals), records loadMs + error scan + card fingerprint; restores page. |
|
|
| Value-baseline capture/compare/list to |
|
|
| Poll body innerText until text appears/disappears. |
|
|
| Escape hatch — |
|
|
| TRUSTED escape hatch — runs |
|
|
| Accessibility-tree (ARIA) snapshot for structure discovery when a selector drifts. An invalid |
|
|
| Trusted-keyboard type into an editable element; aborts if focus doesn't land on the input (never types into the canvas). |
|
|
| Type into a slicer search box; return filtered items; optional pick clicks the match. Same focus-safety abort as |
|
|
| Right-click a data point/visual, read menu items; optional click invokes one (else Escape-closes). |
|
|
| Screenshot to the output dir. With |
|
| — | List visible visuals as |
|
| — | Read the DAX query view editor text (Monaco; reaches the |
|
| — | Read the TMDL view editor text (Monaco; reaches the |
|
|
| Write DAX into the query view + run (F5, Run-button fallback) + read the results grid. OVERWRITES the editor content — but the prior text is captured first; |
|
|
| Read/click a Desktop dialog ( |
|
|
| Raw-CDP deep inspection: compact a11y tree, DOMSnapshot size probe, or V8 heap usage. Read-only. |
|
|
| Forces |
|
|
| GUARDED save. Without |
|
|
| GUARDED process kill — always needs |
|
|
| GUARDED visual repaint — needs |
|
|
| CHEAP aggregate: |
|
|
| List measures/tables/columns/relationships as structured metadata via |
|
|
| Run up to 20 DAX queries sequentially, collect all result sets. Auto-restores the editor buffer (default |
|
| — | Format the DAX query editor via Monaco |
|
|
| Capture/restore the Monaco editor text so |
|
|
| Wait until the canvas render is STABLE ( |
|
|
| Read a |
|
|
| Best-effort: extract a visual's data via right-click data point → "Show as a table". SCOPE: needs a DOM data point — many chart/canvas visuals expose none (returns |
|
|
| Read a slicer's |
|
| — | Best-effort read of the Filters pane as data; opens the pane if closed, reads, then restores it. Honest |
|
|
| Expand/collapse ALL matrix hierarchy levels by iteratively left-clicking the +/- expander buttons; returns |
|
|
| Sort a grid by a column header; returns |
|
|
| Multi-select list-slicer items (first plain, rest Ctrl+click); returns |
|
|
| Drill down/up via the visual-header control, or drill-through via a data-point menu. Honest |
|
|
| Trusted-Tab focus-order / a11y audit; records |
|
| — | ONE-call page judgment: visuals+cards+badges+slicers+broken-visuals+console-errors in a single round-trip. The agentic loop's observe step. |
|
|
| Capture a full page digest and structurally diff two states (cards changed/added/removed, visual/broken deltas, slicer/page changes). |
|
|
| Structured pass/fail assertions over a fresh page digest for loop control; each provided predicate becomes a |
|
|
| Screenshot with numbered overlay boxes over each visual + a legend ( |
Dirty state is not detectable over CDP. Verified against Desktop 2.155: a real report edit changes nothing reachable from any WebView target (no title-bar change, no
*, Save button always enabled, nowindow.powerbi.isDirty). Power BI Desktop's dirty flag lives in its native WPF host shell, which CDP cannot see. Sopbi_save/pbi_close/pbi_reloadnever detect unsaved work — they gate on your explicit intent flag (confirm/discardChanges/saveFirst). This is deliberate: a guard that silently under-reports "clean" would be more dangerous than one that always asks you to state intent.
Cost tiers
A loop should poll the CHEAP tools; call HEAVY tools only intentionally.
Tier | Latency (warm) | Tools |
CHEAP | sub-second |
|
MEDIUM | ~1-5s (click + poll) |
|
HEAVY | ~10-45s+ (deliberate) |
|
Choosing between similar tools
Several tools overlap in area but answer different questions — pick by intent:
State reads:
pbi_status(build/title/page/zoom) ·pbi_state_probe(toggles/cards/badges/selection — cheapest data probe) ·pbi_health(broken/console/heap quick-check) ·pbi_page_digest(everything in one call — supersetsstate_probe; use it as the agentic observe step,state_probewhen you only need the cheap scorecard).Slicers:
pbi_set_slicer(one button/item) ·pbi_multiselect_slicer(several list items, Ctrl-held) ·pbi_search_slicer(type in the search box, optional pick). Distinct mechanics — not interchangeable.Grids:
pbi_read_matrix(matrix/pivot, merges row headers) ·pbi_read_table(flattableEx, no row headers).pbi_show_as_tableis the best-effort extractor for non-grid visuals (and often returnsnot-extractable— see below).Waiting:
pbi_wait_for(a specific text appears/disappears) ·pbi_wait_stable(render fully settles — deterministic, use after a nav/edit).Escape hatches:
pbi_eval(page.evaluate, synthetic events — read-only DOM) ·pbi_run_code(trustedpage.mouse/page.keyboard— real input).Screenshots:
pbi_screenshot(full page or one visual viavisualTitle) ·pbi_annotate_screenshot(numbered overlay boxes + legend for multimodal judging).
Model & DAX introspection
pbi_model_info gives you a metadata browser — measures, tables, columns, and
relationships as structured rows — with no XMLA connection. It runs INFO.VIEW.* DAX
(INFO.VIEW.MEASURES/TABLES/COLUMNS/RELATIONSHIPS()) inside the DAX query view and shapes
the result grid, so you read the model over the same CDP channel as everything else.
Precondition: the DAX query view must be OPEN in Desktop (the Home ribbon's "DAX query view" tab). These tools reach the separate
daxQueryViewCDP target and will not open ribbon views for you — same precondition aspbi_read_dax_editor.Filter with
table(exact[Table]),nameLike(CONTAINSSTRINGon[Name]), andtop. The heavyExpression/FormatStringDefinition/DetailRowsDefinitioncolumns are dropped by default; passincludeExpression:trueto keep them.pbi_dax_batchruns up to 20 queries in one call;pbi_format_daxpretty-prints the editor via Monaco's Format Document action.Editor safety.
pbi_dax_query/pbi_model_info/pbi_dax_batchoverwrite the query editor, so they capture the prior text first and restore it (restore:trueondax_query, on by default fordax_batch, always formodel_info). You can also snapshot and re-instate the buffer manually withpbi_editor_buffer {action:"capture"|"restore"}.
Reading visual data
Grids expose their full data in the DOM; charts do not. This asymmetry is the single most important thing to know when reading values:
Matrices & tables (
pbi_read_matrix,pbi_read_table) return the complete grid — every cell is a real DOM node, and both tools scroll virtualized grids and merge the rows. This is the reliable path for any tabular data.Chart values are NOT in the DOM. Verified against Desktop 2.155: donut/column/line visuals render their marks without persistent DOM values — arcs aren't addressable SVG paths, and the
role="option"nodes carryaria-label="null"(only category labels are present as text). Scraping a chart's values from the DOM does not work.pbi_show_as_tableis a best-effort extra path: it right-clicks a data point → "Show as a table" (or theAlt+Shift+F11accessible show-data table), reads the overlay grid, and restores the canvas. It only works where the visual exposes a DOM data point — many chart/canvas/custom visuals expose none, so it honestly returns{extracted:false, code:"not-extractable"}. Preferread_matrix/read_tablefor tabular data; reach forshow_as_tableonly when the data lives behind a chart.
Agentic loop v2
The canonical safe act → observe → judge → verify loop. Every state-changing step is
behind an explicit guard flag — the loop only passes confirm / saveFirst /
discardChanges when the edit is legitimate, so a test-click loop can never persist garbage
or lose work. The v1.2.0 tools collapse the observe/judge/verify steps into single
round-trips:
settle —
pbi_wait_stableblocks until the render is actually done (LayoutCount / RecalcStyleCount flat + aria-busy clear), instead of guessing at a fixed timeout.observe —
pbi_page_digestreturns visuals + cards + badges + slicers + broken visuals + console errors in ONE call.judge —
pbi_assertruns structured predicates (cardEquals,visualCountAtLeast,noBrokenVisuals,activePageIs) over a fresh digest and returns pass/fail.verify change —
pbi_diff_statecaptures a digest before an action and structurally diffs it after (which cards changed, visual/broken deltas, slicer/page changes).
pbi_launch → pbi_wait_for
→ pbi_diff_state {action:"capture", name:"before"} # snapshot the page digest
→ (edit via your model/authoring MCP)
→ pbi_reload {saveFirst:true, stable:true} # repaint from the loaded model + wait for settle (guarded; no data refresh)
→ pbi_page_digest # ONE-call observe: broken visuals? console errors? cards?
→ pbi_assert {noBrokenVisuals:true, cardEquals:{...}} # judge: did it land?
→ pbi_diff_state {action:"compare", name:"before"} # verify: exactly what changed
→ judge → fix → repeat
→ (restore slicers/selection: pbi_deselect / CLEAR bookmark)
→ pbi_save {confirm:true} # opt-in save
→ pbi_close {discardChanges:true} # deliberate teardown at the very end (flag always required)Between iterations, observe with pbi_page_digest / pbi_health and pbi_status {light:true} (all CHEAP). Restore slicers/selection BEFORE pbi_save {confirm:true} so
you don't persist test-click state. pbi_save, pbi_close, and pbi_reload NEVER act
without their guard flag — without it they return a refusal object, not the action.
Robustness
Newer tools resolve elements through an ARIA-role resolver (role + accessible name)
rather than brittle class selectors, so they survive Desktop DOM churn better. When a tool
can't complete, it returns a structured REASON code instead of a bare error, so a loop
can branch on the failure kind without string-matching:
| Meaning |
| The target element (visual, column, slicer, matrix, drill control) wasn't present. |
| The surface exists but hasn't populated yet (e.g. an empty/collapsed Filters pane, a show-as-table grid that never appeared). |
| The wrong CDP view is focused for the operation. |
| A read landed mid-render; retry after |
| The data exists but isn't reachable over the DOM (e.g. a chart with no DOM data point for |
Safety & etiquette
Every interaction happens against the live, unsaved report in memory. Follow these:
Never save after test clicks. Every click mutates unsaved in-memory report state. Restore slicers / active page when done (a CLEAR bookmark or
pbi_deselecthelps; the sweep/baseline/bookmark tools restore the starting page/chrome for you).pbi_dialogrefuses to click a Save button unlessbuttonis passed exactly as"Save"(case-sensitive) — a guard against accidentally saving the report.Token guard is honest, not a sandbox.
pbi_evalandpbi_run_codereject code that referencespowerBIAccessToken, but that is a best-effort textual (regex) guard, not a security sandbox. Do not rely on it as a boundary.Restore state after test clicks so the next run starts clean.
Screenshots and baselines are written to
PBI_OUTPUT_DIR(a temp dir), never the repo.
Troubleshooting
{connected:false}from every tool — Desktop isn't reachable on the CDP port. Launch viapbi_launch(the port is enabled only at launch and cannot be attached later). Verify withInvoke-RestMethod http://127.0.0.1:9222/json/version.Use
127.0.0.1, neverlocalhost—localhostresolves IPv6 first and times out; the debug port binds IPv4 loopback only.renderer-busyerrors — apage.evaluatelanded while the canvas was mid-render (page switch or visual queries in flight). The read fails fast rather than hanging; retry in a few seconds, or usepbi_wait_forto gate on readiness.Report renders as if frozen / blank — check
pbi_launch's pre-flight warnings. Orphanedmsmdsrv(Analysis Services) engines from earlier debug launches hold RAM and starve the new instance; end themsmdsrvprocesses with no matching Desktop (or reboot) before blaming the report. Also avoid two Desktop instances at once.A selector stopped matching after a Desktop update — use
pbi_snapshot(ARIA tree) orpbi_deep_snapshotto rediscover structure, thenpbi_run_codeto drive it.
Coexistence with other CDP clients
CDP supports multiple simultaneous clients attached to the same endpoint. So
pbi-webview2 can run alongside any other CDP client (Playwright, another MCP server, a
DevTools window) pointed at http://127.0.0.1:9222 at the same time — they don't
conflict. On disconnect, pbi-webview2 never closes Desktop: connectOverCDP's close
only detaches the CDP session, it doesn't terminate the process.
Development / test
npm test # smoke test — passes WITHOUT Desktop running (asserts connected:false)The smoke test spawns node server.js, speaks MCP over stdio, asserts all 55 tools are
registered (exact-count assertion), then calls pbi_status and asserts it returns
{connected:false, error, hint} (Desktop not running). It points the CDP endpoint at a
dead port so the connect fails fast — no Desktop required.
License
MIT — see LICENSE.
Available Tools
33 toolspbi_baselineA
Capture / compare / list value baselines. capture: store {cards, badges, visibleVisualCount} per page (default current; pages:["*"]=all) to /baselines/.json. compare: re-capture + diff. list: available baseline names.
Inputs: action (required), name, pages.
pbi_clickA
Generic tag-then-act click by text | ariaLabel | selector (ctrl for action buttons; index disambiguates). Overlay-intercept coordinate fallback. Returns {clicked, method, matchedLabel, candidateCount}.
Inputs: ctrl, text, index, selector, +2 more.
pbi_cross_filter_testB
Click a data point and judge whether a cross-filter FIRED (highlights rose OR card fingerprint changed). Optional restore re-clicks and verifies baseline. Returns {fired, highlightsBefore, highlightsAfter, changedCards, restored}.
Inputs: restore, selector, ariaLabel.
pbi_dax_queryA
Write DAX into the DAX query view editor and RUN it, then read the results grid. Reaches the daxQueryView CDP target (SEPARATE from reportView); {ok:false, reason} if the view has never been opened in Desktop (open the DAX query view tab first — this tool will NOT open ribbon views). Runs via F5 on the focused editor, falling back to a visible "Run" button. Returns {ran, columns, rows, rowCount, error?}. CAVEAT: this OVERWRITES whatever DAX the user had in the query editor — prior text is NOT restored (unrecoverable). Use a throwaway query view.
Inputs: dax (required), timeoutMs.
pbi_deep_snapshotA
Raw-CDP deep inspection of the reportView page. what:"axtree" (default) = a compact {role,name} projection of the full accessibility tree (capped to maxNodes, totalNodes reported); "dom" = a DOMSnapshot size probe ({documentCount, note}); "heap" = V8 heap usage ({usedMB, totalMB}). Read-only; safe.
Inputs: what, maxNodes.
pbi_deselectC
Clear visual/group selection via the neighbour-page-and-back trick (never blind-clicks the canvas). Returns {selectedBefore, selectedAfter, page}.
Inputs: no named parameters.
pbi_dialogA
Automate a Desktop dialog (desktopDialogHost CDP target — exists ONLY while a dialog is showing). action:"read" returns {open, text, buttons}; action:"click" trusted-clicks the button matching button (exact, then case-insensitive contains). Returns {open:false, reason:"no dialog showing"} when none is up. SAFETY: a button labelled "Save" (any case) is refused UNLESS the button param is EXACTLY "Save" (case-sensitive) — protects against accidentally saving the report.
Inputs: action (required), button.
pbi_emulate_themeA
Force the WebView media query prefers-color-scheme to light/dark/no-preference (via page.emulateMedia). Returns {emulated, scheme}. NOTE (verified 2026-07-15): the signal flips correctly (matchMedia reflects it) but Power BI Desktop reports do NOT restyle on prefers-color-scheme — report theming is driven by theme.json + app settings, not this CSS signal — so this is effectively INERT for PBI report canvases (it would matter only for a generic web app). Kept for completeness; do not expect a visual change. RESET to "no-preference" when done.
Inputs: scheme (required).
pbi_evalA
Escape hatch: evaluate a function-body or arrow-fn string in the reportView page. Rejects any code referencing powerBIAccessToken. Returns the JSON-serialized result. NOTE: the powerBIAccessToken rejection is a best-effort textual guard (a regex on the code string), not a sandbox.
Inputs: js (required).
pbi_fire_bookmarkA
Fire ANY bookmark by name via the View > Bookmarks pane (trusted coordinate click; optional group; expandable groups auto-expanded). Returns {fired, landedPage, warning?}.
Inputs: name (required), group, expectPage.
pbi_goto_pageA
Navigate to a page by EXACT name. Returns as soon as the tab switches (the canvas may still be rendering — use pbi_wait_for before reading values); waitReady:true blocks until visuals are painted. On not-found returns closest candidates.
Inputs: name (required), waitReady.
pbi_hover_tooltipA
Trusted mouse-move hover to a point (offsetX/offsetY to aim off-center, e.g. donut ring) and read the tooltip text. Returns {tooltipText} or {tooltipText:null}.
Inputs: offsetX, offsetY, selector, ariaLabel.
pbi_launchA
Launch Power BI Desktop on a .pbip WITH the CDP debug port. Resolves a launcher in order: PBI_DESKTOP_BRIDGE env → powerbi-desktop on PATH → direct PBIDesktop.exe (reported as launcher: bridge-env|bridge-path|direct). Injects the WebView2 remote-debugging env var into Desktop's own process, spawns detached, waits until the CDP port answers. If the port is already up, reports the running instance instead of launching a second one. Pre-flight warns about orphaned PBIDesktop/msmdsrv. After it returns cdpUp:true, call pbi_wait_for {text:""} — the canvas is still rendering.
Inputs: pbip (required), port, waitPortMs.
pbi_matrix_expandB
Expand or collapse a matrix hierarchy row (by rowHeader). Grid re-renders; returns {rowsBefore, rowsAfter}.
Inputs: rowHeader (required), collapse, titleMatch.
pbi_pagesA
List all report page tabs as [{name, active}].
Inputs: no named parameters.
pbi_page_sweepA
HEAVY, deliberately renders every page: each page switch makes Desktop re-query all its visuals (blank/busy for that page’s real load time, often 10-30s+). Run as an intentional health sweep (≤2 pages per call; user warned/away), never casually. Iterates pages, waits canvas-ready (cap 30s/page), records loadMs + error scan + cards fingerprint; restores the original page.
Inputs: pages, errorScan.
pbi_perf_analyzerA
Run the Performance Analyzer lane: per-visual render ms; optional captureQueryFor grabs one visual DAX via clipboard (clobbers clipboard). Restores pane + ribbon.
Inputs: captureQueryFor.
pbi_read_cardsA
Parsed card visuals as [{title, value}].
Inputs: no named parameters.
pbi_read_dax_editorA
Read the DAX query view editor text (Monaco). Reaches the daxQueryView CDP target (a SEPARATE page from reportView); returns {ok:false, reason} if that view has never been opened in Desktop (open it via the DAX query view tab first — this tool will NOT open ribbon views). Read-only.
Inputs: no named parameters.
pbi_read_matrixA
Read a matrix/grid fully as {columns, rows:[{header, cells}], ariaRowCount, ariaColCount, complete}. Scrolls virtualized grids and merges by row header.
Inputs: index, titleMatch.
pbi_read_tmdlA
Read the TMDL view editor text (Monaco). Reaches the tmdlView CDP target (a SEPARATE page from reportView); returns {ok:false, reason} if that view has never been opened in Desktop (open the TMDL view tab first — this tool will NOT open ribbon views). Read-only.
Inputs: no named parameters.
pbi_run_codeA
TRUSTED escape hatch (browser_run_code_unsafe equivalent). Runs an async JS function with the live Playwright reportView page — page.mouse/page.keyboard give REAL trusted input (unlike pbi_eval, which is page.evaluate and cannot do trusted clicks/keys). Use for rediscovery + interactions the canned tools do not cover. The code string must be an async arrow/function taking (page); its return value is JSON-serialized. Rejects powerBIAccessToken. UNSAFE: arbitrary automation — mutating clicks change report state, so restore after. The powerBIAccessToken rejection is a best-effort textual guard (a regex on the code string), not a sandbox. NOTE inside page.evaluate sandboxes setTimeout is undefined — use page.waitForTimeout(ms).
Inputs: code (required).
pbi_scan_errorsA
Scan visible visuals for broken/errored surfaces + recent non-benign console errors. Excludes the permanent favicon.ico error (reported as benignFaviconCount).
Inputs: no named parameters.
pbi_screenshotA
Screenshot the reportView page to the output dir (never the repo). Optional filename + fullPage. With visualTitle, clips the shot to the matching visual (exact then case-insensitive contains on the same title logic as pbi_visuals) and returns {clippedTo}; on a miss returns {saved:false, reason, candidates}. Returns {path}.
Inputs: filename, fullPage, visualTitle.
pbi_search_slicerA
Type into a slicer's search box and (optionally) click the matching result. Focuses the box IN-PAGE and ABORTS (searched:false) if focus does not land on it — never sends keystrokes to the canvas (a mis-focused select-all+delete once removed all page visuals). Clears via the input's own value, types query, polls for filtered .slicerItemContainer items, returns them as matches. With pick, clicks the item equalling (then including) pick. Returns {searched, picked, pickedLabel?, matches} or {searched:false, reason}. Mutates a filter when pick clicks — restore after.
Inputs: query (required), pick, container.
pbi_set_slicerA
Set a slicer: kind "button" (buttonSlicerVisual, returns aria-pressed before/after) or "item" (list slicer item, returns a fresh state probe).
Inputs: kind (required), value (required).
pbi_snapshotA
Accessibility-tree snapshot of the reportView page (browser_snapshot equivalent) for structure discovery when a selector drifts. Returns the ARIA snapshot (roles + accessible names, indented) as lines. selector scopes to a sub-tree (default "body"); filter (regex string) keeps only matching lines (with their indent); maxLines caps output. Read-only.
Inputs: filter, maxLines, selector.
pbi_state_probeB
Batched scorecard: {activePage, toggles, cards, badges, selectedCount, slicerItemsVisible, visibleVisualCount}.
Inputs: no named parameters.
pbi_statusA
Connect to Power BI Desktop over CDP and report status: build, title bar, active page, page count, zoom, canvasReady. Returns {connected:false,...} if unreachable.
Inputs: no named parameters.
pbi_typeA
Type text into an editable element (input/textarea/contenteditable/searchbox/textbox) via TRUSTED keyboard input. Target resolution: selector > element whose aria-label includes ariaLabel > first visible search/text input. Focuses the input IN-PAGE and ABORTS (typed:false) if focus does not land on it — never sends keystrokes to the canvas. clear empties the input via its own value (no global select-all); submit presses Enter. Returns {typed, matchedLabel, cleared, submitted}. WARNING: real typing mutates report state — restore/clear when testing.
Inputs: text (required), clear, submit, selector, +1 more.
pbi_visualsA
List every visible visual on the active page as [{title, type, x, y, width, height, hasError}]. type is a class-token heuristic (barChart, slicer, card, tableEx, pivotTable, donutChart, …; null when unknown); title from the container/descendant aria-label or .visualTitle; hasError reuses the broken-visual scan. Coordinates are getBoundingClientRect() rounded. Read-only.
Inputs: no named parameters.
pbi_wait_forB
Poll body innerText until text appears (text) or disappears (textGone). Returns {satisfied, elapsedMs}.
Inputs: text, textGone, timeoutMs.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 33 tools
Most tools have clearly distinct purposes, with detailed descriptions that differentiate similar functionalities (e.g., pbi_eval vs pbi_run_code, pbi_snapshot vs pbi_deep_snapshot). A few pairs could be confused (pbi_type vs pbi_search_slicer) but the descriptions provide enough context to resolve ambiguity.
All tools consistently follow a 'pbi_' prefix with a verb_noun pattern (e.g., pbi_click, pbi_read_cards, pbi_search_slicer). There is no mixing of naming conventions, and the pattern is predictable and clear.
With 33 tools, the server covers a wide range of Power BI automation tasks, but the count feels high for an MCP server. Some tools are noted as inert or very niche (e.g., pbi_emulate_theme), suggesting slight bloat. Still, the domain complexity somewhat justifies the number.
The tool set covers essential operations: launch, navigation, visuals reading, interaction (click, type, slicer, context menu), data query (DAX, TMDL), error scanning, and state probing. Missing are data refresh or structural modifications like adding pages or visuals, but for automation and testing, the set is comprehensive.
Related MCP Connectors
Control a real browser from AI agents: navigate, click, type, screenshot, read the page.
Give AI agents a real cloud Chrome: open sessions, navigate, read pages, click, type and screenshot.
Web scraping, browser automation, screenshots and AI data extraction for AI agents via MCP.
Cloud browser, web extraction, screenshots, and remote CDP tools for AI agents.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceEnables AI assistants to control and inspect a live Chrome browser for automated web debugging, performance analysis, and Lighthouse audits. It allows agents to capture screenshots, monitor network requests, and measure Core Web Vitals using plain-English prompts.1,868,745 npmApache 2.0
- FlicenseNot gradedqualityDmaintenanceEnables AI agents to interact with TradingView Desktop via Chrome DevTools Protocol for automated morning briefs, chart analysis, Pine script development, and trading workflows.85 npm-
- AlicenseNot gradedqualityCmaintenanceEnables AI agents to securely connect to your own Power BI or Fabric tenant using your Azure identity, with tools to inspect workspaces and models, run read-only DAX, validate and deploy your own reports, take page screenshots, and enforce guardrails that protect shared resources.1MIT
- FlicenseNot gradedqualityCmaintenanceEnables an AI assistant to control and read a locally running desktop charting application via Chrome DevTools Protocol, supporting Pine Script development, chart navigation, drawing, alerts, replay, and screenshots.85 npm-