Official agent skill

Kql

by microsoft in microsoft/fabric-rti-mcp

KQL language expertise for writing correct, efficient Kusto queries using the Fabric RTI MCP tools.

OfficialMITAuto-check passedData & Analytics

Install Kql

skills CLI
$ npx skills add microsoft/fabric-rti-mcp --skill kql -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install microsoft/fabric-rti-mcp kql --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/microsoft/fabric-rti-mcp.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.github/skills/kql .claude/skills/kql && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
kql
GitHub stars
131
Token cost
~6.2k tokens
SKILL.md length
1,897 words
Files
5 (incl. references)
Skills in repo
1
Repo updated
First seen
Licence
MIT

At a glance

KQL language expertise for writing correct, efficient Kusto queries using the Fabric RTI MCP tools.

  • Works in 12 steps: Running KQL with Fabric RTI MCP → Dynamic Type Discipline → Join Patterns & Pitfalls → …
  • Azure Data Explorer
  • SKILL.md covers 1. Running KQL with Fabric RTI…, 2. Dynamic Type Discipline, 3. Join Patterns & Pitfalls and 4. Regex in KQL, plus 7 more sections
  • Reaches help.kusto.windows.net

What it does

Kql is an agent skill from microsoft/fabric-rti-mcp, published by the product's own GitHub organization. KQL language expertise for writing correct, efficient Kusto queries using the Fabric RTI MCP tools. Covers syntax gotchas, join patterns, dynamic types, datetime pitfalls, regex patterns, serialization, memory management, result-size discipline, and advanced functions (geo, vector, graph). USE THIS SKILL whenever writing, debugging, or reviewing KQL queries — even simple ones — because the gotchas section prevents the most common errors that waste tool calls and cause expensive retry cascades. Trigger on: KQL…

Its SKILL.md is about 6.2k tokens, which your agent loads only when the skill is triggered. The skill folder holds 5 other files, including reference files (for example `references/advanced-patterns.md`, `references/discovery-queries.md` and `references/error-recovery.md`).

It sits in Data & Analytics, covering MCP servers, Data analysis and Forecasting and time series. It works with Microsoft Sentinel and Microsoft Azure. The repository describes itself as: MCP server for Fabric Real-Time Intelligence (https://aka.ms/fabricrti) supporting tools for Eventhouse (https://aka.ms/eventhouse), Azure Data Explorer (https://aka.ms/adx, and…. The licence is MIT.

When your agent uses it

  • Azure Data Explorer
  • Fabric Eventhouse
  • Data exploration
  • Anomaly detection

Example prompts

  • “/kql”

Requirements

  • Python 3

Workflow steps

12 steps, taken from the step headings in SKILL.md.

  1. Running KQL with Fabric RTI MCP
  2. Dynamic Type Discipline
  3. Join Patterns & Pitfalls
  4. Regex in KQL
  5. Serialization Requirements
  6. Memory-Safe Query Patterns
  7. Result Size Discipline
  8. String Comparison Strictness
  9. Advanced Functions
  10. Self-Correction Lookup Table
  11. Datetime Pitfalls
  12. Operator Naming & Equality

What it can do on your machine

Read from SKILL.md and the folder at commit 3c765f9. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    No scripts in the folder and no shell commands in SKILL.md (its code samples are kql).

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    Hosts in commands or code, which the agent is likely to contact:

    • help.kusto.windows.net

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

Kql loads about 6.2k tokens when it runs, and up to ~18k if it reads all its reference files. Until then it costs about 194 tokens; SKILL.md has 1,897 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~194
When it runs · the whole SKILL.md, loaded when a task matches
~6.2k
With references · SKILL.md plus every file in references/, read only if the agent opens them
~18k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check passed

The automated check found no risky patterns in SKILL.md.

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from microsoft/fabric-rti-mcp at commit 3c765f9, republished under its MIT licence (© microsoft). 1,897 words, ~6,192 tokens.

Download SKILL.mdSave it as .claude/skills/kql/SKILL.md (or your agent's skills folder). This skill also uses 4 other files; get the full folder from GitHub.
name
kql
description
KQL language expertise for writing correct, efficient Kusto queries using the Fabric RTI MCP tools. Covers syntax gotchas, join patterns, dynamic types, datetime pitfalls, regex patterns, serialization, memory management, result-size discipline, and advanced functions (geo, vector, graph). USE THIS SKILL whenever writing, debugging, or reviewing KQL queries — even simple ones — because the gotchas section prevents the most common errors that waste tool calls and cause expensive retry cascades. Trigger on: KQL, Kusto, ADX, Azure Data Explorer, Fabric Eventhouse, log analysis, data exploration, time series, anomaly detection, summarize, where clause, join, extend, project, let statement, parse operator, extract function, any mention of pipe-forward query syntax.

KQL Mastery

Try it yourself: All ✅ examples in this skill can be run against the public help cluster: https://help.kusto.windows.net, database Samples (contains StormEvents, SimpleGraph_Nodes/Edges, nyc_taxi, and more).

1. Running KQL with Fabric RTI MCP

Fabric RTI MCP exposes Kusto functionality as MCP tools. Authentication is handled transparently using Azure Identity.

Available tools
ToolPurpose
kusto_queryExecute a KQL query on a database
kusto_commandExecute a management command (.show, .create, etc.)
kusto_list_entitiesList databases, tables, external tables, materialized views, functions, graphs
kusto_describe_databaseGet schema for all entities in a database
kusto_describe_database_entityGet schema for a specific entity (table, function, etc.)
kusto_sample_entityGet sample data from a table or other entity
kusto_graph_queryExecute a graph query using snapshots or transient graphs
kusto_ingest_inline_into_tableIngest inline CSV data into a table
kusto_known_servicesList configured Kusto services
kusto_get_shotsRetrieve semantically similar shots from a shots table
kusto_deeplink_from_queryBuild a deeplink URL to open a query in the web explorer
kusto_show_queryplanGet the execution plan for a query without running it
kusto_diagnosticsGet a best-effort cluster health and capacity summary
Query vs management commands

KQL has two execution planes, each with its own MCP tool:

PlaneToolStarts withExamples
Querykusto_queryTable name, let, print, datatableStormEvents | where State == "TEXAS"
Managementkusto_command.show, .create, .set, .drop, .alter.show tables, .show table T schema
Basic usage
# Query plane — use kusto_query
kusto_query(
    cluster_uri="https://help.kusto.windows.net",
    database="Samples",
    query="StormEvents | summarize count() by EventType | top 5 by count_ desc"
)

# Management plane — use kusto_command
kusto_command(
    cluster_uri="https://help.kusto.windows.net",
    database="Samples",
    command=".show tables"
)

# Schema exploration — use kusto_describe_database or kusto_describe_database_entity
kusto_describe_database(
    cluster_uri="https://help.kusto.windows.net",
    database="Samples"
)

# Sample data — use kusto_sample_entity
kusto_sample_entity(
    cluster_uri="https://help.kusto.windows.net",
    database="Samples",
    entity_name="StormEvents",
    entity_type="table",
    sample_size=5
)

# Graph queries — use kusto_graph_query
kusto_graph_query(
    cluster_uri="https://mycluster.kusto.windows.net",
    database="MyDB",
    graph_name="MyGraph",
    query="| graph-match (node) project labels=labels(node)"
)

# Deeplinks — use kusto_deeplink_from_query
kusto_deeplink_from_query(
    cluster_uri="https://help.kusto.windows.net",
    database="Samples",
    query="StormEvents | count"
)
Exploration workflow

When encountering a new cluster or database:

  1. List entities: kusto_list_entities(cluster_uri, entity_type="tables", database="MyDB")
  2. Get schema: kusto_describe_database_entity(entity_name="MyTable", entity_type="table", cluster_uri=..., database=...)
  3. Sample data: kusto_sample_entity(entity_name="MyTable", entity_type="table", cluster_uri=..., sample_size=5)
  4. Count rows: kusto_query(query="MyTable | count", cluster_uri=..., database=...)
  5. Run analysis: kusto_query(query="MyTable | where ... | summarize ...", cluster_uri=..., database=...)

2. Dynamic Type Discipline

KQL's dynamic type is flexible but strict in certain contexts. A common mistake is using a dynamic column in summarize by, order by, or join on without casting.

The rule: Any time you use a dynamic-typed column in by, on, or order by, wrap it in an explicit cast.

kql
// ❌ ERROR: "Summarize group key 'Partners' is of a 'dynamic' type"
| summarize count() by Partners

// ✅ FIX
| summarize count() by tostring(Partners)
kql
// ❌ ERROR: "order operator: key can't be of dynamic type"
| order by Area desc

// ✅ FIX
| order by tostring(Area) desc
kql
// ❌ ERROR in join: dynamic join key
| join kind=inner other on $left.Area == $right.Area

// ✅ FIX — cast both sides
| extend Area_str = tostring(Area)
| join kind=inner (other | extend Area_str = tostring(Area)) on Area_str

Self-correction: When you see "is of a 'dynamic' type" in an error, add tostring(), tolong(), or todouble().

3. Join Patterns & Pitfalls

KQL joins have constraints that differ from SQL.

Equality only

KQL join conditions support only ==. No <, >, !=, or function calls in join predicates.

kql
// ❌ ERROR: "Only equality is allowed in this context"
| join on geo_distance_2points(a.Lat, a.Lon, b.Lat, b.Lon) < 1000

// ✅ WORKAROUND — pre-bucket into spatial cells, then join on cell ID
| extend cell = geo_point_to_s2cell(Lon, Lat, 8)
| join kind=inner (other | extend cell = geo_point_to_s2cell(Lon, Lat, 8)) on cell

For range joins, pre-bin values: | extend bin_val = bin(Value, 100), then join on bin_val.

Left/right attribute matching

Both sides of a join on clause must reference column entities only — not expressions, not aggregates.

kql
// ❌ ERROR: "for each left attribute, right attribute should be selected"
| join kind=inner other on $left.col1

// ✅ FIX — specify both sides explicitly
| join kind=inner other on $left.col1 == $right.col1
Cardinality check before large joins

Always check cardinality before joining tables with >10K rows. A cross-join explosion was the source of the single E_RUNAWAY_QUERY error (25K × 195 = potential 4.8M rows).

kql
// Before joining, check how many rows each side contributes
TableA | summarize dcount(JoinKey)  // → 25,000? Too many for an unconstrained join
TableB | summarize dcount(JoinKey)  // → 195? OK if filtered first

4. Regex in KQL

KQL handles regex natively — no need for Python.

The extract_all gotcha

Unlike Python's re.findall(), KQL's extract_all requires capturing groups in the regex:

kql
// ❌ ERROR: "extractall(): argument 2 must be a valid regex with [1..16] matching groups"
| extend words = extract_all(@"[a-zA-Z]{3,}", Text)

// ✅ FIX — add parentheses around the pattern
| extend words = extract_all(@"([a-zA-Z]{3,})", Text)
Regex toolkit — don't fall back to Python
FunctionUse caseExample
extract(regex, group, source)Single matchextract(@"User '([^']+)'", 1, Msg)
extract_all(regex, source)All matches (needs ())extract_all(@"(\w+)", Text)
parseStructured extractionparse Msg with * "User '" Sender "' sent" *
matches regexBoolean filterwhere Url matches regex @"^https?://"
replace_regexFind and replacereplace_regex(Text, @"\s+", " ")

5. Serialization Requirements

Window functions need serialized (ordered) input.

kql
// ❌ ERROR: "Function 'row_cumsum' cannot be invoked. The row set must be serialized."
| summarize Online = sum(Direction) by bin(Timestamp, 5m)
| extend CumulativeOnline = row_cumsum(Online)

// ✅ FIX — add | serialize (or | order by, which implicitly serializes)
| summarize Online = sum(Direction) by bin(Timestamp, 5m)
| order by Timestamp asc
| extend CumulativeOnline = row_cumsum(Online)

Functions requiring serialization: row_number(), row_cumsum(), prev(), next(), row_window_session().

6. Memory-Safe Query Patterns

The most common memory error. Caused by scanning too much data without pre-filtering.

The progression of safety
Safest ──────────────────────────────────────────────── Most dangerous
| count    | take 10    | where + summarize    | summarize (no filter)    | full scan
Rules for large tables (>1M rows)
  1. Always start with | count to understand table size
  2. Always | where before | summarize — filter time range, partition key, or category first
  3. Never dcount() on high-cardinality columns without pre-filtering
  4. Check join cardinality before executing (see Section 3)
  5. Use materialize() for subqueries referenced multiple times
kql
// ❌ OUT OF MEMORY — 24M rows, no filter, dcount on every column
Consumption
| summarize dcount(Consumed), count() by Timestamp, HouseholdId, MeterType
| where dcount_Consumed > 1

// ✅ SAFE — filter first, then aggregate
Consumption
| where Timestamp between (datetime(2023-04-15) .. datetime(2023-04-16))
| summarize dcount(Consumed) by HouseholdId, MeterType
| where dcount_Consumed > 1
When you see E_LOW_MEMORY_CONDITION

The query touched too much data. Your options:

  • Add | where filters (time range, partition key)
  • Reduce the number of by columns in summarize
  • Break into smaller time windows and union results
  • Use | sample 10000 for exploratory work instead of full scans
When you see E_RUNAWAY_QUERY

A join or aggregation produced too many output rows. Check join cardinality — one or both sides is too large.

7. Result Size Discipline

Large results slow down analysis. Prevention:

Query typeSafeguard
ExploratoryAlways end with | take 10 or | take 20
AggregationUse | top 20 by ... not unbounded summarize
Wide rows (vectors, JSON)| project only needed columns
make_list() / make_set()Avoid on high-cardinality groups (produces huge cells)
Unknown sizeRun | count first

The vector trap: Tables with embedding columns (1536-dim float arrays) produce ~30KB per row. Even | take 20 yields 600KB. Always | project away vector columns unless you specifically need them.

With MCP tools: Use kusto_sample_entity for quick data previews. For deeper exploration, use kusto_query with | take N or | top N to bound results.

8. String Comparison Strictness

KQL sometimes requires explicit casts when comparing computed string values — even when both sides are already strings.

kql
// ❌ ERROR: "Cannot compare values of types string and string. Try adding explicit casts"
| where geo_point_to_s2cell(Lon, Lat, 16) == other_cell

// ✅ FIX — wrap both sides in tostring()
| where tostring(geo_point_to_s2cell(Lon, Lat, 16)) == tostring(other_cell)

This is most common with computed values from geo_point_to_s2cell() and strcat() comparisons. When in doubt, cast with tostring().

9. Advanced Functions

KQL handles these natively — no need for Python:

Vector similarity
kql
// try it! — cosine similarity on Iris feature vectors
let target = pack_array(5.1, 3.5, 1.4, 0.2);
Iris
| extend Vec = pack_array(SepalLength, SepalWidth, PetalLength, PetalWidth)
| extend sim = series_cosine_similarity(Vec, target)
| top 5 by sim desc
Geo operations
kql
// Distance between two points (meters)
StormEvents | extend dist = geo_distance_2points(BeginLon, BeginLat, EndLon, EndLat)

// Spatial bucketing for joins
StormEvents | extend cell = geo_point_to_s2cell(BeginLon, BeginLat, 8)
Graph queries

Use the kusto_graph_query MCP tool for graph traversal. It automatically handles graph snapshots when available:

kql
// Persistent graph model — query the latest snapshot
graph("Simple")
| graph-match (src)-[e*1..5]->(dst)
  where src.name == "Alice"
  project src.name, dst.name, path_length = array_length(e)

// Transient graph — build inline with make-graph
SimpleGraph_Edges
| make-graph source --> target with SimpleGraph_Nodes on id
| graph-match (src)-[e*1..5]->(dst)
  where src.name == "Alice"
  project src.name, dst.name, path_length = array_length(e)
Time series
kql
// Create a time series and detect anomalies
StormEvents
| make-series count() default=0 on StartTime step 1d
| extend anomalies = series_decompose_anomalies(count_)

For detailed examples and patterns, consult references/advanced-patterns.md.

10. Self-Correction Lookup Table

When you encounter an error, look it up here before retrying:

Error message containsLikely causeFix
is of a 'dynamic' typeDynamic column in by/on/order byWrap in tostring()/tolong()
Only equality is allowedRange predicate in join conditionPre-bucket with S2/H3 cells or bin()
extractall(): matching groupsMissing () in regexAdd (): @"(\w+)" not @"\w+"
row set must be serializedWindow function on unsorted dataAdd | serialize or | order by before it
Cannot compare values of types string and stringComputed string comparisonAdd tostring() on both sides
Failed to resolve column named 'X'Wrong column name or wrong tableUse kusto_describe_database_entity to check column names
E_LOW_MEMORY_CONDITIONQuery touched too much dataAdd | where filters, reduce time range, break into steps
E_RUNAWAY_QUERYJoin/aggregation produced too many rowsCheck cardinality before joining; add pre-filters
for each left attribute, right attributeJoin on clause incompleteUse explicit form: on $left.X == $right.Y
needs to be bracketedReserved word used as identifierUse ['keyword'] syntax
plugin doesn't existUnavailable plugin on this clusterFall back to equivalent function or Python
Expected string literal in datetime()Bare integer in datetime literalUse datetime(2024-01-01) not datetime(2024)
Unexpected token after byComplex expression in summarize by-clauseextend the expression first, then summarize by the column
not recognized / unknown operatorOperator not available on this engineCheck operator support; try equivalent (order by = sort by)

11. Datetime Pitfalls

Datetime literals are a common source of errors. A wrong literal format can cascade into completely different approaches instead of fixing the small issue.

Literal format
kql
// ❌ WRONG — bare year is not a valid datetime
| where StartTime > datetime(2007)

// ✅ RIGHT — always use full date format
| where StartTime > datetime(2007-01-01)
Filtering by year, month, or hour
kql
// ❌ WRONG — comparing datetime column to integer
| where StartTime == 2007

// ✅ RIGHT — use datetime_part() to extract components
| where datetime_part("year", StartTime) == 2007

// ✅ ALSO RIGHT — use between with datetime range
| where StartTime between (datetime(2007-01-01) .. datetime(2007-12-31T23:59:59))
Time bucketing in summarize
kql
// This works, but can be harder to read and reuse in complex queries
| summarize count() by startofmonth(StartTime)

// Clearer — extend first, then summarize by the computed column
| extend Month = startofmonth(StartTime)
| summarize count() by Month
| order by Month asc
Show full SKILL.md (786 more words)Show less
Useful datetime functions
FunctionPurposeExample
bin(ts, 1h)Round down to bucket boundarybin(Timestamp, 1d)
startofmonth(ts)First day of monthstartofmonth(Timestamp)
datetime_part("hour", ts)Extract componentdatetime_part("year", Timestamp)
format_datetime(ts, fmt)Format as stringformat_datetime(Timestamp, "yyyy-MM")
ago(1d)Relative timewhere Timestamp > ago(1d)
between(a .. b)Range filter (inclusive)where Timestamp between (datetime(2024-01-01) .. datetime(2024-01-31T23:59:59))
todatetime(str)Parse string → datetimetodatetime("2024-01-15T10:30:00Z")
totimespan(str)Parse string → timespantotimespan("01:30:00")

12. Operator Naming & Equality

KQL has subtle differences from SQL syntax.

Naming conventions
EntityConventionExample
TablesUpperCamelCaseStormEvents, NetworkLogs
ColumnsUpperCamelCaseStartTime, EventType
Variables (let)snake_caselet filtered_events = ...
Built-in functionssnake_caseformat_bytes(), geo_distance_2points()
Stored functionsUpperCamelCase.create function GetTopUsers
Equality operators
kql
// In where clauses, == is case-sensitive, =~ is case-insensitive
| where State == "TEXAS"      // exact match
| where State =~ "texas"      // case-insensitive
| where State != "TEXAS"      // not equal
| where State !~ "texas"      // case-insensitive not equal

// In joins, use == only
| join kind=inner other on $left.Key == $right.Key
sort vs order

Both sort by and order by work identically in KQL — they are aliases. Use whichever you prefer, but be consistent.

contains vs has
kql
// contains: substring match (slower)
| where Message contains "error"        // finds "MyErrorHandler" too

// has: term/word match (faster, uses index)
| where Message has "error"             // matches word boundaries only

// For exact prefix/suffix
| where Message startswith "Error:"
| where Message endswith ".log"

13. Error Recovery Strategy

When a first KQL query fails, the temptation is to abandon the entire approach and try something completely different. The correct response is almost always to fix the specific error, not change strategy.

The pattern to avoid
Query 1: extract(@"pattern", 1, col)  → Parse error
Query 2: todynamic(col)               → Different error  
Query 3: parse_json(col)              → Another error
Query 4: Python script                → Works but 10x tokens
The correct pattern
Query 1: extract(@"pattern", 1, col)  → Parse error (bad escaping)
Query 2: extract(@"pattern", 1, col)  → Fix the specific escaping issue → Success

Rules for error recovery:

  1. Read the error message carefully — it almost always tells you exactly what's wrong
  2. Fix the specific syntax/escaping issue, don't switch approaches
  3. Use the self-correction table (Section 10) to map errors to fixes
  4. Only switch approaches after 2 failed fixes of the same query
  5. The parse operator is often simpler than extract() for structured text:
kql
// Instead of complex regex:
// extract(@"User '([^']+)' sent (\d+) bytes", 1, Message)

// Use parse for structured extraction:
| parse Message with * "User '" Username "' sent " ByteCount " bytes" *

14. Query Writing Checklist

Before running any KQL query, mentally check:

  1. Pre-filtered? Large tables have a | where before any | summarize
  2. Result bounded? Exploratory queries end with | take N or | top N
  3. Dynamic columns cast? Any dynamic column in by/on/order by is wrapped
  4. Regex has groups? extract_all patterns have () around what you want to capture
  5. Join cardinality safe? Both sides checked with dcount() before joining
  6. Needed columns only? Wide tables get | project to drop unneeded columns
  7. Datetime literals valid? Using datetime(2024-01-01) not datetime(2024) or bare integers
  8. Complex by-expressions? Use | extend first, then | summarize by the computed column
  9. Error recovery plan? If a query fails, fix the specific error — don't change strategy
  10. Right tool? Use kusto_query for queries, kusto_command for management commands, kusto_sample_entity for quick previews
  11. Checked the plan? For expensive queries (joins, large tables), use kusto_show_queryplan to compare approaches before executing
  12. Cluster healthy? Before heavy workloads, use kusto_diagnostics to check capacity and permissions

15. Diagnostics & Query Optimization

Two tools let you look before you leap: kusto_show_queryplan for query cost estimation and kusto_diagnostics for cluster health.

Query plan analysis

kusto_show_queryplan plans a query without executing it. Returns:

FieldWhat it tells you
stats.PlanSizeOverall plan complexity (bytes). Compare two approaches — higher = heavier.
stats.RelopSizeLogical operator tree size. Grows with operator count.
execution_hints.estimated_rowsTotal rows the engine expects to process. The strongest cost signal.
execution_hints.shard_scansPer-shard {total_rows, has_selection}. More shards = more parallel scans.
execution_hints.shard_scans[].has_selectiontrue = a filter narrows the scan (extent pruning). false = full scan.
execution_hints.concurrencyParallelism hint. -1 = auto (precomputed), 1 = parallel partitions.
relop_treeLogical operator tree. Look for ConstantDataTable (precomputed) or InnerEquiJoin (expensive).
errorSemantic errors caught without executing. Validates column names and table references.
kusto_show_queryplan(
    query="Trips | where pickup_datetime > datetime(2014-01-01) | summarize count() by vendor_id",
    cluster_uri="https://help.kusto.windows.net",
    database="Samples"
)
What queryplan CAN detect
  • Expensive joins: Self-joins double estimated_rows and shard_scans count.
  • Materialize overhead: materialize() + join has higher PlanSize than single-pass multi-aggregation.
  • Precomputed results: Bare | count returns estimated_rows=1 and ConstantDataTable in the tree — no scan.
  • Column/table typos: Returns an error field with the semantic error message.
  • Filter presence: has_selection=true vs false shows whether a where clause narrows the scan.
What queryplan CANNOT detect
  • Data volume differences: Full scan vs filtered scan on the same table show similar estimated_rows (both report table size). Use has_selection instead — false means full scan.
  • Type mismatches: KQL auto-casts, so where fare_amount == "expensive" plans successfully (returns 0 rows at runtime).
  • Ambiguous join columns: KQL resolves these at plan time without error.
Comparing two query approaches

The pattern: plan both, compare estimated_rows and shard_scans.

# Plan A: direct summarize
plan_a = kusto_show_queryplan(query="Trips | summarize count() by vendor_id, payment_type", ...)

# Plan B: self-join (looks "clever" but is worse)
plan_b = kusto_show_queryplan(query="Trips | as T | join kind=inner (T | summarize by vendor_id) on vendor_id | summarize count() by payment_type", ...)

# Compare: plan_b.execution_hints.estimated_rows will be 2x plan_a's → pick A

Regression thresholds: Flag a rewrite as a regression if estimated_rows increases >50% or shard_scans count increases >30%.

Cluster diagnostics

kusto_diagnostics runs 7 commands (best-effort — permission failures don't block others):

SectionWhat it tells you
capacityResource slots: Queries, Ingestions, Merges, etc. Each has Total/Consumed/Remaining.
clusterNode count, cores, RAM (total and available), product version.
principal_rolesYour permissions per database (Viewer, Admin, etc.).
diagnosticsCluster health: IsHealthy, merge/ingestion load factors, extent counts.
workload_groupsConfigured workload policies (requires admin).
rowstoresRowstore memory state (requires admin).
ingestion_failuresFailed ingestions in last 24h.
kusto_diagnostics(
    cluster_uri="https://help.kusto.windows.net",
    database="Samples"
)
When to use diagnostics
  • Before heavy workloads: Check capacity — if Queries.Remaining is low, wait or batch.
  • Batching decisions: batch_size = min(remaining_slots / 2, num_tasks) — leave 50% headroom.
  • Permission checks: principal_roles tells you what you can do before you try and fail.
  • Debugging ingestion issues: ingestion_failures surfaces errors from the last 24h.

© microsoft, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

SKILL.md and 4 other files (references) in .github/skills/kql of microsoft/fabric-rti-mcp.

  • SKILL.md
  • references/advanced-patterns.md
  • references/discovery-queries.md
  • references/error-recovery.md
  • references/query-templates.md

Open the folder on GitHubat commit 3c765f9

Compare with similar skills

Kql next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Kql compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Kql this skillmicrosoft/fabric-rti-mcp131—~6.2kAutomated safety check: PassMIT
Azure Kustomicrosoft/GitHub-Copilot-for-Azure2551 repos~2.2kAutomated safety check: PassMIT
Kqlmicrosoft/skills3.1k—~4.7kAutomated safety check: PassMIT
Apex Azure Kustojonathan-vella/apex217—~984Automated safety check: PassMIT
Azure AI Anomalydetector Javamicrosoft/skills3.1k6 repos~2.3kAutomated safety check: PassMIT
Analyzing Cloud Storage Access Patternsmukul975/Anthropic-Cybersecurity-Skills34k—~599Automated safety check: PassApache-2.0

Similar skills

  • Azure Kusto

    microsoft/GitHub-Copilot-for-Azure

    Official

    Query and analyze data in Azure Data Explorer (Kusto/ADX) using KQL for log analytics, telemetry, and time series analysis.

    255 GitHub starsUsed in 1 repo~2.2k tokens
    Data & AnalyticsAuto-check passed
  • Kql

    microsoft/skills

    Official

    KQL language expertise for writing correct, efficient Kusto Query Language queries.

    3.1k GitHub stars~4.7k tokensUpdated yesterday
    Data & AnalyticsAuto-check passed
  • Apex Azure Kusto

    jonathan-vella/apex

    ANALYSIS SKILL — Query and analyze data in Azure Data Explorer (Kusto/ADX) using KQL.

    217 GitHub stars~984 tokensUpdated yesterday
    Data & AnalyticsAuto-check passed
  • Official

    Build anomaly detection applications with Azure AI Anomaly Detector SDK for Java.

    3.1k GitHub starsUsed in 6 repos~2.3k tokens
    Data & AnalyticsAuto-check passed
  • Analyzing Cloud Storage Access Patterns

    mukul975/Anthropic-Cybersecurity-Skills

    Detect abnormal access in AWS S3, GCS, and Azure Blob Storage by analyzing CloudTrail Data Events, GCS audit logs, and Azure Storage Analytics for after-hours bulk downloads, new-IP access, and…

    34k GitHub stars~599 tokensUpdated 1 mo ago
    Data & AnalyticsAuto-check passed
  • Kql Query Authoring

    SCStelz/security-investigator

    A skill your agent uses when asked to write, create, or help with KQL (Kusto Query Language) queries for Microsoft Sentinel, Defender XDR, or Azure Data Explorer.

    249 GitHub stars~5.7k tokensUpdated yesterday
    Data & AnalyticsAuto-check passed

Questions about Kql

What does Kql do?

KQL language expertise for writing correct, efficient Kusto queries using the Fabric RTI MCP tools. Kql is an agent skill from microsoft/fabric-rti-mcp, published by the product's own GitHub organization. KQL language expertise for writing correct, efficient Kusto queries using the Fabric RTI MCP tools.

When should I use Kql?

Kql fits situations like: azure Data Explorer; fabric Eventhouse; data exploration; anomaly detection.

How do I install Kql in Claude Code?

Run `npx skills add microsoft/fabric-rti-mcp --skill kql -a claude-code`. Or copy the skill folder (.github/skills/kql in microsoft/fabric-rti-mcp) into .claude/skills/kql in your project. Claude Code loads it when a task matches its description.

How do I install Kql in Codex?

Run `npx skills add microsoft/fabric-rti-mcp --skill kql -a codex`. Or copy the skill folder (.github/skills/kql in microsoft/fabric-rti-mcp) into .agents/skills/kql in your project. Codex loads it when a task matches its description.

Can I use Kql in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add microsoft/fabric-rti-mcp --skill kql -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/kql, .gemini/skills/kql, .github/skills/kql and .opencode/skills/kql in your project.

What does Kql need to run?

SKILL.md names no scripts, command-line tools or credentials: Kql is instructions for the agent only. Our summary lists: Python 3.

Does Kql access the network?

SKILL.md names 1 domain. In commands or code: help.kusto.windows.net; the agent is likely to contact it when it follows the instructions. This is read from the text; nothing was executed.

Is Kql safe to install?

Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.

What licence does Kql use?

Kql is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Kql use?

About 6.2k tokens (SKILL.md is roughly 25k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full. Its references folder adds about 12k tokens, read only when the agent opens those files.

What are the alternatives to Kql?

Skills that share tags, products or a category with Kql: Azure Kusto (microsoft/GitHub-Copilot-for-Azure, 255 stars), Kql (microsoft/skills, 3.1k stars), Apex Azure Kusto (jonathan-vella/apex, 217 stars) and Azure AI Anomalydetector Java (microsoft/skills, 3.1k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Kql?

microsoft (a GitHub organization, an official publisher) maintains it in microsoft/fabric-rti-mcp, which has 131 GitHub stars. The repository was last updated on October 1, 2026.

Source: microsoft/fabric-rti-mcp on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.