← Back to series overview

Build the Report Locally with Copilot + Power BI MCP

Yes, I know, "just reload Desktop instead of restarting" sounds trivial until you've done it 40 times in a day. This is the edit-reload-screenshot loop I wish someone had shown me earlier.

What you'll build in Part 4

  • A Git-friendly .pbip project bound to the Part 3 semantic model
  • 4 functional pages: Overview, Product, Customer, Store, generated with Copilot
  • Visuals bound to certified measures with working slicers + drill
  • Local edit to reload to screenshot loop via Power BI Desktop Bridge, no restarts
  • A basic GitHub Actions deploy (the only place needing a service principal)

Table of Contents

Why PBIP for local development

All started with a customer editing PBIX files back and forth over email, you can guess how that ended. .pbix is a binary blob, no diffs, no reviews. PBIP (the project format of today, not the old PBIX-only flow) is just text files you can version and generate with Copilot. To start I'm assuming your Part 3 model validated, if RLS isn't tested yet, go back, the Bridge screenshots below won't catch a security hole.

SalesReport.pbip/
├── SalesReport.pbiproj
├── definition/        ← points at Part 3 model (DirectLake)
└── report/
    ├── report.json
    ├── pages/         ← Copilot generates these
    └── visuals/

Scaffold the PBIP project

# VS Code extensions
code --install-extension ms-powerbi.powerbi-pbip
code --install-extension github.copilot
code --install-extension github.copilot-chat

# Convert or init
pbi-tools convert -input "SalesReport.pbix" -output "SalesReport.pbip" -format PBIP
# ...or init fresh and point dataset at Part 3 model ID

Generate pages + visuals with Copilot

You: Create Overview page: 4 KPI cards (Total Sales, YoY Growth, Margin, Active Customers), bar chart Sales by Category, line Monthly Trend, slicers Year/Region/Category. Bind to Sales Star Schema measures
Copilot: Generating page JSON + visual bindings…
// MCP: powerbi.createReportPage(ws, datasetId, pageSpec)
// report/pages/Overview.reportpage.json
{
  "name": "Overview", "displayName": "Executive Overview",
  "visualContainers": [
    { "name": "kpi-sales", "visualType": "card", "queryRef": "TotalSales" },
    { "name": "kpi-yoy", "visualType": "card", "queryRef": "YoYGrowth" },
    { "name": "bar-category", "visualType": "clusteredBarChart",
      "queryRef": "SalesByCategory" },
    { "name": "line-trend", "visualType": "lineChart",
      "queryRef": "MonthlyTrend" }
  ]
}
Repeat for Product, Customer, Store pages. Open in Power BI Desktop to preview, numbers must match Part 3 DAX tests.
Don't polish yet: default theme is fine here. Don't hand-tune colors. Part 5 replaces the whole theme from Figma in one shot.

Bind data + add interactions

You: Wire bar to line cross-filtering, add drill-through from Category to Product page, and conditional formatting on YoY card (green positive / red negative)
Copilot:
// MCP: powerbi.bindVisual(ws, reportId, "kpi-yoy", {
  measure: "YoY Growth %",
  conditionalFormatting: [
    { condition: "> 0", color: "#00875A" },
    { condition: "< 0", color: "#DE350B" }
  ]})
// MCP: powerbi.addDrillthrough(ws, reportId, "bar-category", "Product")
Interactions live. Click a category bar to trend + KPIs filter. Right-click to drill to Product.

Live preview with Desktop Bridge: reload + screenshot

Editing PBIP JSON in VS Code is only half the loop, you need to see it. The Power BI Desktop Bridge (Preview) lets Copilot and external tools talk to a running Power BI Desktop on your machine, so you never restart to preview.

What it is: a local JSON-RPC server inside Power BI Desktop over named pipe pbi-desktop-bridge-{processId}. Local-only, one operation at a time. Prereq: Desktop on Windows + File to Options to Preview Features to Enable external tool access to Power BI Desktop through secure local APIs (on by default). See official Bridge overview.

Bridge CLI cheat sheet

# Install once
npm install -g @microsoft/powerbi-desktop-bridge-cli

powerbi-desktop status        # which Desktop window / file / unsaved changes / pages
powerbi-desktop manifest      # which bridge methods this Desktop version supports
powerbi-desktop open ./SalesReport.pbip   # open PBIP and wait for bridge
powerbi-desktop reload        # reload PBIR from disk after VS Code edits
powerbi-desktop screenshot Overview       # capture one page as PNG
powerbi-desktop screenshot-all            # capture every page in pages.json

The Copilot edit to reload to screenshot loop

You: I moved the YoY card 40px right in Overview.reportpage.json. Reload Desktop and screenshot Overview to verify nothing overlaps
Copilot / agent: Edit-and-verify via Bridge…
# 1. check state first (avoid clobbering unsaved work)
powerbi-desktop status
# currentFilePath: .../SalesReport.pbip, hasUnsavedChanges: false [ok]

# 2. reload report from disk (model too by default)
powerbi-desktop reload

# 3. visual check
powerbi-desktop screenshot Overview
# to Overview.png, agent "sees" card positions, flags overlap
No restart. If the screenshot shows overlap, Copilot fixes the JSON and repeats, the same loop the Report Authoring Skill uses.
Watch out with Bridge: it is still Preview, so the API can change. Windows and local only, no remote. Screenshots need the internal pageId from PBIR (don't forget that, you get MethodNotFound otherwise). If a reload is already running, wait. Concurrent calls just error out.

How it maps to this series: Fabric MCP = cloud data APIs. Power BI Modeling MCP = model editing. Bridge = controlling the local Desktop renderer. Use all three in Part 4, then reuse screenshot-all in Part 5 for themed before/after proof.

Deploy with GitHub Actions, this is where you need a service principal

Local work used your user login. GitHub Actions can't click "Sign in", so here, and only here in this series, create a service principal: Entra app registration to secret to enable Service principals can use Fabric APIs in Admin portal to add SP to workspace as Admin/Member to store AZURE_CLIENT_ID / SECRET / TENANT_ID as GitHub secrets.

# .github/workflows/deploy-pbip.yml (simplified)
name: Deploy PBIP
on:
  push: { branches: [main] }
jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: test
    steps:
      - uses: actions/checkout@v4
      - uses: azure/login@v2
        with:
          client-id: ${{ secrets.AZURE_CLIENT_ID }}
          tenant-id: ${{ secrets.AZURE_TENANT_ID }}
          subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
      - name: Deploy PBIP
        uses: microsoft/powerbi-deploy-action@v1
        with:
          pbip-path: 'SalesReport.pbip'
          workspace-id: ${{ secrets.PBI_WORKSPACE_ID }}

Merge to main to Test workspace updates. Prod stays manual approval, we'll harden that in the finale's checklist.

What's next in Part 5, the payoff

You have a correct report. Now make it beautiful. Part 5 connects Figma MCP, pulls your design system tokens, generates a Power BI theme, restyles every page, and publishes the stunning finale.

← Part 3: Semantic Models Continue to Part 5: Make It Beautiful →