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 querytools
Execute raw DAX queries and inspect metadata of Microsoft Fabric Power BI semantic models via the MCP server ExecuteQuery tool. Use when the user already knows the DAX to write, wants to run EVALUATE statements, or needs to inspect model metadata (tables, columns, measures, relationships, hierarchies) using INFO functions. For natural-language business questions (where you generate the DAX), use `fabriciq`. For creating, deploying, or managing semantic model definitions, use `semantic-model-authoring`. Triggers: "run DAX query", "execute EVALUATE", "semantic model metadata", "list semantic model tables", "INFO.VIEW.TABLES", "get measure expression", "DAX against", "query the model".
development
Develops and manages Power BI semantic models across Desktop, PBIP projects, and Fabric Service. Handles: (1) creating new models (Import, DirectQuery, Direct Lake), (2) editing existing models (e.g. measures, tables, columns, relationships), (3) deploying models to Fabric workspaces, (4) working with PBIP project files, (5) refreshing semantic models, (6) configuring data sources and permissions, (7) DAX performance optimization. Supports both Power BI Desktop and Fabric Service development workflows. For read-only DAX queries, use `semantic-model-consumption`. Does NOT handle report layout/visual authoring, workspace administration, or RLS/OLS role membership management. Triggers: "create semantic model", "edit semantic model", "add a DAX measure to semantic model", "refresh semantic model", "set semantic model permissions", "Prepare semantic model for AI/Copilot".
development
Develops and manages Power BI semantic models across Desktop, PBIP projects, and Fabric Service. Handles: (1) creating new models (Import, DirectQuery, Direct Lake), (2) editing existing models (e.g. measures, tables, columns, relationships), (3) deploying models to Fabric workspaces, (4) working with PBIP project files, (5) refreshing semantic models, (6) configuring data sources and permissions, (7) DAX performance optimization. Supports both Power BI Desktop and Fabric Service development workflows. For read-only DAX queries, use `semantic-model-consumption`. Does NOT handle report layout/visual authoring, workspace administration, or RLS/OLS role membership management. Triggers: "create semantic model", "edit semantic model", "add a DAX measure to semantic model", "refresh semantic model", "set semantic model permissions", "Prepare semantic model for AI/Copilot".
testing
Check for skills-for-fabric marketplace updates at session start. Compares local version against GitHub releases and shows changelog if updates are available. Use when the user wants to: (1) check for skill updates, (2) see what's new in skills-for-fabric, (3) verify current version. Triggers: "check for updates", "am I up to date", "what version", "update skills", "show changelog".