{"id":50374864,"url":"https://github.com/danlugo/daxter","last_synced_at":"2026-06-12T00:01:28.285Z","repository":{"id":361289653,"uuid":"1253885690","full_name":"Danlugo/daxter","owner":"Danlugo","description":"Cross-platform, Docker-only CLI for the Power BI Service — query, document, and operate semantic models over XMLA. .NET 8.","archived":false,"fork":false,"pushed_at":"2026-06-03T23:27:35.000Z","size":447,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-04T01:02:44.391Z","etag":null,"topics":["analysis-services","cli","dax","docker","dotnet","fabric","powerbi","xmla"],"latest_commit_sha":null,"homepage":null,"language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Danlugo.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":"docs/ROADMAP.md","authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-29T23:05:55.000Z","updated_at":"2026-06-03T23:28:14.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/Danlugo/daxter","commit_stats":null,"previous_names":["danlugo/daxter"],"tags_count":38,"template":false,"template_full_name":null,"purl":"pkg:github/Danlugo/daxter","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Danlugo%2Fdaxter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Danlugo%2Fdaxter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Danlugo%2Fdaxter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Danlugo%2Fdaxter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Danlugo","download_url":"https://codeload.github.com/Danlugo/daxter/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Danlugo%2Fdaxter/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34222709,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-11T02:00:06.485Z","response_time":57,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["analysis-services","cli","dax","docker","dotnet","fabric","powerbi","xmla"],"created_at":"2026-05-30T09:01:20.629Z","updated_at":"2026-06-12T00:01:28.269Z","avatar_url":"https://github.com/Danlugo.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# DAXter\n\n[![CI](https://github.com/Danlugo/daxter/actions/workflows/ci.yml/badge.svg)](https://github.com/Danlugo/daxter/actions/workflows/ci.yml)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)\n![.NET 8](https://img.shields.io/badge/.NET-8.0%20LTS-512BD4)\n![Docker](https://img.shields.io/badge/Docker-multi--stage-2496ED)\n\n**Ask Claude to operate your Power BI + Fabric — or do it from your terminal / browser.**\nDAXter is a Docker-only **Power BI + Fabric** client with three surfaces — a **CLI**, an\n**MCP server** (catalogue discoverable via `daxter_capabilities`), and a **Blazor web console** — sharing one .NET engine. Covers\n**XMLA** (DAX/MDX/DMV), **Power BI REST**, **Fabric REST** (Copy Jobs · Notebooks ·\nsqlEndpoints · bindConnection · pipelines · semantic-model getDefinition), and **T-SQL on\nFabric Warehouses / Lakehouse SQL endpoints**. Plus a TOM-backed **model editor**, a\n**refresh scheduler** (file-backed queue · one shared worker · runs survive client drop),\nand a writes-gate with **glob-pattern allow/deny lists**. No Windows-only tooling, no .NET\ninstall — `docker run` it on macOS, Linux, or Windows.\n\n## Use it in Claude Desktop\n\nRun DAXter as an **MCP server**, then just **ask Claude** about your Power BI in plain language.\n**You need:** Docker Desktop running and a Power BI account (you sign in **as yourself** — no\nservice principal required to start).\n\n### 1. One-click extension (easiest)\n\n1. Download **[`daxter.mcpb`](https://github.com/Danlugo/daxter/releases/latest/download/daxter.mcpb)** — this link always serves the latest.\n2. In Claude Desktop open **Settings → Extensions → Advanced settings → Install Extension…**, pick `daxter.mcpb` (or **drag the file onto the Extensions page**), and **Install**. *(On macOS double-clicking the file also opens the installer; on Windows use this Settings flow — double-click may not be wired up.)*\n3. Ask Claude **\"sign me in to Power BI\"** → it returns a device-code link; sign in as yourself. Then ask away — *\"What measures are in the Sales model? Run `EVALUATE TOPN(10, Sales)`. When was it last refreshed? Who has access?\"*\n\nThe extension just **launches the same Docker image** — the .NET/XMLA engine runs in the\ncontainer, no JSON and no env file. To set a default workspace or enable writes, use the **web\nconsole** (below) once; otherwise name the workspace in your request.\n\n### 2. Let Claude Code set it up (manual config)\n\nPoint **Claude Code** (which can run commands; a plain Claude Desktop *chat* can't) at the guide —\nit pulls the image and merges the `daxter` server into your config:\n\n\u003e *\"Using **Claude Code**, set up the DAXter MCP server for my Claude Desktop by following https://github.com/Danlugo/daxter/blob/main/SETUP.md — **fetch the raw file (`raw.githubusercontent.com/Danlugo/daxter/main/SETUP.md`) so you get the exact commands, not a summary**, then run all the Docker/config commands and **start the web console**. I'll do the browser sign-in at localhost and the final Claude Desktop restart.\"*\n\nThen **fully quit \u0026 reopen Claude Desktop** and sign in as above. Full walkthrough (Windows notes,\nmulti-client, service principal) → **[`SETUP.md`](SETUP.md)**; a prompt per tool →\n[`examples/mcp.md`](examples/mcp.md).\n\n## What it does\n\n| Module | Commands |\n|--------|----------|\n| **Query** | `query` (DAX/MDX), `dmv`, `ls` — table / CSV / JSON |\n| **Model** | `model measures` · `measure` · `mcode` · `parameters` · `partitions` · `rls` · `export` (.bim) · `diff` |\n| **Edit** ⚠ | `model edit measure/parameter/role/column/edit-column/source/calc-table/import-table/relationship` (+ `delete-*`, raw `tmsl`) — **dry-run by default**, gated, `.bim` backup before apply. `edit-column` changes an existing column's **format / data type / sort-by / summarize-by / folder / hidden** (via TOM — raw TMSL can't edit a standalone column). Available in **CLI · MCP · web (Model Edit page)** — one shared engine |\n| **Ops** | `refresh model/table/partitions` (queued through the shared worker) · `refresh status` · `refresh trigger` · `refresh history` · `cache clear` (with `--dry-run`/`--yes`/`--force`) |\n| **Workspace** | `ws ls/datasets/reports/lineage/report-inventory/export-report/permissions/gateways/connections/datasources` (REST). `report-inventory` classifies reports **thin/thick + downloadable**; `export-report` pulls a report's **definition** (PBIR/legacy field references) and optionally its `.pbix` |\n| **Connections** ⚠ | See a model's **current \"maps to\" mapping** (`ws item-connections`), list **shareable cloud connections** (`ws connections`), **take over** + **bind** each source to any connection — cloud or gateway — via the Fabric `bindConnection` API: `ws bind-connection` (per-source, incl. cloud \"Maps to\") and `ws bind-gateway` (whole-model). **Dry-run by default**, gated, needs ownership. In **CLI · MCP (`daxter_item_connections`/`daxter_connections`/`daxter_take_over`/`daxter_bind_connection`/`daxter_bind_to_gateway`) · web (Connections page — both gateway and cloud sections writable)** |\n| **RLS** | List roles · view a role's table filter expressions (DAX) and members — the same definitions Tabular Editor shows in its role tree. Read-only browser; edits still go through Model Edit. In **CLI (`model rls --role`) · MCP (`daxter_rls`, `daxter_role_filters`, `daxter_role_members`) · web (RLS page)** |\n| **Test** | `test-rls --role/--user` (XMLA impersonation) |\n| **Pipeline** | `pipeline ls/stages/operations` · `pipeline rules` (deployment rules, inferred from per-stage parameter differences) · `pipeline audit` (models without rules, or `--mode check` to find matching models) · saved rule sets |\n| **SQL** | `sql endpoints` (list Warehouses + Lakehouse SQL endpoints in a workspace) · `sql query --endpoint NAME --query \"…\"` (T-SQL on a Fabric SQL endpoint over TDS, AAD-authenticated with your existing sign-in). Read-only by default; `--allow-writes` for INSERT/UPDATE/DELETE/MERGE/DDL. In **CLI · MCP (`daxter_sql_endpoints` / `daxter_sql_query`) · web (SQL page)** |\n| **Fabric items** | View, run, and monitor **Copy Jobs** and **Notebooks** — list per workspace, view definition (the `copyjob-content.json` / `artifact.content.ipynb`), run on demand (writes-gated, confirm modal), and watch recent runs (status / duration / failure reason) with cancel. In **CLI (`fabric copy-jobs/notebooks ls/show/run/runs/cancel`) · MCP (`daxter_copy_jobs`, `daxter_copy_job_definition`, `daxter_run_copy_job`, `daxter_notebooks`, `daxter_notebook_definition`, `daxter_run_notebook`, `daxter_item_runs`, `daxter_item_job_status`, `daxter_cancel_item_job`) · web (Copy Jobs + Notebooks pages)** |\n| **Foundations** | environment profiles (`--env`), device-code + service-principal auth |\n\nThe MCP server exposes these at **full parity** as **62 tools** (`daxter_login` + 41 read + 20 gated\nwrite/edit). Refreshes from any interface (CLI, MCP, UI) are **queued through one shared worker** that\nruns them **one per model at a time** — see [Refresh scheduler](#refresh-scheduler). See\n[`docs/PRODUCT.md`](docs/PRODUCT.md) for the product plan,\n[`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) for the design.\n\n## Refresh scheduler\n\nDAXter is one integrated system — the CLI, MCP server and web console are thin interfaces over a single\nengine. **All refreshes route through one shared queue** (file-backed on the `daxter-tokens` volume),\ndrained by a single **worker hosted by the web container**. The worker runs **one refresh per model at a\ntime** (different models in parallel), honours partition order + `--retries`, and records job status.\n\n- `daxter refresh model|table|partitions … --yes` (CLI) and `daxter_refresh` (MCP) **enqueue** a job and\n  return a **job id** — they don't execute inline. Two refreshes can never collide on the same model.\n- Track jobs with `daxter refresh status` (CLI), `daxter_refresh_jobs` (MCP), or the web **Jobs** page —\n  all read the same queue, so a CLI- or MCP-launched refresh shows up everywhere.\n- **Run the web container** (below) so its worker drains the queue — it does so whether or not you open\n  the UI. Without a worker, jobs simply wait (a heartbeat warning tells you none is running).\n\n## Web console\n\n`daxter web` serves a local Blazor console — the simplest way to **sign in**, set defaults, browse\nmodels, run DAX (with autocomplete + formatting), **edit the model** (parameters/roles/relationships/\ntables, on the gated **Model Edit** page), audit deployment rules, and read logs:\n\n```bash\ndocker run -d -p 127.0.0.1:8080:8080 --name daxter-web \\\n  -v daxter-tokens:/home/daxter/.daxter \\\n  ghcr.io/danlugo/daxter:latest web          # → http://localhost:8080 (host-localhost only)\n```\n\nOpen it → **Status → Sign in** (device-code) → **⚙ Configure** to set defaults / *Allow writes*. The\n`daxter-tokens` volume holds your **token and settings** — the single config source the CLI and MCP\nserver read too. This container also **hosts the refresh worker** that drains the shared queue (see\n[Refresh scheduler](#refresh-scheduler)), so keep it running to execute refreshes launched from any\ninterface. Full page-by-page tour in [`docs/PRODUCT.md`](docs/PRODUCT.md).\n\n## CLI\n\nPull the prebuilt **multi-arch** image (`linux/amd64` + `linux/arm64`, native on Apple Silicon and\nx86 — published to GHCR on every release) and run:\n\n```bash\ndocker pull ghcr.io/danlugo/daxter:latest\n./bin/daxter query 'EVALUATE TOPN(10, Sales)'     # DAX → table\n./bin/daxter model measures --with-expr -o csv    # metadata → CSV\n./bin/daxter ws gateways                          # REST inventory\n```\n\n`bin/daxter` runs the image with the token volume, mounts the current dir at `/work`, loads `./.env`,\nand attaches a TTY when interactive. Output is `-o table` (default), `csv`, or `json`; results to\n**stdout**, status to **stderr**. Configure via the web console or env vars\n(`DAXTER_WORKSPACE`, `DAXTER_DATASET`, auth — see [`SETUP.md`](SETUP.md#advanced-service-principal--headless)).\nBuild from source with `make image`; ship offline with `make save` / `make load`. Every command has a\nrunnable example in **[`examples/cli.md`](examples/cli.md)**.\n\n\u003e XMLA addresses use **names**, not the GUIDs in portal URLs. List model names with\n\u003e `./bin/daxter dmv 'SELECT [CATALOG_NAME] FROM $SYSTEM.DBSCHEMA_CATALOGS'`, then set\n\u003e `DAXTER_WORKSPACE` / `--dataset` accordingly.\n\n## MCP server\n\nThe same image is a **Model Context Protocol** server (stdio) via the `mcp` subcommand — add it with\nthe [extension](#1-one-click-extension-easiest) or the config block in [`SETUP.md`](SETUP.md). Call\n**`daxter_capabilities`** to discover every tool/feature in one shot (auto-generated from the registered\ntools, so it's always current). Tools\naccept optional `workspace`/`dataset` (name **or** id); results are JSON, capped to 1,000 rows. The\nwrite tools (`daxter_refresh`, `daxter_clear_cache`) are **dry-run by default** and only act when\n`execute=true` **and** writes are enabled — via the web console (*Allow writes*) or\n`DAXTER_MCP_ALLOW_WRITES=true`. `daxter_refresh` **queues** the job (returns a job id) for the shared\nworker to run — track it with `daxter_refresh_jobs`, **resume** an interrupted/failed one with\n`daxter_resume_refresh` (re-runs only the **not-yet-done partitions** by default). Refreshes execute via\nthe **server-managed Enhanced Refresh API** (no long-lived client connection → can't hang/drop;\n`maxParallelism` via `DAXTER_REFRESH_MAX_PARALLELISM`, default 4; `DAXTER_REFRESH_ENGINE=xmla` forces the\nlegacy client path for non-Premium models). The single worker\ndrains the queue **up to 4 models concurrently** (per-model serialized; queue depth is unbounded) —\nraise/lower it with `DAXTER_REFRESH_MAX_CONCURRENT_MODELS` (clamped to 1–16; higher consumes more\ncapacity + XMLA sessions).\nOnce on, **PROD is allowed by default**; set\n`DAXTER_MCP_BLOCK_PROD_WRITES=true` to re-block it. The **model-edit** tools (`daxter_edit_*`,\n`daxter_delete_*`, `daxter_set_*`, `daxter_create_*`, raw `daxter_edit_tmsl`) sit behind a **separate,\nstricter gate** — `DAXTER_MCP_ALLOW_MODEL_EDIT=true` or the web console *Allow model edits* — and take\na `.bim` backup before applying (XMLA edits are irreversible for PBIX download). Prompts per tool →\n[`examples/mcp.md`](examples/mcp.md).\n\n## Authentication\n\n- **Device code (default, interactive)** — sign in as yourself; the token caches in the\n  `daxter-tokens` volume, so later runs are silent. Best for operations that need *your* permissions\n  (e.g. gateway names). Requires an app registration with public client flows (or the built-in default).\n- **Service principal (automation)** — set `DAXTER_AUTH_MODE=service-principal` +\n  `DAXTER_TENANT_ID`/`DAXTER_CLIENT_ID`/`DAXTER_CLIENT_SECRET`. The tenant admin must enable *\"Allow\n  service principals to use Power BI APIs\"* and the SP must be a workspace member.\n\nSee the [`examples/cli.md`](examples/cli.md#authenticate) auth walkthrough.\n\n## Requirements\n\n- Docker (Desktop or Engine).\n- A workspace on **Premium / PPU / Fabric** with the **XMLA endpoint enabled** (Admin portal →\n  Capacity settings → XMLA Endpoint = Read or Read/Write).\n- An Entra ID identity with access to the workspace.\n\n## How it works\n\n| Layer | Choice |\n|-------|--------|\n| Runtime | .NET 8 (LTS), Linux container |\n| XMLA client | `Microsoft.AnalysisServices.AdomdClient` 19.114 (managed, cross-platform) |\n| Auth | `Microsoft.Identity.Client` (MSAL) → injected via `AccessToken` |\n| CLI | `System.CommandLine` 2.0 |\n| Output | Spectre.Console (table), CSV (RFC 4180), JSON |\n\nMulti-stage build — the `sdk:8.0` stage restores + **tests** + publishes; the slim `runtime:8.0`\nstage ships only the app as a non-root user. Full design in\n[`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md).\n\n**Image security.** A scanner (e.g. `docker scout`) will flag CVEs on the image — these are\n**inherited from the .NET runtime's Debian base** (`perl`, `gnutls28`, …), **not** DAXter's code, and\nsit in paths a .NET app never executes. They clear when the base image is refreshed; a future\n[chiseled base](https://learn.microsoft.com/dotnet/core/docker/container-images) would remove them.\nDAXter's own layers add no critical/high vulnerabilities.\n\n## Limitations\n\n- **Model editing** (measures, parameters, RLS/OLS roles, calculated columns, **column properties**\n  — format / data type / sort-by / summarize-by / folder / hidden — partition M sources,\n  calculated tables, + raw TMSL) is supported — **dry-run by default**, behind a dedicated gate, with\n  a `.bim` backup before every apply. ⚠ Editing a Power BI Desktop–authored model over XMLA is\n  **irreversible for PBIX download** (keep your original .pbix) and requires the workspace XMLA\n  endpoint set to **Read/Write**.\n- The workspace must expose an XMLA endpoint (Premium/PPU/Fabric). Pro datasets don't (REST commands\n  like `ws` / `refresh history` still work on Pro).\n- TMSL refresh / `cache clear` need XMLA set to **Read/Write**; `refresh trigger` (REST) works with\n  read-only XMLA.\n- `test-rls` impersonation requires the connecting identity to be a workspace/model admin.\n- Tenant-wide audit (Scanner/Admin API) requires a Fabric admin identity — not included.\n\n## Contributing \u0026 development\n\nDocker-only — no local .NET SDK required. Build/test commands, repo layout, and conventions live in\n**[`CLAUDE.md`](CLAUDE.md)**; design in [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md); roadmap in\n[`docs/ROADMAP.md`](docs/ROADMAP.md).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdanlugo%2Fdaxter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdanlugo%2Fdaxter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdanlugo%2Fdaxter/lists"}