Skip to main content
Glama
vilsem-mcp
by bcsnpc
README.md
# vilsem-mcp

Any visual, from a chat, into your existing Power BI report, with real data, with no executable code in the report.

vilsem-mcp doesn't make charts - Claude Code and Codex make charts; vilsem-mcp gives them your model, real data, eyes, and a safe pen. The MCP contains no model and needs no LLM API key. The calling agent's own service/account may have its own costs.

This is the custom-visual layer for agentic Power BI authoring. Use it alongside Microsoft's Power BI Report skill for native visuals and the Power BI Modeling MCP for model changes. The shared, installed renderer executes trusted application code; the authored spec is declarative JSON, not executable source.

## Why not just ask Claude Code / Codex directly?

For a custom visual outside Power BI's native catalog:

| Work | Agent without this custom-visual layer | With vilsem-mcp |
| --- | --- | --- |
| Understand the model | Find a connector, inspect fields and recover calculation context | get_schema summary/full with calculations, statistics and relationship context |
| Choose an analytical design | Work out question, grain, units and alternatives | Still the agent's decision; design_visual and design-guide supply a structured brief |
| Use real numbers | Implement a query bridge and field normalization | Read-only Desktop/Fabric DAX and actual field bindings |
| See and improve a draft | Build a renderer and screenshot pipeline | preview_visual, critique and shared-data preview_variants |
| Put it in a report | Implement/package/import a custom visual and configure queries | commit_visual writes one supported custom visual and missing renderer registration |
| Edit the visual a month later, in a new chat | Find the old source/chat, re-explain, rebuild, repackage, re-import and rebind | Read the report's spec: "make the Sankey labels bigger"; preview a small patch |
| What runs inside the report | Each separately authored compiled visual's implementation | One shared renderer plus plain declarative specifications |
| Security review | Review each executable implementation and its dependencies | Review the shared renderer/dependencies and the bounded format; report policies still apply |

The comparison does not imply native Power BI visuals require custom coding. Microsoft's companion Report skill handles those.

## Three authoring layers

1. **Composition:** a supported chart type and configuration; the smallest spec and maintenance cost.
2. **Template:** ECharts structure, declared slots, parameters and bounded transforms; more control, with explicit layout and unit responsibilities.
3. **Expression series:** the Round3 GroupC bounded-geometry language is planned, **not available in0.6 yet**. Arbitrary renderItem/JavaScript remains refused; see the expressiveness gaps.

## Agent workflow

connect_model -> get_schema -> design_visual prompt -> validate_spec -> explain_visual -> preview_visual with critique -> compare variants/iterate -> list_pages -> commit_visual -> Desktop reload and native checks. **Never commit without a preview.** Read vilsem://docs/design-guide and vilsem://docs/workflow.

For edits: list_visuals -> read_visual -> update_visual(dryRun:true) -> inspect before/after and confirm when not already authorized -> update_visual(dryRun:false, etag) -> reload and verify. The report folder is the source of truth. Manual format-pane values win over authored formatting and survive updates unless resetFormatting:true is explicit.

## Install

Requires Node.js 22.12+, 24, or 26+. Install from GitHub or clone this repository and run `npm install`, then `npm test`. Its `prepare` step builds the server. All model connections run through `@vilsem/core/connect`; no Vilsem app checkout, Electron installation or application path configuration is needed.

The dependency is the immutable public `core-v0.6.0` release tarball. For local core development only, install a locally packed core tarball; restore the public release URL before distributing.

### Model authentication

Desktop requires Windows and a running Power BI Desktop model. Core identifies the Desktop Analysis Services process and reads its workspace port file. Pass `source.port` if multiple instances are open. ADOMD/XMLA uses your Windows identity; no cloud login is needed.

For Fabric, configure these environment variables on the MCP server process:

- Service principal: `AUTH_MODE=service_principal`, `FABRIC_TENANT_ID`, `FABRIC_CLIENT_ID`, `FABRIC_CLIENT_SECRET`.
- User login: `AUTH_MODE=device_code` (default). Login instructions go to stderr; complete Microsoft's device-code login in your browser. Only the refresh token is cached under `~/.vilsem/auth/`, scoped to tenant/client/resource, in a 0700 directory and atomic 0600 file. Windows uses the user-profile ACLs; POSIX modes are not Windows ACLs.
- Optional `FABRIC_ACCESS_TOKEN` for an externally supplied short-lived token. Refresh requires another configured credential flow.

No credentials are read from another repository's `.env` or Electron workspace store. Set credentials in your MCP client's server environment. Core also preserves `setInteractiveTokens` for Electron PKCE login.

### Connect from an existing report binding

Call `connect_model` with `source:{kind:"report",reportDir:"/path/to/Example.Report"}` to follow that report's `definition.pbir` without changing it:

- `datasetReference.byPath`: core matches the local semantic-model path to an open Desktop session, even when another model is open. With Desktop closed it reads the local TMDL or BIM definitions and returns `schemaOnly:true`. Schema-only connections cannot run DAX or preview live values; open the model and reconnect first.
- `datasetReference.byConnection`: a Power BI XMLA URL identifies the workspace and semantic model. Core resolves them through the configured Fabric credentials and uses the existing Fabric query/introspection adapter. It does not try to discover a local Desktop port. The connecting identity needs access to list the workspace and model, execute queries and inspect its definition. Windows-integrated SSAS server bindings use the local ADOMD bridge and Windows identity; cloud credentials are never forwarded to arbitrary servers.

Quoted connection-string values are supported. Passwords and impersonation settings embedded in the report are ignored; configure authentication on the MCP server. Missing or ambiguous targets fail explicitly. `commit_visual` still writes only the Vilsem visual in the selected local report; it does not change the binding, semantic model, native visuals or report policies.

There is no npm-registry publication. Install the tagged GitHub release:

```sh
npm install -g git+https://github.com/bcsnpc/vilsem-mcp.git#v0.6.0
vilsem-mcp
# Or: npx --yes --package=git+https://github.com/bcsnpc/vilsem-mcp.git#v0.6.0 vilsem-mcp
```

The process serves stdio JSON-RPC; diagnostics go to stderr.

### Claude Desktop

Add to `claude_desktop_config.json`, replacing the paths:

```json
{"mcpServers":{"vilsem":{"command":"node","args":["D:/vilsem-mcp/dist/index.js"]}}}
```

### Claude Code

```sh
claude mcp add --transport stdio vilsem -- node D:/vilsem-mcp/dist/index.js
```

### Codex

Add to `~/.codex/config.toml` or a trusted project's `.codex/config.toml`:

```toml
[mcp_servers.vilsem]
command = "node"
args = ["D:/vilsem-mcp/dist/index.js"]

```

See the [official MCP configuration documentation](https://learn.chatgpt.com/docs/extend/mcp).

## Desktop setup and walkthrough

In Desktop, open File → Options and settings → Options → Preview features. Enable **“Detect and reload external PBIP changes”** and restart Desktop. Microsoft documents this exact preview feature in [external project editing](https://learn.microsoft.com/en-us/power-bi/developer/projects/projects-external-editing).

1. Open your semantic model in Desktop, then call `connect_model` with `source:{kind:"desktop"}`; provide a port if more than one engine is open. For Fabric, provide workspaceId and datasetId.
2. Call `get_schema` and choose existing dimensions/measures, or declare query-only `spec.measures`. With scoped measures pass `connectionId` to validation, explanation and commit as well as preview. Use `run_dax` to verify the data.
3. Call `list_templates`, read `vilsem://docs/spec-format`, then `validate_spec`, `explain_visual` and `preview_visual`. Check the PNG and option before writing.
4. Call `list_pages`, choose a position without overlap, then `commit_visual` with the report folder, page folder name, a new visualName, spec, bindings and layout.
5. Accept Desktop's external-change reload prompt. Avoid saving stale Desktop state over external changes. Microsoft excludes report-level `report.json` changes from live reload: first-time custom-visual registration can require reopening the project. For visual-only reload, first import the renderer package in Desktop and save the project, then commit a visual.
6. Inspect the rendered result and call `validate_visuals`. Save the report from Desktop when satisfied.

## Tools and resources

Tools: connect_model, get_schema, run_dax, list_templates, validate_spec, explain_visual, preview_visual, preview_variants, list_pages, list_visuals, read_visual, diff_visual, update_visual, commit_visual, validate_visuals. Prompt: design_visual. Resources: vilsem://templates/<template-id>, vilsem://docs/spec-format, vilsem://docs/design-guide and vilsem://docs/workflow.

get_schema defaults to summary (no expressions or relationship graph); use full for business calculation and join semantics. Exact cardinality/BLANK share/ranges are cached60seconds with at most10samples. Permission/provider failures are explicit; internal engine row identities are skipped. page/reportDir adds read-only page query bindings without editing or validating native visuals.

Preview critique checks measured SVG text bounds, ellipses, axis rotation, categorical palette size, null/zero series, empty required slots, missing formats, page-background text contrast, legend/plot intersections, pie slice count and dual-axis review. It includes actionable paths/fixes and explicit coverage limits. It is not analytical correctness, accessibility or host-interaction certification. preview_variants makes one identical data query for up to4candidate specs; scoped definitions must match.

read_visual returns the migrated spec, bindings, pane objects, layout, source-version provenance, etag and live PNG. diff_visual is read-only. update_visual accepts RFC6902operations, defaults to dry run, protects identity/prototype/version paths and checks intervening file changes. It preserves field queries for styling-only edits and refuses implicit rebinding. A legacy inline measure with no stored owning-table metadata returns table:null; the temporary preview namespace is disclosed, not invented as original provenance.

`list_pages` reads all visual positions to help avoid overlap. `validate_visuals` examines only the stable Vilsem Renderer GUID; it does not report native visual validation results. `commit_visual` adds one new visual, rejects collisions and updates only missing renderer resources/registration. It does not rebuild or serialize other report content.

## Interactions

Rendered Vilsem ECharts visuals cross-filter through Power BI selection IDs, accept cross-highlights, support drill-through/context menu requests, native tooltips and bookmark selection restoration. Those host interaction contracts have automated harness tests in Vilsem. Availability depends on bound field identities and report configuration; report-page tooltip resolution, drill-through destinations and bookmarks require testing in the actual report. A PNG preview is static and does not verify these interactions. DOM-adapter visuals and every possible template transform have not been certified for interaction parity.

## What this does NOT do

- Create, edit or validate native Power BI visuals.
- Create or edit slicers, filters, bookmarks or themes.
- Change semantic models or model metadata.
- Persist measures to the model; declare a visual-scoped measure instead, or use Microsoft's Power BI Modeling MCP to add one to the model.
- Read or write PBIX files. Existing enhanced PBIR report folders are required.

Desktop connections require Windows and Power BI Desktop. Column sampling can be expensive on large models and permission failures are returned per column.

Headless previews support the core ECharts composition types and declarative templates. DOM-adapter visual types are rejected by this resolver rather than emitted as empty or substituted charts. Previews render the chart marks and option; renderer-owned card chrome/title headers and existing report-level filters are not replayed. Fonts can differ between server PNGs and Desktop if the machines have different installed fonts. Preview queries and arbitrary DAX default to200 rows and accept `maxRows` up to10000. Choose a complete bounded window for population statistics; Desktop's categorical host reduction is2000 rows.

## Development and verification

Run `npm run build` and `npm test`. Tests cover spec resolution, deterministic SVG/PNG rendering, custom-only PBIR insertion/validation and a real stdio client/server round trip. Live Fabric credentials, Desktop query access, reload prompts and host interactions need environment-backed acceptance; passing fixture tests alone does not establish those results.

For an independent test, follow [author a visual from scratch](docs/author-from-scratch.md).

## Stress round2 examples (0.4.0)

111 freshly authored stress examples passed the actual validate/explain/preview/commit
workflow and individual Desktop rendering inspection at900x600. Nine capabilities
remain explicit refusals, documented with sandbox/asset requirements. This is not
exhaustive formatting, resize, cloud-target or interaction certification.
See the [120-case results](https://github.com/bcsnpc/vilsem/blob/main/docs/qa/stress2/results.md)
and [format additions](https://github.com/bcsnpc/vilsem/blob/main/docs/qa/stress2/format-additions.md).

Use core0.6.0 and the bundled renderer4.35. Existing reports keep the same GUID
and primitiveSpec property. New features enforce renderer version floors rather
than silently replacing an existing report resource. `list_templates` retains
the template contract and includes `exampleSpec`: use that complete envelope to
preserve query-only `measures`, theme and instance parameters.

The catalog includes the nine original examples below plus111 stress examples.
Failed authored probes are kept under `docs/expressiveness/`, never listed as
working templates. Word packing, swarm and partition label bounds require their
declared fixed plot dimensions; density/cohort/statistical assumptions and limits
are documented in `vilsem://docs/spec-format`.

## Original expressiveness examples (0.3.0)

Nine new standalone `.vilsem-visual.json` specs ship in `templates/` and are listed by `list_templates`. They cover Sankey, Gantt, running-total waterfall, sunburst, bullet, radial progress, dual-axis reference measures, facets and a relationship network. Bind their declared slots to your own schema; no model data is embedded. Use core 0.3.0 / renderer 4.14.2.0+. The [expressiveness report](docs/expressiveness-gaps.md) records the live workflow, bounds and the deliberately unsupported Marimekko custom callback.

### Theme, formatting and preview locale

Visual backgrounds default to transparent and use the report/host foreground and
palette. `canvasTheme` is an explicit override. `preview_visual` accepts
`theme:"report"|"light"|"dark"|<theme JSON>`; a supplied reportDir selects the
referenced registered theme by default. Desktop's Appearance pane can override
the background/transparency or force report-theme inheritance.

Directly bound model and query-scoped measure formats apply to labels, value axes and
native tooltips, including heatmap cell labels and bound-field tooltips. Percent, grouping, currency, decimal precision, comma scaling
and date patterns use the same bounded converter in core. This is a supported
subset of Excel/Power BI formatting, not a claim to implement every conditional,
scientific or dynamic model-format expression.

Derived counts, densities, coordinates and ratios can have different units from
their input measure. They require an authored formatter when units change;
the renderer does not blindly apply the source currency/percent format to them.
SSR can read the report JSON's font family. Automatic inheritance of an arbitrary
report font in Desktop is not yet verified; the current native default is Segoe UI.

Desktop uses the host viewer locale. SSR defaults to `locale:"en-US"`; pass the
viewer locale (for example `"en-IN"` or `"de-DE"`) to preview_visual when matching
regional grouping, decimal separators or month names. The result reports
formattingLocale. A report's model culture alone does not determine each
viewer's regional display settings. Values and calculations are unchanged.

`preview_visual.interactions` describes supported host routes and known limits;
it is not evidence that SSR exercised native selection, report-page tooltips or
bookmarks. Native acceptance is recorded separately in the QA interaction log.

### Deterministic template thumbnails

`npm run thumbnails` rebuilds all shipped light-theme example PNGs using the same
synthetic fixtures as the rendering regressions. `node scripts/render-thumbnails.mjs
--dark` checks the alternative host background. Each theme directory under
`templates/thumbnails/` includes a manifest with source/PNG hashes and failures.
These thumbnails contain synthetic demonstration values, not model/client data.


### PNG typography

The isolated PNG worker expands the pixel-font shorthand emitted by ECharts to
CSS longhand before rasterizing. This preserves requested font sizes, weight and
style in previews (including large KPI callouts). Unknown shorthand is left
unchanged; literal chart text is never rewritten. Installed fonts still determine
font fallback. The SVG option and native chart typography are unchanged. The adapter workaround
addresses [resvg-js font shorthand support](https://github.com/thx/resvg-js/issues/404).


Default plain treemap labels use padded top-left placement and black/white text
chosen from the actual mapped opaque node fill, including rgba colours with
alpha1. Explicit label colours, authored positions/alignment and existing rich
markup retain their authored treatment. Unknown/translucent paints require an
explicit readable text style; no automatic contrast guarantee is claimed for them.

### Core0.5 native verification limits

Light/dark native rendering, percentage host tooltips, full currency labels and
plain treemap headers were checked against the owned AdventureWorks QA report.
The complete source suites pass. Physical mouse Ctrl-click and the final
stale-selection-to-slicer transition still need manual verification: Windows
Computer Use lost its native pipe and could not recover after retry/reset.
Keyboard additive selection and the other documented native interactions passed;
Ctrl/clear/highlight/stale-identity paths also have host regression coverage.
These are explicit verification limits, not claims that every native interaction,
report font, Service/Fabric target or historical screenshot was retested.


## Editable specifications and governance

New commits carry specVersion0.6; readers migrate specs from0.1 onward, including round2 string measure definitions into {expression,format} objects. version:1 remains the separate wire envelope. Migrations are pure, forward-only and tested; unknown future versions are explicitly rejected. Maintaining readable historical specs is a release requirement, not a guarantee that unknown future formats run in an older renderer.

The shared formatting contract has69properties: theme/background, title, chart text, series palette, labels, legend, category/primary/secondary axes, lines and border. Author spec.formatting or patch individual properties; separate Desktop pane objects override only the corresponding properties. Changing secondary-axis percentage formatting preserves a primary currency axis. Common controls apply to ECharts compositions/templates; legacy DOM adapters currently expose only Appearance, Title and Border. Native pane controls do not yet cover every specialized ECharts property. Series palette input currently accepts comma-separated hex colours.

Visual-scoped measures: **yes**. Persistent model measures/changes: **via Microsoft's Power BI Modeling MCP**. Queries and committed bindings use their respective connecting/viewing identities; Power BI owns RLS, workspace access, sensitivity labels, report filters and refresh. A preview query does not replay report-level filters or certify policies.

The write flow stores bindings/specifications, not returned model rows. Author literals/annotations are possible in JSON, so review specs before sharing. The plain spec lives in PBIR and can be versioned with the report in git; ownership belongs to the report, rather than a lost chat or developer's compiled project. An approved shared VILSEM_TEMPLATES_DIR library is a GroupC feature, not shipped in0.6. The MCP does not add a second manifest or list_recent tool.

Core0.6 and MCP0.6 verification is recorded in the [Round3 GroupB log](https://github.com/bcsnpc/vilsem/blob/main/docs/qa/stress3/group-b-status.md). Source/host and real-data SSR editing pass; final native Sankey/manual-pane acceptance is UNVERIFIED because ComputerUse cannot connect and Desktop snapshots are blank/time out. Automatic common-axis bounds can be reset with autoMin/autoMax. Native aggregate-column bindings outside the supported Measure/NativeMeasure shapes are reported as unsupported rather than guessed.


Contrast feedback uses4.5for normal text and3for text estimated at24px or larger, informed by [W3C contrast guidance](https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum.html). It deliberately does not relax the threshold for smaller bold text, and it compares against the preview page rather than proving the paint behind every label. This is review assistance, not a WCAG compliance result.

TDQS

A3.7/5.0

Scored across 15 tools

Disambiguation4/5

Most tools have clearly distinct roles: list_pages/list_visuals/read_visual separate discovery vs full read, update_visual vs commit_visual separate modify vs add. However, several near-pairs require careful reading: validate_visuals (report-level PBIR scan) vs validate_spec (spec without data), and explain_visual vs preview_visual (both resolve a spec, one without render). Descriptions do disambiguate these, but the surface is dense enough that misselection is plausible.

Naming Consistency5/5

Every tool follows a strict verb_noun snake_case pattern (list_templates, run_dax, validate_visuals, connect_model, get_schema, preview_visual, diff_visual, update_visual, commit_visual). No mixing of styles or vague verbs; the verb choice meaningfully signals the operation type (list/get/read vs validate vs preview vs update/commit).

Tool Count5/5

15 tools is at the top of the well-scoped range and each earns its place in a genuine authoring workflow: connection, schema, DAX, discovery, validation, explanation, preview, variant comparison, read, diff, update, and commit. No filler tools are visible and the count reflects a coherent multi-stage pipeline rather than sprawl.

Completeness4/5

The lifecycle is nearly full: connect, inspect, validate, explain, preview, compare, list, read, diff, patch, and commit are all present. Gaps remain around destructive/report-structuring operations — there is no delete_visual to remove a committed visual and no page-creation tool, only add-to-existing-page via commit_visual — which agents may need to work around.

Maintenance

ActivityMaintained
ResponsivenessNo recent community issues