Skip to content

kusto_query: migrate query path/parsing to v2 and add --show-stats#1787

Open
gholliday wants to merge 2 commits intomicrosoft:mainfrom
gholliday:granth/kusto-query-v2-show-stats
Open

kusto_query: migrate query path/parsing to v2 and add --show-stats#1787
gholliday wants to merge 2 commits intomicrosoft:mainfrom
gholliday:granth/kusto-query-v2-show-stats

Conversation

@gholliday
Copy link
Copy Markdown
Member

What does this PR do?

This PR updates the existing kusto_query tool to use Kusto query v2 response handling and adds an optional --show-stats parameter 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/mgmt unchanged, and adds a changelog entry.

GitHub issue number?

Closes #1786

Pre-merge Checklist

  • Required for All PRs
    • Read contribution guidelines
    • PR title clearly describes the change
    • Commit history is clean with descriptive messages (cleanup guide)
    • Added comprehensive tests for new/modified functionality
    • Updated changelog entry under servers/Azure.Mcp.Server/changelog-entries/
  • For MCP tool changes:
    • One tool per PR: This PR modifies only kusto_query
    • Updated servers/Azure.Mcp.Server/README.md documentation
    • Validate README.md changes using eng/scripts/Process-PackageReadMe.ps1 (not applicable - README not changed)
    • Updated command list in /servers/Azure.Mcp.Server/docs/azmcp-commands.md
    • Ran ./eng/scripts/Update-AzCommandsMetadata.ps1 to update tool metadata in azmcp-commands.md
    • For modified tool descriptions, ran ToolDescriptionEvaluator with score >= 0.4 and top-3 ranking for related prompts
    • For tools with new names, update consolidated-tools.json (not applicable - no new tool name)
  • Extra steps for Azure MCP Server tool changes:
    • Updated test prompts in /servers/Azure.Mcp.Server/docs/e2eTestPrompts.md

Validation run

  • dotnet build .\mcp.sln
  • ./eng/scripts/Test-Code.ps1 -Paths Kusto
  • MCP smoke test over stdio protocol (initialize + tools/list + kusto_query) against https://help.kusto.windows.net with and without show-stats

Invoking Livetests

Copilot submitted PRs are not trustworthy by default. Users with write access 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.

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>
@github-actions
Copy link
Copy Markdown

Thank you for your contribution @gholliday! We will review the pull request and get back to you soon.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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/query and adds v2 frame parsing (PrimaryResult + DataSetCompletion error handling).
  • Adds --show-stats option and plumbs statistics extraction from QueryCompletionInformation frames 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.

Comment thread tools/Azure.Mcp.Tools.Kusto/src/Services/KustoService.cs Outdated
Comment thread tools/Azure.Mcp.Tools.Kusto/src/Services/KustoService.cs Outdated
Comment thread tools/Azure.Mcp.Tools.Kusto/src/Services/KustoService.cs Outdated
Comment thread tools/Azure.Mcp.Tools.Kusto/src/Services/KustoService.cs Outdated
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>
@gholliday
Copy link
Copy Markdown
Member Author

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.

@anuchandy
Copy link
Copy Markdown
Member

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

@anuchandy anuchandy added the Do Not Merge Do Not Merge / WIP PRs label Mar 17, 2026
Copy link
Copy Markdown
Contributor

@jongio jongio left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. 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.

  2. v2 endpoint affects dot-command callers (HIGH) - GetTableSchemaAsync sends .show table through ExecuteQueryCommandAsync, 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"));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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"));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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)
{
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

[BUG] kusto_query does not expose query execution stats for users

6 participants