Content
# pbi_mcp
Local MCP server to query the currently open Power BI Desktop model with DAX.
## What it does
- Finds the latest Power BI Desktop Analysis Services workspace.
- Reads the live SSAS port from `msmdsrv.port.txt`.
- Loads the Power BI ADOMD client directly through pythonnet.
- Runs a DAX query and returns JSON records.
## Requirements
- Windows
- Power BI Desktop open with a report loaded
- Python 3.12+
- The ADOMD.NET client installed by Power BI Desktop or Microsoft.NET ADOMD.NET
## Install dependencies
```bat
python -m pip install mcp pandas pythonnet
```
## Local test
Run a simple query directly:
```bat
python -c "from pbi_mcp import execute_dax_query; print(execute_dax_query('EVALUATE ROW(\"Ok\", 1)'))"
```
Expected output:
```json
[{"[Ok]":1}]
```
If your Power BI installation bundles `Microsoft.PowerBI.AdomdClient.dll` instead of
`Microsoft.AnalysisServices.AdomdClient.dll`, the code will load it directly.
## End-to-End DAX Flow
The tested path is:
1. Generate DAX with the compiler, for example `generateDax(...)` via `npx ts-node -e ...`.
2. Print a JSON envelope like `{"kind":"powerbi.dax","version":1,"daxQuery":"..."}`.
3. Capture `stdout`, parse the JSON, extract `daxQuery`, and pass that string to `query_powerbi_dax`.
That keeps the human input small. The manual part is the generator config; the DAX itself is an artifact.
`query_powerbi_dax` expects raw DAX text. The JSON envelope is only for the generator output.
Example from Python:
```python
import json
import subprocess
from pbi_mcp import query_powerbi_dax
generated = subprocess.run(
[
"npx",
"ts-node",
"-e",
"import { generateDax } from './src/insight/powerbi/daxStudioCompiler'; const daxQuery = generateDax({ dashboard: 'financials', date: { mode: 'last_n_months', months: 12, granularity: 'month' }, measures: ['NetCrDr Total'], top_n: { metric: 'NetCrDr Total', group_by: ['customer_l2', 'month'], n: 100, order: 'desc' } }); process.stdout.write(JSON.stringify({ kind: 'powerbi.dax', version: 1, daxQuery }));",
],
cwd=r"C:\Users\BrandonGomez\AgentToolkit\IG-CG-TEAMS-APPS\apps\chatbot",
capture_output=True,
text=True,
check=True,
).stdout
payload = json.loads(generated)
result = query_powerbi_dax(payload["daxQuery"].strip())
print(result)
```
If the generator emits plain DAX instead of JSON, trim the output and pass it directly:
```python
raw_dax = generated.strip()
result = query_powerbi_dax(raw_dax)
print(result)
```
If you want to confirm port discovery only:
```bat
python -c "from pbi_mcp import discover_powerbi_port; print(discover_powerbi_port())"
```
## Register in Codex
Use the Windows Python executable and an absolute path to the script:
```bat
codex mcp add pbi_mcp -- "C:\Users\BrandonGomez\AppData\Local\Programs\Python\Python312\python.exe" "C:\Users\BrandonGomez\AgentToolkit\PBIMCP2\pbi_mcp.py"
```
Check it:
```bat
codex mcp list
codex mcp get pbi_mcp
```
## Register in Claude Code
```bat
claude mcp add --scope user --transport stdio pbi_mcp -- "C:\Users\BrandonGomez\AppData\Local\Programs\Python\Python312\python.exe" "C:\Users\BrandonGomez\AgentToolkit\PBIMCP2\pbi_mcp.py"
```
Check it:
```bat
claude mcp list
claude mcp get pbi_mcp
```
## Example DAX
```DAX
EVALUATE ROW("Ok", 1)
```
For a real model query, replace the table and column names with ones that exist in your report.