plugins/fabric-skills/skills/fabriciq/SKILL.md
Answer business questions by querying Power BI reports and dashboards through the FabricIQ MCP endpoint. Orchestrates: discover Power BI artifacts, inspect report/model schemas, resolve entity values, generate DAX, execute queries. Returns plain-language answers from Power BI semantic models. Use when the user asks a natural-language question about Power BI report or dashboard content (not raw DAX). Triggers: "ask power bi", "PBI question", "discover report", "report data", "dashboard data", "what are the top", "show me the power bi data", "which products sold", "compare sales in report".
npx skillsauth add microsoft/skills-for-fabric fabriciqInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Update Check — ONCE PER SESSION (mandatory) The first time this skill is used in a session, run the check-updates skill before proceeding.
- GitHub Copilot CLI / VS Code: invoke the
check-updatesskill.- Claude Code / Cowork / Cursor / Windsurf / Codex: compare local vs remote package.json version.
- Skip if the check was already performed earlier in this session.
CRITICAL NOTES
- To find artifact details (including artifact ID) from a search query: use
DiscoverArtifactswith the search term — do not call workspace/item list APIs- To find the semantic model behind a report: call
GetReportMetadataand extract the model GUID from the response- When the user provides a Power BI URL: call
ResolveReportIdFromUrlto get the correct report GUID before proceeding
⚠️ STOP — Read this entire skill document in full before taking any action. Do not begin orchestrating tool calls until you have read and internalized all sections below, including Workflow, DAX Rules, Verified Answers, and Error Recovery. Skipping ahead leads to incorrect queries and missed instructions.
You help users analyze Power BI data. You orchestrate each step: discover artifacts, inspect report and model schemas, resolve values, and execute queries. Uses the FabricIQ MCP server.
| Task | Reference | Notes |
|---|---|---|
| Fabric Topology & Key Concepts | COMMON-CORE.md § Fabric Topology & Key Concepts | Hierarchy; Finding Things in Fabric |
| Environment URLs | COMMON-CORE.md § Environment URLs | Production (Public Cloud) |
| Authentication & Token Acquisition | COMMON-CORE.md § Authentication & Token Acquisition | Wrong audience = 401; covers token audiences, delegated vs app permissions, OAuth flows, identity types, and Entra app registration |
| Authentication Recipes | COMMON-CLI.md § Authentication Recipes | az login flows, environment detection, token acquisition, and debugging |
| Gotchas, Best Practices & Troubleshooting | COMMON-CORE.md § Gotchas, Best Practices & Troubleshooting | Common Errors; Best Practices |
| Must/Prefer/Avoid | SKILL.md § Must/Prefer/Avoid | Guardrails for Power BI consumption |
| Workflow | SKILL.md § Workflow | FabricIQ orchestration steps |
| Tool | Purpose |
|------|---------|
| DiscoverArtifacts(searchQuery, artifactTypes?, maxResults?) | Search for Power BI reports and semantic models by free text. Call FIRST when the user has not provided an artifact GUID or Power BI URL. Maximum 50 results. Prefer reports over standalone semantic models |
| ResolveReportIdFromUrl(url) | Call when the user pastes a Power BI or Fabric URL whose report ID has not already been resolved. Required for workspace-App URLs (.../groups/me/apps/<appId>/reports/<reportId>) where the path-level reportId is the per-app instance ID, not the published-report GUID |
| GetReportMetadata(reportObjectId=<guid>) | Retrieve report pages, visuals, filters, workspace info. Supports optional queries parameter (JMESPath strings) to project a slim subset — pass queries only when a previous call returned an overview/summary instead of full data. On first call, omit queries to see complete metadata |
| GetSemanticModelSchema(artifactId=<guid>) | Retrieve table/column/measure definitions, relationships, custom AI instructions, and verified answers. Supports optional queries parameter (JMESPath). On first call, omit queries to see complete schema |
| ValueSearch(artifactId, searchTerms, scope?) | Call BEFORE writing a DAX filter on a named entity (customer, product, region, etc.). Returns the column + exact value to filter against so DAX does not guess canonical spelling |
| ExecuteQuery(artifactId=<guid>, daxQueries=[...], maxRows?) | Execute 1–4 DAX queries (one EVALUATE per entry) and return tabular results. Default 250 rows per query, max 1,000. Set maxRows if you need more than the default |
GetReportMetadata and GetSemanticModelSchema tool results in full before proceeding. Follow any instructions that these tools provide (e.g., CustomInstructions, VerifiedAnswers). Do not skip, skim, or partially read these results — they contain critical context for correct DAX generationDiscoverArtifacts unless you already have the artifact IDValueSearch to inform accurate DAX filtersIdentify the artifact —
ResolveReportIdFromUrl(url) unless the platform already pre-registered the artifact as [rpt_N] / [dataset_N] (in which case use that GUID directly). ResolveReportIdFromUrl is the only reliable way to map a workspace-App report URL to the underlying published-report GUIDDiscoverArtifacts(searchQuery=<keywords from user request>)DiscoverArtifacts with a broad term — tell the user the result is the top matches, not exhaustiveGetReportMetadata requires the Report GUID (reportObjectId), while GetSemanticModelSchema, ExecuteQuery, and ValueSearch require the Semantic Model GUID (artifactId). DiscoverArtifacts returns both artifact types with distinct IDs. When starting from a report, call GetReportMetadata first — its response includes the underlying semantic model ID in the semanticModel field, which you then pass to schema/query toolsInspect the report — If the artifact is a Report, call GetReportMetadata(reportObjectId=...) without the queries parameter to get the full response first. This gives you the complete picture of pages, visuals, bindings, and filters. Only use JMESPath queries on follow-up calls if the initial response was truncated or you need to drill into a specific slice. When querying report data, always apply report filters, page filters, and relevant visual filters in the DAX query by default. Do not skip any report-level filter — even if the referenced table or column does not appear in the schema (some tables are hidden but still required for correct filtering). Use TREATAS to apply such filters, e.g. if report metadata shows 'Budget'[Scenario] in ('Actual', 'Forecast') but Budget is not in the schema, apply: TREATAS({"Actual", "Forecast"}, 'Budget'[Scenario]). When the user's question explicitly contradicts a filter (e.g., the report is filtered to year=2022 and the user asks about 2023), override that filter on the conflicting dimension in your DAX, keep all other filters intact, and disclose the override in your answer. If the intent is ambiguous — the question could plausibly mean "I want a different slice" or "your report filter is wrong" — ask which the user wants before running the query.
Analyze schema — Call GetSemanticModelSchema(artifactId=...) without the queries parameter to get the full schema first. This gives you the complete picture of tables, columns, measures, relationships, CustomInstructions (domain-specific guidance from the model author — follow them), and VerifiedAnswers. Only use JMESPath queries on follow-up calls if the initial response was truncated (warning text or compact summary in the body) and you need to project a specific slice.
Check for verified answers — If the model's VerifiedAnswers contains an entry that closely matches the user's question, follow the Verified Answers rules below.
Resolve entity values — If the user names a concrete value (a specific customer, product, region, etc.), call ValueSearch(artifactId=<model guid>, searchTerms=[<value>]) against the semantic model before constructing your DAX filter.
Write DAX — Write DAX from the schema, scoped to the columns and measures used by the report's visuals when applicable. Prefer model-defined measures over ad-hoc CALCULATE.
Query — Call ExecuteQuery with daxQueries (1–4 entries). Run independent queries in parallel within the same call.
Verify — If a query returns BLANK or an unexpected empty result, inspect the schema, measures, and filters and retry at most once with corrected DAX.
Answer — Synthesize results into a clear answer with data citations. Lead with the finding, use bold for key numbers, format as text tables in terminal environments. Never mention DAX, schemas, or tool names. Refer to artifacts by name, not by ID.
When the user asks a follow-up about the same artifact:
ValueSearch againExecuteQuery and presentIf a DAX query returns blank, few rows, or unexpected totals:
ExecuteQuery| Error | Action | |-------|--------| | Invalid DAX | Read the error message, fix the DAX, retry once | | Unauthorized (no PBI access) | May be a real access issue (admin needs to enable Power BI MCP access), or a known limitation where the tool reports "no access" for artifacts it can't reach. Let the user know | | Throttled | Tell the user Power BI is rate-limited; try again shortly | | Row/value limit exceeded | Data is truncated but usable. Suggest aggregating instead of dumping raw rows | | Feature not enabled | The PBI MCP endpoint may not be enabled on the tenant. Ask the user to contact their admin | | Timeout | The semantic model may be cold-loading. Retry once. If it times out again, suggest the user retry in a few minutes |
Power BI reports and semantic models only. Paginated reports, dashboards, and any other Power BI or Fabric artifact type are not currently supported. If the user points at an unsupported artifact, say so and suggest a report or semantic model instead.
When the semantic model contains verified answers, and one matches the user's question:
schema.VerifiedAnswers[?regex_match(Question, 'keyword')] | [0]Bindings object maps visual roles (Rows, Category, Columns, Values, Y, Series, Breakdown, etc.) to columns and measures — the list of keys is not exhaustive. Cross-reference each binding item with the schema to classify it as a column or measure.Filters as a SEPARATE SUMMARIZECOLUMNS filter argument. Do not skip any filter. Translate each filter as:
• Positive IN: TREATAS({values}, table[col])
• All other conditions (NOT IN, NOT NULL, IS BLANK, ranges): KEEPFILTERS(FILTER(ALL(table[col]), <condition>))
• Multi-column tuple filters: KEEPFILTERS(FILTER(ALL(table[col1], table[col2]), <condition>))
Never combine multiple column filters into a single FILTER('table', ...) — this causes incorrect grand totals due to auto-exist.IsLevel1Subtotal DESC, IsLevel2Subtotal DESC, …) then by the primary value measure DESC within each level. This ensures subtotals appear before leaf rows and survive truncation.
If the hierarchy has 4+ grouping levels, consider bounding the detail query to the top 3–4 levels or using TOPN per level to keep the result within row limits. Call both queries in parallel.Verified answer definitions take precedence over Custom Instructions. When a verified answer is matched, its bindings, filters, and granularity are the single source of truth. Do not add, remove, or override any filters based on Custom Instructions (e.g., do not add default time-scope filters that the VA omits). The VA was authored with knowledge of the Custom Instructions and intentionally defines its own filter context.
Important: Use JMESPath
queriesonly on follow-up calls — after the initial call withoutquerieshas returned a full or truncated response. Never skip the initial full call in favor of a targeted JMESPath query.
| Purpose | Query |
|---------|-------|
| Overview of pages and visual titles | queries=["ReportMetadata.Pages \| { PageCount: length(@), Pages: @[0:20].{ Page: Title, VisualCount: length(Visuals), VisualTitles: Visuals[].Title } }"] |
| Search visuals by keyword | queries=["ReportMetadata.Pages[].Visuals[?regex_match(to_string(@), 'revenue\|sales')] \| [] \| [:10]"] |
| Extract report and page filters | queries=["{ ReportFilters: ReportMetadata.Filters, PageFilters: ReportMetadata.Pages[?Title == 'PAGE_TITLE'] \| [0].Filters }"] |
| Find report-defined measures | queries=["ReportMetadata.Measures[?regex_match(to_string(@), 'revenue\|target')] \| [0:10]"] |
| Purpose | Query |
|---------|-------|
| Search measures by keyword | queries=["schema.Tables[].Measures[?regex_match(to_string(@), 'revenue\|sales')].{Name: Name, Expression: Expression} \| []"] |
| Get table details | queries=["schema.Tables[?Name == 'TABLE_NAME'].{Columns: Columns[].{Name: Name, Type: Type}, Measures: Measures[].{Name: Name, Expression: Expression}} \| [0]"] |
| Search verified answers | queries=["schema.VerifiedAnswers[?regex_match(to_string(@), 'revenue\|sales')] \| [:5].{Title: Title, Question: Question}"] |
| List all relationships | queries=["schema.ActiveRelationships[].{PK: PK, FK: FK}"] |
When writing DAX queries, follow these strict rules:
EVALUATE statement per query — never multipleORDER BY clause when EVALUATE returns multiple rowsORDERBY function to sort the final query resultDEFINE at the beginning if the query includes VAR, MEASURE, COLUMN, or TABLE definitions before EVALUATEDEFINE, use only a single DEFINE block. Separate definitions by newline without commas or semicolonsDEFINE MEASURE 'TableName'[MeasureName] = ...). The host table must exist in the semantic model[MeasureName])CALCULATE function — use a variable to store the result firstIN operator, the table operand must be a table variable, not a table expressionVAR definitionSUMMARIZE instead)SUMMARIZE(<table expression>, <column1>, ..., <columnN>)SUMMARIZECOLUMNS insteadVALUES('Table'[Column])[Column] (not _TableVar[Column])CURRENTGROUP is valid ONLY within GROUPBYTOPN, ORDER BY) must use the new column namesORDER BY, FILTER) within table expressions like SELECTCOLUMNS or CALCULATETABLEINTERSECT, UNION, EXCEPT): both input tables must have identical column countsROW with time intelligence calculations, supply external filters through CALCULATETABLE to establish a clear "current date" referenceMAX('Calendar'[Date]) alone — it may return a future date. Use LASTNONBLANK or filter with [Measure] <> BLANK() before ordering by dateGetReportMetadata and redefine it inline with DEFINE MEASURE. Referencing a report-only measure by name from a DAX query will fail.TOPN for ranking. Default to at most 50 rows unless the user asks for moreUser: "What are the top 5 products by revenue in the Sales Benchmark report?"
Agent steps:
DiscoverArtifacts with searchQuery: "Sales Benchmark"ArtifactIdGetReportMetadata with the report IDGetSemanticModelSchema with the artifact ID — check verified answers and custom instructionsExecuteQuery with the artifact ID and DAX queryUser: "Now break this down by region"
Agent steps:
ExecuteQuery with the artifact ID and new DAX queryUser: "What are the sales for Terra Firma in the budget scenario?"
Agent steps:
ValueSearch with artifact ID and searchTerms: ["Terra Firma", "Budget"]ExecuteQuery with the artifact ID and DAX querydevelopment
Build a guided requirements-to-implementation workflow for new Power BI reports and dashboards from semantic models, datasets, or PBIP projects. Use when the user wants to: (1) plan then implement a report, (2) define audience, scope, page plan, design direction, dependencies, and delivery target, (3) create a locked report spec with approval before PBIR authoring. For direct edits to existing report files, use `powerbi-report-authoring`. For design-only critique or redesign, use `powerbi-report-design`. Triggers: "build me a dashboard", "create a new report", "plan then implement", "define and build Power BI report", "walk me through creating a report".
tools
Manage Power BI report workspace items in Microsoft Fabric via `az rest` CLI against the Fabric REST API. Use when the user wants to: (1) create reports from PBIR definitions, (2) get or download report definitions, (3) update report definitions or properties, (4) list workspace reports, (5) delete reports. For report layout authoring (pages, visuals, filters, formatting), use `powerbi-report-authoring`. Triggers: upload Power BI report, download PBIR definition, publish Power BI report to Fabric, manage Power BI reports.
development
Generate Power BI report visual design guidance before PBIR files are written. Use when the user wants to: (1) choose tone, signature, page archetypes, chart types, layout, color, typography, theme direction, or accessibility approach, (2) redesign/restyle an existing report, apply a brand, or critique chart/layout choices, (3) produce a design contract for `powerbi-report-authoring`. For end-to-end requirements, approval, and build sequencing, use `powerbi-report-planning`. Triggers: "design Power BI report", "make dashboard look professional", "choose chart type", "apply brand to report", "redesign report", "create design brief".
tools
Create and modify Power BI report files in PBIR/PBIP format using the `powerbi-report-author` and `powerbi-desktop` CLIs. Use when the user wants to: (1) implement an approved report spec or design brief, (2) add or edit pages, visuals, filters, slicers, bookmarks, themes, or formatting, (3) validate PBIR and verify rendering in Power BI Desktop. For open-ended visual design, use `powerbi-report-design` first. For end-to-end requirements and approval workflow, use `powerbi-report-planning` first. Triggers: "edit PBIR", "create Power BI report page", "add visual to PBIP", "format report visual", "validate Power BI report", "reload Desktop screenshot", "implement an approved PBIP report spec", "edit PBIR pages/visuals".