JP

jonathan-pap/powerbi-report-mcp

开发工具
21 stars 0 forks 质量 90 趋势 90

MCP server for building Power BI reports — pages, visuals, themes, layouts. PBIR format. Works with Claude Code, Claude Desktop, and as a Cowork plugin.

概览

Power BI Report MCP Server Build Power BI reports with natural language — currently tested with Claude (Code, Desktop, Cowork). Tested: Claude Code · Claude Desktop · Claude Cowork Built on the standard MCP protocol — other MCP-compatible clients should work but are not yet verified. One prompt. One batch call. Full page in Power BI Desktop. - (v0.9.6) — same checks the test suite runs (margins, gaps, overlap, off-canvas, banner geometry), callable from agents on a single page or across the entire report - (v0.9.5) — see CHANGELOG.md - — every tool name now starts with pbir_ to avoid collisions with sibling MCP servers in the same session - — drop a single .plugin file into Claude (no terminal, no Node install) — see Quick Start › Cowork plugin - — modern MCP SDK entrypoint with structured outputSchema on every read tool - — fewer "did you mean…

README

Power BI Report MCP Server

Build Power BI reports with natural language — currently tested with Claude (Code, Desktop, Cowork).

Tested: Claude Code · Claude Desktop · Claude Cowork Built on the standard MCP protocol — other MCP-compatible clients should work but are not yet verified.


“Create an executive summary page with 6 KPI cards, a revenue trend line chart, and a bar chart by country”

One prompt. One batch call. Full page in Power BI Desktop.


What’s new in 0.9

  • Layout validator now reachable via pbir_validate_wireframe (v0.9.6) — same checks the test suite runs (margins, gaps, overlap, off-canvas, banner geometry), callable from agents on a single page or across the entire report
  • Tier C.1 catalog reduction + pbir_guide fix (v0.9.5) — see CHANGELOG.md

What’s new in 0.8

  • pbir_ tool prefix — every tool name now starts with pbir_ to avoid collisions with sibling MCP servers in the same session
  • Cowork plugin — drop a single .plugin file into Claude (no terminal, no Node install) — see Quick Start › Cowork plugin
  • registerTool migration — modern MCP SDK entrypoint with structured outputSchema on every read tool
  • Typo catcher + auto-pageId — fewer “did you mean…” round-trips when there’s only one page
  • Tighter outputSchema (v0.8.2) — 14 read tools now ship per-tool zod response schemas; mutation tools keep the loose envelope

Full details: CHANGELOG.md.


What is this?

The first open-source MCP server for Power BI report authoring. It connects Claude (or any MCP-compatible client — see Tested clients) to Power BI’s PBIR (Power BI Report) file format, turning natural language into real report pages — cards, charts, tables, themes, filters, and formatting.

No REST API keys. No Power BI service. Just local files + Claude.

You: "Build me a sales dashboard with KPIs, trend charts, and a detail table"

AI:  pbir_create_page → pbir_add_visual (batch: 12 visuals) → pbir_set_report_theme → done.
     Open in Power BI Desktop. ✓

Why MCP?

MCP (Model Context Protocol) is an open standard that lets AI assistants call external tools. Instead of the AI generating code for you to run, it directly executes operations through the MCP server.

graph LR
    subgraph AI["AI Assistant"]
        LLM["Claude, GPT,Copilot, Cursor..."]
    end

    subgraph MCP["Two MCP Servers"]
        direction TB
        MODEL["powerbi-modeling-mcpTables, columns, measures, DAX"]
        REPORT["powerbi-report-mcpPages, visuals, themes, filters"]
    end

    subgraph Files["Power BI Project"]
        direction TB
        SEM[".SemanticModelTMDL / measures"]
        REP[".ReportPBIR / visual.json"]
    end

    PBI["Power BIDesktop"]

    LLM |"stdio / MCP"| MODEL
    LLM |"stdio / MCP"| REPORT
    MODEL |"read"| SEM
    REPORT |"read/write"| REP
    REP -->|"open .pbip"| PBI

The typical workflow:

1. Query the model   --> "What tables and measures are available?"    (modeling-mcp)
2. Build the report  --> "Create a dashboard with those measures"     (report-mcp)
3. Open in Desktop   --> Ctrl+Shift+F5 to refresh

Both servers run simultaneously as MCP tools. The AI queries the semantic model for exact table/column/measure names, then uses those to build correctly-bound report pages — no guessing, no broken fields.

Zero vendor lock-in — built on @modelcontextprotocol/sdk + zod. No Anthropic, OpenAI, or Microsoft SDK imports.


How It Compares

This MCP Server Manual PBI Desktop Power BI REST API pbi-tools
Input Natural language Mouse clicks REST calls + auth CLI commands
Speed 10-page report in minutes Hours Hours (code-heavy) Minutes (extract/deploy)
Auth required None (local files) None Azure AD + Service Principal None
AI-native Yes (MCP) No No No
Format PBIR (file-based) PBIX (binary) Cloud-only PBIX ↔ folder
Creates visuals Yes Yes Limited No (metadata only)
Themes & formatting Yes Yes Limited No
Filters Yes Yes Yes No
Works offline Yes Yes No Yes

Quick Start

Full walkthrough: docs/quickstart.md

1. Install

git clone https://github.com/jonathan-pap/powerbi-report-mcp.git
cd powerbi-report-mcp
npm install
npm run build

2. Configure your MCP client

Ready-to-use config files are in the configs/ folder — copy the one for your client, update the path, done.

Config Client Copy to
claude-desktop.json Claude Desktop %LOCALAPPDATA%\...\Claude\claude_desktop_config.json
cursor.json Cursor ~/.cursor/mcp.json
vscode-copilot.json GitHub Copilot .vscode/mcp.json
windsurf.json Windsurf ~/.windsurf/mcp.json
continue-dev.json Continue.dev ~/.continue/config.json
cline.json Cline VS Code Settings → MCP Servers

Claude Code (no config file needed):

claude mcp add powerbi-report-mcp node C:\path\to\powerbi-report-mcp\dist\index.js

Each config includes both powerbi-report-mcp and powerbi-modeling-mcp for full dual-layer access. See configs/README.md for optional settings (pre-connect to a report, load all tools at startup).

Optional: opt into minimal tool loading

For long Claude Code / Cowork sessions where catalog tokens matter, set MCP_TOOLS=minimal to load only the 12 default tools at startup (saves ~7,500 catalog tokens; the remaining 44 activate on demand via pbir_load_tools):

{
  "mcpServers": {
    "powerbi-report-mcp": {
      "command": "node",
      "args": ["/path/to/powerbi-report-mcp/dist/index.js"],
      "env": {
        "MCP_TOOLS": "minimal"   // optional — saves ~7,500 catalog tokens
      }
    }
  }
}

Trade-off summary (full breakdown in Smart Tool Loading below):

  • Default (load-all) — All 56 tools available immediately. Best for unpredictable/exploratory sessions and clients that snapshot the tool list at startup. ~14,500 catalog tokens.
  • MCP_TOOLS=minimal — 12 default tools at startup; others activatable via pbir_load_tools. Best for known-narrow workflows. ~7,000 catalog tokens. Requires MCP client support for notifications/tools/list_changed to surface activated tools mid-session — Claude Code/Desktop don’t refresh; Cowork may; verify before relying.

3b. Cowork plugin

Prefer to skip the git clone/npm install dance? Grab the latest .plugin bundle from GitHub Releases and drag it into Claude — the plugin ships the server, skills, and a default MCP wiring in one file.

Step Action
1 Download powerbi-report-builder-.plugin from the latest release
2 Open Claude → Settings → Plugins → Install from file (or drag-and-drop the .plugin onto the Claude window)
3 Approve the bundled MCP server when prompted
4 In any Claude conversation: “Connect to C:\Projects\Sales.Report and list pages”

Cowork is a hosted Claude experience with native plugin support. The plugin bundle is a single zip; nothing leaves your machine and the MCP server still runs locally over stdio.

3. Connect and build

Connect to C:\Projects\Sales.Report
Create a page called "Overview" with 4 KPI cards and a bar chart by country

4. Open in Power BI Desktop

Open the .pbip file — or if already open, press Ctrl+Shift+F5 to refresh.

Headless / eval mode

For automated/eval use you can auto-bind a report at startup with the PBIR_REPORT_PATH env var instead of calling pbir_set_report:

PBIR_REPORT_PATH=evals/fixtures/sample.Report node dist/index.js

If the path is invalid the server logs to stderr and continues running unbound (use pbir_set_report to recover). The CLI arg form (node dist/index.js ) still works and wins when both are set.


Smart Tool Loading

By default all 56 tools load at startup — this is the most compatible configuration, and what you want for Claude Desktop and most other MCP clients whose tool catalog is a snapshot taken at session start.

For token-sensitive setups (e.g. Claude Code with large prompt budgets on dev machines), you can opt into the minimal mode — only 12 core tools load at startup, and the LLM activates more on-demand via pbir_load_tools:

"env": { "MCP_TOOLS": "minimal" }
graph TD
    subgraph DEFAULT["11 Core Tools — always loaded in both modes"]
        A1[pbir_set_report] --- A2[pbir_list_pages] --- A3[pbir_list_visuals] --- A4[pbir_create_page] --- A5[pbir_add_visual]
        B1[pbir_get_visual] --- B2[pbir_format_visual] --- B3[pbir_update_visual_bindings] --- B4[pbir_set_report_theme] --- B5[pbir_bulk_bind]
        C1[pbir_model_usage]
    end

    LT[pbir_load_tools -- always available]

    subgraph ONDEMAND["43 On-Demand Tools"]
        C1[pbir_delete_page] --- C2[pbir_rename_page] --- C3[pbir_duplicate_page] --- C4[pbir_move_visual] --- C5[pbir_delete_visual]
        D1[pbir_set_datapoint_colors] --- D2[pbir_set_conditional_format] --- D3[pbir_add_page_filter] --- D4[pbir_set_visual_sort] --- D5[guide]
        E1[pbir_list_bookmarks] --- E2[pbir_set_page_background] --- E3[...]
    end

    DEFAULT --> LT --> ONDEMAND

    style DEFAULT fill:#1a7f37,color:#fff
    style ONDEMAND fill:#333,color:#ccc
    style LT fill:#0078D4,color:#fff
Mode Tools at Startup Token Overhead Use Case
default 56 + pbir_load_tools ~16,500 tokens Claude Desktop, most clients, first-time users
MCP_TOOLS=minimal 12 + pbir_load_tools ~3,400 tokens Claude Code / clients that refresh the tool list mid-session
MCP_TOOLS=all (legacy alias) 56 + pbir_load_tools ~16,500 tokens Same as default; kept for backward-compat

Default is “load everything” because Claude Desktop snapshots the MCP tool catalog at session start and never refreshes it — tools activated mid-session via pbir_load_tools would otherwise be invisible to the model. Clients that honour tools/list_changed notifications (Claude Code, Cowork) can opt into MCP_TOOLS=minimal to claw back ~13k tokens.


Tool Reference

Default Tools

Tool Description
pbir_set_report Connect to a .Report folder at runtime
pbir_list_pages List all pages (id, name, visual count)
pbir_list_visuals List visuals on a page (id, type, position, title)
pbir_create_page Create a new page
pbir_add_visual Add one or many visuals with bindings, formatting, colors
pbir_get_visual Inspect a visual’s config and bindings
pbir_format_visual Format axes, legend, labels, borders, background
pbir_update_visual_bindings Replace data bindings on a visual
pbir_set_report_theme Apply a custom JSON theme to the whole report
pbir_bulk_bind Rebind multiple visuals in one call
pbir_model_usage Cross-reference semantic model with report — three-tier classification (direct/indirect/unused), DAX lineage, UDF functions, conditional formatting detection
pbir_load_tools List and activate on-demand tools

Why pbir_model_usage is a default tool — the deletion fail-safe

pbir_model_usage ships in the default set (not on-demand) because it is the safety layer for AI-driven cleanup of the semantic model. When a user asks Claude to “remove unused measures” or “clean up dead columns”, the model would otherwise guess based on measure names and delete things blindly — and break visuals that depend on indirect references.

With pbir_model_usage always available, the LLM can call it first and see the full dependency picture before touching anything:

Scenario Without pbir_model_usage With pbir_model_usage
User: “delete all unused measures” LLM guesses by name, deletes Margin Delta pp, Margin Arrow breaks the next day LLM sees Margin Delta pp is status indirect (referenced by Margin Arrow), keeps it, lists only the truly safe ones
User: “is Discount Arrow used anywhere?” LLM greps the visual JSON, misses conditional formatting bindings LLM sees it bound to cardImage.imageData / referenceLabel.value and reports exactly where
User: “which UDF functions can I drop?” LLM has no lineage info LLM sees reference counts per function and flags zero-reference functions

The MCP tool gives Claude that understanding before it mutates anything — what prevents “oops” deletes. The tool returns a slim JSON response (~7K tokens) by default so it’s cheap to call before every destructive operation.

On-Demand Tools (43)


Batch Mode — Build Pages Fast

Create an entire page in a single pbir_add_visual call:

{
  "pageId": "abc123",
  "visuals": [
    {
      "visualType": "shape", "shapeType": "rectangle",
      "x": 0, "y": 0, "width": 1280, "height": 50,
      "fillColor": "#1F3864", "textContent": "Sales Dashboard",
      "textColor": "#FFFFFF", "textBold": true, "textSize": 20
    },
    {
      "visualType": "card",
      "x": 10, "y": 60, "width": 300, "height": 100,
      "title": "Revenue",
      "bindings": [
        { "bucket": "Fields", "fields": [{ "field": "Sales[Revenue]", "type": "measure" }] }
      ]
    },
    {
      "visualType": "clusteredBarChart",
      "x": 10, "y": 170, "width": 620, "height": 260,
      "title": "Revenue by Country",
      "bindings": [
        { "bucket": "Category", "fields": [{ "field": "Store[Country]", "type": "column" }] },
        { "bucket": "Y", "fields": [{ "field": "Sales[Revenue]", "type": "measure" }] }
      ],
      "dataColors": [{ "color": "#0078D4" }]
    }
  ]
}

One call creates the banner, KPI card, and chart — with data bindings, titles, and colors.


Supported Visual Types

Full reference: docs/visual-types.md

Naming Gotchas

barChart              = Stacked bar       (NOT clustered)
columnChart           = Stacked column    (NOT clustered)
clusteredBarChart     = Clustered bar     ✓
clusteredColumnChart  = Clustered column  ✓
stackedBarChart       = DOES NOT EXIST    ✗ (use barChart)
scatterChart          = Uses "Details" bucket, NOT "Category"
Combo charts          = Use "ColumnY" + "LineY", NOT "Y" + "Y2"

Quick Reference

Category Types
Bar/Column barChart · clusteredBarChart · columnChart · clusteredColumnChart · hundredPercentStackedBarChart · hundredPercentStackedColumnChart
Line/Area lineChart · areaChart · stackedAreaChart · hundredPercentStackedAreaChart
Combo lineClusteredColumnComboChart · lineStackedColumnComboChart
Pie/Donut pieChart · donutChart · funnelChart · treemap
Tables tableEx · pivotTable (matrix)
Cards card · cardVisual · multiRowCard · kpi · gauge
Slicers slicer (Basic/Dropdown) · listSlicer · textSlicer · advancedSlicerVisual
Maps azureMap · map · filledMap
Scatter scatterChart
Other ribbonChart · waterfallChart · decompositionTreeVisual
Decorative textbox · shape · image · actionButton · pageNavigator

Formatting

pbir_format_visual(target="auto")          → auto-routes to container or visual (default)
pbir_format_visual(target="container")     → title, background, border, padding, shadow
pbir_format_visual(target="visual")        → axes, legend, labels, line styles, data points

Hex colors starting with # are automatically wrapped in PBIR format.


Themes & Conditional Formatting

Report-Level Theme

{
  "name": "Corporate Brand",
  "dataColors": ["#0078D4", "#00BCF2", "#00B294", "#FF8C00", "#E81123"],
  "background": "#FFFFFF",
  "foreground": "#1F3864",
  "tableAccent": "#0078D4"
}

Gradient Conditional Format

{
  "formatType": "gradient",
  "entity": "Sales", "property2": "Revenue", "isMeasure": true,
  "minColor": "#FF6B6B", "midColor": "#FFD93D", "maxColor": "#6BCB77"
}

Page Themes (presets)

dark · light · corporate · blue-purple


Filters

// Categorical — include specific values
{ "filterType": "categorical", "entity": "Store", "property": "Region", "values": ["East", "West"] }

// TopN — top 10 products (visual-level only)
{ "filterType": "topN", "entity": "Product", "property": "Name", "n": 10,
  "topNDirection": "Top", "orderByEntity": "Sales", "orderByProperty": "Revenue",
  "orderByIsMeasure": true, "visualId": "xyz" }

// Relative date — last 12 months
{ "filterType": "relativeDate", "entity": "Date", "property": "Date",
  "period": "months", "count": 12, "dateDirection": "last" }

Architecture

powerbi-report-mcp/
├── src/
│   ├── index.ts              # Server entry, smart tool loading, safe() wrapper
│   ├── pbir.ts               # PbirProject — PBIR file I/O abstraction
│   ├── context.ts            # ServerContext interface
│   ├── model-usage.ts        # Model usage analysis — three-tier classification, UDF parsing, conditional formatting
│   ├── tools/
│   │   ├── report.ts         # Page & report management (20 tools)
│   │   ├── visuals.ts        # Visual CRUD (8 tools)
│   │   ├── format.ts         # Formatting, sort & colors (6 tools)
│   │   ├── bindings.ts       # Data binding (1 tool)
│   │   ├── themes.ts         # Report themes (6 tools)
│   │   ├── filters.ts        # Page/visual filters (4 tools)
│   │   ├── bulk.ts           # Bulk operations (3 tools)
│   │   ├── bookmarks.ts      # Bookmark CRUD (4 tools)
│   │   └── guide.ts          # Knowledge layer (1 tool, 2 topics)
│   └── helpers/
│       ├── createVisual.ts   # Visual creation engine
│       ├── formatting.ts     # PBIR formatting builder
│       └── defaults.ts       # Theme presets
├── .usage/                   # Generated usage dashboards (gitignored)
├── dist/                     # Compiled JS (committed for no-build deploy)
├── pbi report/               # Sample report (financials model)
├── docs/                     # Guides and references
└── skills/                   # LLM skill documents

Full details: ARCHITECTURE.md

Data Flow

sequenceDiagram
    actor User
    participant AI as AI Assistant
    participant Model as powerbi-modeling-mcp
    participant Report as powerbi-report-mcp
    participant PBI as Power BI Desktop

    User->>AI: "Create a sales dashboard with KPIs and a chart by country"
    AI->>Model: What measures are on the Sales table?
    Model-->>AI: Net Revenue, Net Profit, Margin %, Orders, Units Sold
    AI->>Report: pbir_create_page("Sales Dashboard")
    Report-->>AI: pageId: abc123
    AI->>Report: pbir_add_visual(batch: 6 cards + 2 charts + table)
    Report-->>AI: 9 visuals created
    AI->>Report: pbir_set_report_theme({ dataColors: [...] })
    Report-->>AI: theme applied
    AI-->>User: Done! Open .pbip in Power BI Desktop
    User->>PBI: Ctrl+Shift+F5

PBIR Folder Structure

MyProject.Report/
  definition/
    report.json                 # Report settings, theme config
    pages/
      pages.json                # Page order and active page
      {pageId}/
        page.json               # Page name, size, visibility
        visuals/
          {visualId}/
            visual.json         # Type, position, bindings, formatting
  StaticResources/
    RegisteredResources/        # Custom theme JSON files
  definition.pbir               # Semantic model reference

Token Efficiency

Mode Tools Loaded Tokens/Turn Cost per 10-Page Report
Default 11 ~3,100 $0.01 – $0.45
All 48 ~14,500 $0.02 – $2.50

Tested clients

The MCP is built on the standard MCP protocol. Currently verified against:

Client Status Config
Claude Code ✅ Tested claude mcp add or local .mcp.json
Claude Desktop ✅ Tested claude_desktop_config.json
Claude Cowork ✅ Tested Drag the .plugin from Releases

Other MCP-compatible clients (Cursor, Continue.dev, Cline, GitHub Copilot agent mode, OpenAI via mcp-proxy, custom @modelcontextprotocol/sdk agents) should work since this is a standard MCP server — but they haven’t been verified against this codebase yet. If you try one and it works (or doesn’t), please open an issue so we can update this table.


Documentation

Doc Description
docs/quickstart.md 5-minute setup guide
docs/example-prompts.md 15 example prompts
docs/visual-types.md Visual type reference + formatting containers per type
docs/wireframes.md Layout guide — zones, spacing, 3 sample layouts with exact positions
docs/pbir-gotchas.md PBIR schema discoveries
ARCHITECTURE.md Codebase architecture
CONTRIBUTING.md How to contribute
CHANGELOG.md Version history

Known Issues

Feature Status Notes
Visual calculations Disabled Correct PBIR format identified but not rendering programmatically

Tips

  • Pair with powerbi-modeling-mcp to query the semantic model for exact table/column names before binding
  • Use Table[Column] shorthand in bindings: "field": "Sales[Revenue]"
  • barChart = stacked bar, clusteredBarChart = clustered — there is no stackedBarChart
  • Add shapes before data visuals for correct z-order layering
  • pbir_format_visual merges with existing formatting — safe to call incrementally
  • TopN filters are visual-level only — pass visualId to pbir_add_page_filter
  • All tools return { success: false, error: "..." } on failure — the server never crashes
  • Use pbir_model_usage to see which measures/columns are used in visuals — it classifies fields as direct (on a visual), indirect (referenced by direct measures/relationships), or unused (safe to remove). It detects conditional formatting bindings (images, reference labels, colors) that other tools miss
  • Always call pbir_model_usage before any delete / cleanup request — it’s the fail-safe that stops the LLM from removing indirectly-referenced measures. See Why pbir_model_usage is a default tool for the full rationale
  • pbir_model_usage also parses UDF functions and calculation groups from TMDL/BIM, counts measure references per function, and surfaces DAX lineage

License

MIT — use it however you want.

View this README on GitHub

安装

This server does not publish a one-line install command.

Open the repository installation guide

配置

{ "mcpServers": { "powerbi-report-mcp": { "command": "node", "args": ["/path/to/powerbi-report-mcp/dist/index.js"], "env": { "MCP_TOOLS": "minimal" // optional — saves ~7,500 catalog tokens } } } }