kusto_query: migrate query path/parsing to v2 and add --show-stats#1787
kusto_query: migrate query path/parsing to v2 and add --show-stats#1787gholliday wants to merge 2 commits intomicrosoft:mainfrom
Conversation
Migrate query requests to /v2/rest/query with required headers and decompression, parse v2 frames including DataSetCompletion errors, and add --show-stats structured statistics output with unit tests, docs, and changelog entry. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Thank you for your contribution @gholliday! We will review the pull request and get back to you soon. |
There was a problem hiding this comment.
Pull request overview
Updates the kusto_query tool to execute queries via Kusto’s /v2/rest/query frame-based response format and adds an optional --show-stats flag to return structured execution statistics alongside results.
Changes:
- Migrates Kusto query execution to
/v2/rest/queryand adds v2 frame parsing (PrimaryResult + DataSetCompletion error handling). - Adds
--show-statsoption and plumbs statistics extraction fromQueryCompletionInformationframes through to the command response. - Adds/updates unit tests and updates command documentation and changelog entry.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| tools/Azure.Mcp.Tools.Kusto/src/Services/KustoService.cs | Adds v2 frame parsing, statistics extraction, and new QueryItemsWithStatisticsAsync APIs. |
| tools/Azure.Mcp.Tools.Kusto/src/Services/KustoClient.cs | Switches query endpoint to /v2/rest/query, uses named HttpClient, and adds readonly/compression/connection headers. |
| tools/Azure.Mcp.Tools.Kusto/src/Services/IKustoService.cs | Extends interface with new tuple-returning QueryItemsWithStatisticsAsync overloads. |
| tools/Azure.Mcp.Tools.Kusto/src/Commands/QueryCommand.cs | Adds --show-stats option and includes optional statistics in the response model. |
| tools/Azure.Mcp.Tools.Kusto/src/Options/QueryOptions.cs | Adds ShowStats option binding model property. |
| tools/Azure.Mcp.Tools.Kusto/src/Options/KustoOptionDefinitions.cs | Defines --show-stats option. |
| tools/Azure.Mcp.Tools.Kusto/src/KustoSetup.cs | Registers a named HttpClient configured for gzip/deflate decompression. |
| tools/Azure.Mcp.Tools.Kusto/tests/Azure.Mcp.Tools.Kusto.UnitTests/QueryCommandTests.cs | Updates tests to use new service API and adds coverage for --show-stats. |
| tools/Azure.Mcp.Tools.Kusto/tests/Azure.Mcp.Tools.Kusto.UnitTests/KustoServiceTests.cs | Adds new tests for v2 frame parsing, error handling, and statistics extraction. |
| tools/Azure.Mcp.Tools.Kusto/tests/Azure.Mcp.Tools.Kusto.UnitTests/KustoClientTests.cs | Adds assertions for v2 endpoint usage and expected request headers. |
| servers/Azure.Mcp.Server/docs/e2eTestPrompts.md | Adds an e2e prompt that exercises statistics output. |
| servers/Azure.Mcp.Server/docs/azmcp-commands.md | Documents optional [--show-stats] on kusto query. |
| servers/Azure.Mcp.Server/changelog-entries/copilot-kusto-query-v2-show-stats.yaml | Changelog entry for v2 migration and the new stats option. |
Remove duplicate QueryItems validation, replace brittle columns JSON string construction with writer-based object creation, and avoid reparsing rows by cloning JsonElement directly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
I can't get the tests working, and it appears that they are a pre-existing issue. I've contributed this to microsoft/fabric-rti-mcp#123 instead, so feel free to close this one. |
|
Hi @danield137 - the author mentioned this functionality has been added to fabric-rti-mcp and suggested closing this PR. Please take a look and close it if these changes are out of scope. I'm adding "Do not merge" label |
jongio
left a comment
There was a problem hiding this comment.
Kusto v2 Migration Review
Good work on the v2 frame parsing - the DataSetHeader/DataTable/DataSetCompletion flow is well-structured and the statistics extraction is thorough. Test coverage for the new parsing logic is solid.
Two issues need attention before this could merge:
-
x-ms-readonly: true on management commands (HIGH) - The header is set in
GenerateRequestAsync, which is shared between query and control paths. Management commands should not carry this header. -
v2 endpoint affects dot-command callers (HIGH) -
GetTableSchemaAsyncsends.show tablethroughExecuteQueryCommandAsync, which now routes to/v2/rest/query. The v2 frame structure differs from v1 - this may be why tests are failing.
Also flagged: duplicate Accept-Encoding headers (handler already configures decompression), a JSON roundtrip allocation that could use the existing Utf8JsonWriter pattern, and ~700 lines of static statistics parsing that could be extracted to KustoStatisticsParser.
Note: This branch is stale relative to main - it is missing the sovereign cloud auth (GetKustoScope) and KQL identifier validation (EscapeKqlIdentifier) that were added after branching. These are not PR regressions but would require merge conflict resolution.
| httpRequest.Headers.Add("x-ms-client-version", "Kusto.Client.Light"); | ||
| httpRequest.Headers.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json")); | ||
| httpRequest.Headers.Add("x-ms-readonly", "true"); | ||
| httpRequest.Headers.AcceptEncoding.Add(new System.Net.Http.Headers.StringWithQualityHeaderValue("gzip")); |
There was a problem hiding this comment.
[HIGH] x-ms-readonly applied to all request types
GenerateRequestAsync is shared between query and control command paths. Setting x-ms-readonly: true here means it gets sent on management commands (.show table, .set, etc.) through ExecuteControlCommandAsync too - that is semantically wrong for write operations.
Consider adding a bool isReadOnly = true parameter to GenerateRequestAsync and passing false from ExecuteControlCommandAsync.
| public Task<KustoResult> ExecuteQueryCommandAsync(string database, string text, CancellationToken cancellationToken) | ||
| => ExecuteCommandAsync("/v1/rest/query", database, text, cancellationToken); | ||
| => ExecuteCommandAsync("/v2/rest/query", database, text, cancellationToken); | ||
|
|
There was a problem hiding this comment.
[HIGH] v2 endpoint change affects dot-command callers
ExecuteQueryCommandAsync now routes to /v2/rest/query. On main, GetTableSchemaAsync calls this method to run .show table {tableName} cslschema - a management-style dot-command.
The v2 query endpoint returns results in a different frame structure (DataSetHeader, DataTable frames, DataSetCompletion) compared to v1 flat JSON. If the v2 frame parser does not handle dot-command responses the same way, this would break schema retrieval. This might be the root cause of the test failures you mentioned.
Consider routing dot-commands through ExecuteControlCommandAsync (which still uses /v1/rest/mgmt), or validating that v2 parsing handles dot-command response frames correctly.
| httpRequest.Headers.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json")); | ||
| httpRequest.Headers.Add("x-ms-readonly", "true"); | ||
| httpRequest.Headers.AcceptEncoding.Add(new System.Net.Http.Headers.StringWithQualityHeaderValue("gzip")); | ||
| httpRequest.Headers.AcceptEncoding.Add(new System.Net.Http.Headers.StringWithQualityHeaderValue("deflate")); |
There was a problem hiding this comment.
[MEDIUM] Duplicate Accept-Encoding
KustoSetup.cs:24 already configures AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate on the handler. This automatically adds Accept-Encoding: gzip, deflate to outgoing requests and decompresses responses transparently.
These two manual AcceptEncoding.Add calls (lines 231-232) create duplicate header values. The handler-level config is sufficient - you can remove both lines.
| } | ||
|
|
||
| private static void AddCpuStats(JsonObject result, JsonElement resourceUsage) | ||
| { |
There was a problem hiding this comment.
[MEDIUM] JSON roundtrip allocation
This path does JsonObject -> ToJsonString() -> JsonDocument.Parse() -> Clone() - a serialize-then-reparse cycle for data already in memory.
ConvertPrimaryResultToItems in this same file already uses the Utf8JsonWriter + ArrayBufferWriter<byte> pattern to avoid the string intermediate. Same approach works here:
var buffer = new ArrayBufferWriter<byte>();
using var writer = new Utf8JsonWriter(buffer);
result.WriteTo(writer);
writer.Flush();
using var document = JsonDocument.Parse(buffer.WrittenMemory);
return document.RootElement.Clone();| httpRequest.Headers.AcceptEncoding.Add(new System.Net.Http.Headers.StringWithQualityHeaderValue("gzip")); | ||
| httpRequest.Headers.AcceptEncoding.Add(new System.Net.Http.Headers.StringWithQualityHeaderValue("deflate")); | ||
| httpRequest.Headers.Connection.Add("Keep-Alive"); | ||
|
|
There was a problem hiding this comment.
[LOW] Redundant Connection: Keep-Alive
HTTP/1.1 uses persistent connections by default. HttpClient handles connection pooling internally. This header does not change behavior and can be removed.
What does this PR do?
This PR updates the existing
kusto_querytool to use Kusto query v2 response handling and adds an optional--show-statsparameter so users can retrieve execution statistics (CPU, memory, cache, extents, network, and cross-cluster breakdown) alongside query results.It also updates tests and docs for the new behavior, keeps
/v1/rest/mgmtunchanged, and adds a changelog entry.GitHub issue number?
Closes #1786
Pre-merge Checklist
servers/Azure.Mcp.Server/changelog-entries/kusto_queryservers/Azure.Mcp.Server/README.mddocumentationeng/scripts/Process-PackageReadMe.ps1(not applicable - README not changed)/servers/Azure.Mcp.Server/docs/azmcp-commands.md./eng/scripts/Update-AzCommandsMetadata.ps1to update tool metadata in azmcp-commands.mdToolDescriptionEvaluatorwith score >= 0.4 and top-3 ranking for related promptsconsolidated-tools.json(not applicable - no new tool name)/servers/Azure.Mcp.Server/docs/e2eTestPrompts.mdValidation run
dotnet build .\mcp.sln./eng/scripts/Test-Code.ps1 -Paths Kustoinitialize+tools/list+kusto_query) againsthttps://help.kusto.windows.netwith and withoutshow-statsInvoking Livetests
Copilot submitted PRs are not trustworthy by default. Users with
writeaccess to the repo need to validate the contents of this PR before leaving a comment with the text/azp run mcp - pullrequest - live. This will trigger the necessary livetest workflows to complete required validation.