diff --git a/servers/Azure.Mcp.Server/CHANGELOG.md b/servers/Azure.Mcp.Server/CHANGELOG.md index 4d724cbed..263ce66de 100644 --- a/servers/Azure.Mcp.Server/CHANGELOG.md +++ b/servers/Azure.Mcp.Server/CHANGELOG.md @@ -15,6 +15,8 @@ The Azure MCP Server updates automatically by default whenever a new release com - Fixed problem where help option (`--help`) was showing an error and enabling it across all commands and command groups. [[#583](https://github.com/microsoft/mcp/pull/583)] +- Fixed `azmcp_kusto_table_schema` to return table schema. [[#530](https://github.com/microsoft/mcp/issues/530)] + ### Other Changes ## 0.8.2 (2025-09-25) diff --git a/tools/Azure.Mcp.Tools.Kusto/src/Services/KustoService.cs b/tools/Azure.Mcp.Tools.Kusto/src/Services/KustoService.cs index 278069935..c35f26855 100644 --- a/tools/Azure.Mcp.Tools.Kusto/src/Services/KustoService.cs +++ b/tools/Azure.Mcp.Tools.Kusto/src/Services/KustoService.cs @@ -190,10 +190,9 @@ public async Task GetTableSchemaAsync( databaseName, $".show table {tableName} cslschema", CancellationToken.None); var result = KustoResultToStringList(kustoResult); - var schema = result.FirstOrDefault(); - if (schema is not null) + if (result.Count > 0) { - return schema; + return string.Join(Environment.NewLine, result); } throw new Exception($"No schema found for table '{tableName}' in database '{databaseName}'."); } diff --git a/tools/Azure.Mcp.Tools.Kusto/tests/Azure.Mcp.Tools.Kusto.LiveTests/KustoCommandTests.cs b/tools/Azure.Mcp.Tools.Kusto/tests/Azure.Mcp.Tools.Kusto.LiveTests/KustoCommandTests.cs index ec5363922..f4394e9ec 100644 --- a/tools/Azure.Mcp.Tools.Kusto/tests/Azure.Mcp.Tools.Kusto.LiveTests/KustoCommandTests.cs +++ b/tools/Azure.Mcp.Tools.Kusto/tests/Azure.Mcp.Tools.Kusto.LiveTests/KustoCommandTests.cs @@ -17,7 +17,9 @@ public class KustoCommandTests(ITestOutputHelper output) : CommandTestsBase(output) { private const string TestDatabaseName = "ToDoLists"; + private const string TestTableName = "ToDoList"; + #region Init public override async ValueTask InitializeAsync() { await base.InitializeAsync(); // Initialize the base class first @@ -50,7 +52,9 @@ public override async ValueTask InitializeAsync() Assert.Skip("Skipping until auth fixed for Kusto"); } } + #endregion + #region Databases [Fact] public async Task Should_list_databases_in_cluster() { @@ -66,7 +70,9 @@ public async Task Should_list_databases_in_cluster() Assert.Equal(JsonValueKind.Array, databasesArray.ValueKind); Assert.NotEmpty(databasesArray.EnumerateArray()); } + #endregion + #region Tables [Fact] public async Task Should_list_kusto_tables() { @@ -84,6 +90,83 @@ public async Task Should_list_kusto_tables() Assert.NotEmpty(tablesArray.EnumerateArray()); } + [Fact] + public async Task Should_list_tables_with_direct_uri() + { + var clusterInfo = await CallToolAsync( + "azmcp_kusto_cluster_get", + new() + { + { "subscription", Settings.SubscriptionId }, + { "cluster", Settings.ResourceBaseName } + }); + + var clusterUri = clusterInfo.AssertProperty("cluster").AssertProperty("clusterUri").GetString(); + Assert.NotNull(clusterUri); + + var result = await CallToolAsync( + "azmcp_kusto_table_list", + new() + { + { "cluster-uri", clusterUri }, + { "database", TestDatabaseName } + }); + + var tablesArray = result.AssertProperty("tables"); + Assert.Equal(JsonValueKind.Array, tablesArray.ValueKind); + Assert.NotEmpty(tablesArray.EnumerateArray()); + } + + [Fact] + public async Task Should_get_table_schema() + { + var result = await CallToolAsync( + "azmcp_kusto_table_schema", + new() + { + { "subscription", Settings.SubscriptionId }, + { "cluster", Settings.ResourceBaseName }, + { "database", TestDatabaseName }, + { "table", TestTableName } + }); + + var schema = result.AssertProperty("schema").GetString(); + Assert.NotNull(schema); + Assert.Contains("Title:string", schema); + Assert.Contains("IsCompleted:bool", schema); + } + + [Fact] + public async Task Should_get_table_schema_with_direct_uri() + { + var clusterInfo = await CallToolAsync( + "azmcp_kusto_cluster_get", + new() + { + { "subscription", Settings.SubscriptionId }, + { "cluster", Settings.ResourceBaseName } + }); + + var clusterUri = clusterInfo.AssertProperty("cluster").AssertProperty("clusterUri").GetString(); + Assert.NotNull(clusterUri); + + var result = await CallToolAsync( + "azmcp_kusto_table_schema", + new() + { + { "cluster-uri", clusterUri }, + { "database", TestDatabaseName }, + { "table", TestTableName } + }); + + var schema = result.AssertProperty("schema").GetString(); + Assert.NotNull(schema); + Assert.Contains("Title:string", schema); + Assert.Contains("IsCompleted:bool", schema); + } + #endregion + + #region Query [Fact] public async Task Should_query_kusto() { @@ -94,11 +177,40 @@ public async Task Should_query_kusto() { "subscription", Settings.SubscriptionId }, { "cluster", Settings.ResourceBaseName }, { "database", TestDatabaseName }, - { "query", "ToDoList | take 1" } + { "query", $"{TestTableName} | take 1" } + }); + + var itemsArray = result.AssertProperty("items"); + Assert.Equal(JsonValueKind.Array, itemsArray.ValueKind); + Assert.NotEmpty(itemsArray.EnumerateArray()); + } + + [Fact] + public async Task Should_query_kusto_with_direct_uri() + { + var clusterInfo = await CallToolAsync( + "azmcp_kusto_cluster_get", + new() + { + { "subscription", Settings.SubscriptionId }, + { "cluster", Settings.ResourceBaseName } + }); + + var clusterUri = clusterInfo.AssertProperty("cluster").AssertProperty("clusterUri").GetString(); + Assert.NotNull(clusterUri); + + var result = await CallToolAsync( + "azmcp_kusto_query", + new() + { + { "cluster-uri", clusterUri }, + { "database", TestDatabaseName }, + { "query", $"{TestTableName} | take 1" } }); var itemsArray = result.AssertProperty("items"); Assert.Equal(JsonValueKind.Array, itemsArray.ValueKind); Assert.NotEmpty(itemsArray.EnumerateArray()); } + #endregion }