Azure Kusto
microsoft/GitHub-Copilot-for-Azure
Query and analyze data in Azure Data Explorer (Kusto/ADX) using KQL for log analytics, telemetry, and time series analysis.
KQL language expertise for writing correct, efficient Kusto queries using the Fabric RTI MCP tools.
$ npx skills add microsoft/fabric-rti-mcp --skill kql -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install microsoft/fabric-rti-mcp kql --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ 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-srcUse ~/.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/
Install the "kql" agent skill from https://github.com/microsoft/fabric-rti-mcp/tree/main/.github/skills/kql into .claude/skills/kql/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "kql", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/microsoft/fabric-rti-mcp/tree/main/.github/skills/kqlType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add microsoft/fabric-rti-mcp --skill kql -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install microsoft/fabric-rti-mcp kql --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/microsoft/fabric-rti-mcp.git skills-src && mkdir -p .agents/skills && cp -r skills-src/.github/skills/kql .agents/skills/kql && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "kql" agent skill from https://github.com/microsoft/fabric-rti-mcp/tree/main/.github/skills/kql into .agents/skills/kql/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "kql", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add microsoft/fabric-rti-mcp --skill kql -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install microsoft/fabric-rti-mcp kql --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/microsoft/fabric-rti-mcp.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/.github/skills/kql .cursor/skills/kql && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "kql" agent skill from https://github.com/microsoft/fabric-rti-mcp/tree/main/.github/skills/kql into .cursor/skills/kql/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "kql", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/microsoft/fabric-rti-mcp.git --path .github/skills/kql--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add microsoft/fabric-rti-mcp --skill kql -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install microsoft/fabric-rti-mcp kql --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/microsoft/fabric-rti-mcp.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/.github/skills/kql .gemini/skills/kql && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "kql" agent skill from https://github.com/microsoft/fabric-rti-mcp/tree/main/.github/skills/kql into .gemini/skills/kql/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "kql", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install microsoft/fabric-rti-mcp kqlInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add microsoft/fabric-rti-mcp --skill kql -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/microsoft/fabric-rti-mcp.git skills-src && mkdir -p .github/skills && cp -r skills-src/.github/skills/kql .github/skills/kql && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "kql" agent skill from https://github.com/microsoft/fabric-rti-mcp/tree/main/.github/skills/kql into .github/skills/kql/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "kql", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add microsoft/fabric-rti-mcp --skill kql -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install microsoft/fabric-rti-mcp kql --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/microsoft/fabric-rti-mcp.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/.github/skills/kql .opencode/skills/kql && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "kql" agent skill from https://github.com/microsoft/fabric-rti-mcp/tree/main/.github/skills/kql into .opencode/skills/kql/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "kql", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
kqlKQL 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. 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.
12 steps, taken from the step headings in SKILL.md.
Read from SKILL.md and the folder at commit 3c765f9. It shows what the files ask for, not the result of running them.
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.
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.
Hosts in commands or code, which the agent is likely to contact:
help.kusto.windows.netFrom URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
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.
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.
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.
The full file from microsoft/fabric-rti-mcp at commit 3c765f9, republished under its MIT licence (© microsoft). 1,897 words, ~6,192 tokens.
.claude/skills/kql/SKILL.md (or your agent's skills folder). This skill also uses 4 other files; get the full folder from GitHub.Try it yourself: All
✅examples in this skill can be run against the public help cluster:https://help.kusto.windows.net, databaseSamples(containsStormEvents,SimpleGraph_Nodes/Edges,nyc_taxi, and more).
Fabric RTI MCP exposes Kusto functionality as MCP tools. Authentication is handled transparently using Azure Identity.
| Tool | Purpose |
|---|---|
kusto_query | Execute a KQL query on a database |
kusto_command | Execute a management command (.show, .create, etc.) |
kusto_list_entities | List databases, tables, external tables, materialized views, functions, graphs |
kusto_describe_database | Get schema for all entities in a database |
kusto_describe_database_entity | Get schema for a specific entity (table, function, etc.) |
kusto_sample_entity | Get sample data from a table or other entity |
kusto_graph_query | Execute a graph query using snapshots or transient graphs |
kusto_ingest_inline_into_table | Ingest inline CSV data into a table |
kusto_known_services | List configured Kusto services |
kusto_get_shots | Retrieve semantically similar shots from a shots table |
kusto_deeplink_from_query | Build a deeplink URL to open a query in the web explorer |
kusto_show_queryplan | Get the execution plan for a query without running it |
kusto_diagnostics | Get a best-effort cluster health and capacity summary |
KQL has two execution planes, each with its own MCP tool:
| Plane | Tool | Starts with | Examples |
|---|---|---|---|
| Query | kusto_query | Table name, let, print, datatable | StormEvents | where State == "TEXAS" |
| Management | kusto_command | .show, .create, .set, .drop, .alter | .show tables, .show table T schema |
# 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"
)When encountering a new cluster or database:
kusto_list_entities(cluster_uri, entity_type="tables", database="MyDB")kusto_describe_database_entity(entity_name="MyTable", entity_type="table", cluster_uri=..., database=...)kusto_sample_entity(entity_name="MyTable", entity_type="table", cluster_uri=..., sample_size=5)kusto_query(query="MyTable | count", cluster_uri=..., database=...)kusto_query(query="MyTable | where ... | summarize ...", cluster_uri=..., database=...)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.
// ❌ ERROR: "Summarize group key 'Partners' is of a 'dynamic' type"
| summarize count() by Partners
// ✅ FIX
| summarize count() by tostring(Partners)// ❌ ERROR: "order operator: key can't be of dynamic type"
| order by Area desc
// ✅ FIX
| order by tostring(Area) desc// ❌ 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_strSelf-correction: When you see "is of a 'dynamic' type" in an error, add tostring(), tolong(), or todouble().
KQL joins have constraints that differ from SQL.
KQL join conditions support only ==. No <, >, !=, or function calls in join predicates.
// ❌ 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 cellFor range joins, pre-bin values: | extend bin_val = bin(Value, 100), then join on bin_val.
Both sides of a join on clause must reference column entities only — not expressions, not aggregates.
// ❌ 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.col1Always 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).
// 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 firstKQL handles regex natively — no need for Python.
extract_all gotchaUnlike Python's re.findall(), KQL's extract_all requires capturing groups in the regex:
// ❌ 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)| Function | Use case | Example |
|---|---|---|
extract(regex, group, source) | Single match | extract(@"User '([^']+)'", 1, Msg) |
extract_all(regex, source) | All matches (needs ()) | extract_all(@"(\w+)", Text) |
parse | Structured extraction | parse Msg with * "User '" Sender "' sent" * |
matches regex | Boolean filter | where Url matches regex @"^https?://" |
replace_regex | Find and replace | replace_regex(Text, @"\s+", " ") |
Window functions need serialized (ordered) input.
// ❌ 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().
The most common memory error. Caused by scanning too much data without pre-filtering.
Safest ──────────────────────────────────────────────── Most dangerous
| count | take 10 | where + summarize | summarize (no filter) | full scan| count to understand table size| where before | summarize — filter time range, partition key, or category firstdcount() on high-cardinality columns without pre-filteringmaterialize() for subqueries referenced multiple times// ❌ 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 > 1E_LOW_MEMORY_CONDITIONThe query touched too much data. Your options:
| where filters (time range, partition key)by columns in summarize| sample 10000 for exploratory work instead of full scansE_RUNAWAY_QUERYA join or aggregation produced too many output rows. Check join cardinality — one or both sides is too large.
Large results slow down analysis. Prevention:
| Query type | Safeguard |
|---|---|
| Exploratory | Always end with | take 10 or | take 20 |
| Aggregation | Use | 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 size | Run | 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.
KQL sometimes requires explicit casts when comparing computed string values — even when both sides are already strings.
// ❌ 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().
KQL handles these natively — no need for Python:
// 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// 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)Use the kusto_graph_query MCP tool for graph traversal. It automatically handles graph snapshots when available:
// 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)// 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.
When you encounter an error, look it up here before retrying:
| Error message contains | Likely cause | Fix |
|---|---|---|
is of a 'dynamic' type | Dynamic column in by/on/order by | Wrap in tostring()/tolong() |
Only equality is allowed | Range predicate in join condition | Pre-bucket with S2/H3 cells or bin() |
extractall(): matching groups | Missing () in regex | Add (): @"(\w+)" not @"\w+" |
row set must be serialized | Window function on unsorted data | Add | serialize or | order by before it |
Cannot compare values of types string and string | Computed string comparison | Add tostring() on both sides |
Failed to resolve column named 'X' | Wrong column name or wrong table | Use kusto_describe_database_entity to check column names |
E_LOW_MEMORY_CONDITION | Query touched too much data | Add | where filters, reduce time range, break into steps |
E_RUNAWAY_QUERY | Join/aggregation produced too many rows | Check cardinality before joining; add pre-filters |
for each left attribute, right attribute | Join on clause incomplete | Use explicit form: on $left.X == $right.Y |
needs to be bracketed | Reserved word used as identifier | Use ['keyword'] syntax |
plugin doesn't exist | Unavailable plugin on this cluster | Fall back to equivalent function or Python |
Expected string literal in datetime() | Bare integer in datetime literal | Use datetime(2024-01-01) not datetime(2024) |
Unexpected token after by | Complex expression in summarize by-clause | extend the expression first, then summarize by the column |
not recognized / unknown operator | Operator not available on this engine | Check operator support; try equivalent (order by = sort by) |
Datetime literals are a common source of errors. A wrong literal format can cascade into completely different approaches instead of fixing the small issue.
// ❌ WRONG — bare year is not a valid datetime
| where StartTime > datetime(2007)
// ✅ RIGHT — always use full date format
| where StartTime > datetime(2007-01-01)// ❌ 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))// 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| Function | Purpose | Example |
|---|---|---|
bin(ts, 1h) | Round down to bucket boundary | bin(Timestamp, 1d) |
startofmonth(ts) | First day of month | startofmonth(Timestamp) |
datetime_part("hour", ts) | Extract component | datetime_part("year", Timestamp) |
format_datetime(ts, fmt) | Format as string | format_datetime(Timestamp, "yyyy-MM") |
ago(1d) | Relative time | where 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 → datetime | todatetime("2024-01-15T10:30:00Z") |
totimespan(str) | Parse string → timespan | totimespan("01:30:00") |
KQL has subtle differences from SQL syntax.
| Entity | Convention | Example |
|---|---|---|
| Tables | UpperCamelCase | StormEvents, NetworkLogs |
| Columns | UpperCamelCase | StartTime, EventType |
Variables (let) | snake_case | let filtered_events = ... |
| Built-in functions | snake_case | format_bytes(), geo_distance_2points() |
| Stored functions | UpperCamelCase | .create function GetTopUsers |
// 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.KeyBoth sort by and order by work identically in KQL — they are aliases. Use whichever you prefer, but be consistent.
// 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"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.
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 tokensQuery 1: extract(@"pattern", 1, col) → Parse error (bad escaping)
Query 2: extract(@"pattern", 1, col) → Fix the specific escaping issue → SuccessRules for error recovery:
parse operator is often simpler than extract() for structured text:// Instead of complex regex:
// extract(@"User '([^']+)' sent (\d+) bytes", 1, Message)
// Use parse for structured extraction:
| parse Message with * "User '" Username "' sent " ByteCount " bytes" *Before running any KQL query, mentally check:
| where before any | summarize| take N or | top Nby/on/order by is wrappedextract_all patterns have () around what you want to capturedcount() before joining| project to drop unneeded columnsdatetime(2024-01-01) not datetime(2024) or bare integers| extend first, then | summarize by the computed columnkusto_query for queries, kusto_command for management commands, kusto_sample_entity for quick previewskusto_show_queryplan to compare approaches before executingkusto_diagnostics to check capacity and permissionsTwo tools let you look before you leap: kusto_show_queryplan for query cost estimation and kusto_diagnostics for cluster health.
kusto_show_queryplan plans a query without executing it. Returns:
| Field | What it tells you |
|---|---|
stats.PlanSize | Overall plan complexity (bytes). Compare two approaches — higher = heavier. |
stats.RelopSize | Logical operator tree size. Grows with operator count. |
execution_hints.estimated_rows | Total rows the engine expects to process. The strongest cost signal. |
execution_hints.shard_scans | Per-shard {total_rows, has_selection}. More shards = more parallel scans. |
execution_hints.shard_scans[].has_selection | true = a filter narrows the scan (extent pruning). false = full scan. |
execution_hints.concurrency | Parallelism hint. -1 = auto (precomputed), 1 = parallel partitions. |
relop_tree | Logical operator tree. Look for ConstantDataTable (precomputed) or InnerEquiJoin (expensive). |
error | Semantic 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"
)estimated_rows and shard_scans count.materialize() + join has higher PlanSize than single-pass multi-aggregation.| count returns estimated_rows=1 and ConstantDataTable in the tree — no scan.error field with the semantic error message.has_selection=true vs false shows whether a where clause narrows the scan.estimated_rows (both report table size). Use has_selection instead — false means full scan.where fare_amount == "expensive" plans successfully (returns 0 rows at runtime).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 ARegression thresholds: Flag a rewrite as a regression if estimated_rows increases >50% or shard_scans count increases >30%.
kusto_diagnostics runs 7 commands (best-effort — permission failures don't block others):
| Section | What it tells you |
|---|---|
capacity | Resource slots: Queries, Ingestions, Merges, etc. Each has Total/Consumed/Remaining. |
cluster | Node count, cores, RAM (total and available), product version. |
principal_roles | Your permissions per database (Viewer, Admin, etc.). |
diagnostics | Cluster health: IsHealthy, merge/ingestion load factors, extent counts. |
workload_groups | Configured workload policies (requires admin). |
rowstores | Rowstore memory state (requires admin). |
ingestion_failures | Failed ingestions in last 24h. |
kusto_diagnostics(
cluster_uri="https://help.kusto.windows.net",
database="Samples"
)capacity — if Queries.Remaining is low, wait or batch.batch_size = min(remaining_slots / 2, num_tasks) — leave 50% headroom.principal_roles tells you what you can do before you try and fail.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
SKILL.md and 4 other files (references) in .github/skills/kql of microsoft/fabric-rti-mcp.
Open the folder on GitHubat commit 3c765f9
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.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Kql this skillmicrosoft/fabric-rti-mcp | 131 | — | ~6.2k | Automated safety check: Pass | MIT | |
| Azure Kustomicrosoft/GitHub-Copilot-for-Azure | 255 | 1 repos | ~2.2k | Automated safety check: Pass | MIT | |
| Kqlmicrosoft/skills | 3.1k | — | ~4.7k | Automated safety check: Pass | MIT | |
| Apex Azure Kustojonathan-vella/apex | 217 | — | ~984 | Automated safety check: Pass | MIT | |
| Azure AI Anomalydetector Javamicrosoft/skills | 3.1k | 6 repos | ~2.3k | Automated safety check: Pass | MIT | |
| Analyzing Cloud Storage Access Patternsmukul975/Anthropic-Cybersecurity-Skills | 34k | — | ~599 | Automated safety check: Pass | Apache-2.0 |
microsoft/GitHub-Copilot-for-Azure
Query and analyze data in Azure Data Explorer (Kusto/ADX) using KQL for log analytics, telemetry, and time series analysis.
microsoft/skills
KQL language expertise for writing correct, efficient Kusto Query Language queries.
jonathan-vella/apex
ANALYSIS SKILL — Query and analyze data in Azure Data Explorer (Kusto/ADX) using KQL.
microsoft/skills
Build anomaly detection applications with Azure AI Anomaly Detector SDK for Java.
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…
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.
Works with
Categories
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.
Kql fits situations like: azure Data Explorer; fabric Eventhouse; data exploration; anomaly detection.
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.
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.
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.
SKILL.md names no scripts, command-line tools or credentials: Kql is instructions for the agent only. Our summary lists: Python 3.
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.
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.
Kql is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
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.
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.
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.