diff --git a/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.Tests/Areas/Tools/ToolsListCommandTests.cs b/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.Tests/Areas/Tools/ToolsListCommandTests.cs
index d03abe1622..a73703ac2e 100644
--- a/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.Tests/Areas/Tools/ToolsListCommandTests.cs
+++ b/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.Tests/Areas/Tools/ToolsListCommandTests.cs
@@ -41,23 +41,23 @@ public ToolsListCommandTests()
///
/// Helper method to deserialize response results to CommandInfo list
///
- private static ToolsListCommand.ToolsListResult DeserializeCommandsResults(CommandResponse response) =>
- DeserializeJson(response, () => new([], null));
+ private static ToolsListCommand.ToolsListCommandResult DeserializeCommandsResults(CommandResponse response) =>
+ DeserializeJson(response);
///
/// Helper method to deserialize response results to ToolNamesResult
///
- private static ToolsListCommand.ToolsListResult DeserializeResult(CommandResponse response) =>
- DeserializeJson(response, () => new(null, []));
+ private static ToolsListCommand.ToolsListCommandResult DeserializeResult(CommandResponse response) =>
+ DeserializeJson(response);
- private static ToolsListCommand.ToolsListResult DeserializeJson(CommandResponse response, Func defaultValueFactory)
+ private static ToolsListCommand.ToolsListCommandResult DeserializeJson(CommandResponse response)
{
Assert.NotNull(response);
Assert.NotNull(response.Results);
Assert.Equal(HttpStatusCode.OK, response.Status);
var json = JsonSerializer.Serialize(response.Results);
- var result = JsonSerializer.Deserialize(json, ModelsJsonContext.Default.ToolsListResult) ?? defaultValueFactory();
+ var result = JsonSerializer.Deserialize(json, ModelsJsonContext.Default.ToolsListCommandResult);
Assert.NotNull(result);
return result;
@@ -79,6 +79,12 @@ public async Task ExecuteAsync_WithValidContext_ReturnsCommandInfoList()
Assert.NotNull(result.Commands);
Assert.NotEmpty(result.Commands);
+ Assert.Null(result.Names);
+
+ var json = JsonSerializer.SerializeToElement(response.Results);
+ Assert.Equal(JsonValueKind.Object, json.ValueKind);
+ Assert.True(json.TryGetProperty("commands", out _));
+ Assert.False(json.TryGetProperty("names", out _));
foreach (var command in result.Commands)
{
@@ -114,7 +120,7 @@ public async Task ExecuteAsync_JsonSerializationStressTest_HandlesLargeResults()
var json = JsonSerializer.Serialize(response.Results);
// Verify JSON round-trip preserves all data
- var serializedJson = JsonSerializer.Serialize(result, ModelsJsonContext.Default.ToolsListResult);
+ var serializedJson = JsonSerializer.Serialize(result, ModelsJsonContext.Default.ToolsListCommandResult);
Assert.Equal(json, serializedJson);
}
diff --git a/core/Microsoft.Mcp.Core/src/Areas/Tools/Commands/ToolsListCommand.cs b/core/Microsoft.Mcp.Core/src/Areas/Tools/Commands/ToolsListCommand.cs
index 3ace062edf..6b81c3f54e 100644
--- a/core/Microsoft.Mcp.Core/src/Areas/Tools/Commands/ToolsListCommand.cs
+++ b/core/Microsoft.Mcp.Core/src/Areas/Tools/Commands/ToolsListCommand.cs
@@ -1,7 +1,6 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
-using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
@@ -30,7 +29,7 @@ List all available commands and their tools in a hierarchical structure. This co
LocalRequired = false,
Secret = false)]
public sealed class ToolsListCommand(IServiceProvider serviceProvider, ILogger logger)
- : BaseCommand
+ : BaseCommand
{
private static readonly HashSet s_ignored = new(StringComparer.OrdinalIgnoreCase) { "server", "tools" };
private static readonly HashSet s_surfaced = new(StringComparer.OrdinalIgnoreCase) { "extension" };
@@ -82,11 +81,15 @@ public override async Task ExecuteAsync(CommandContext context,
if (options.NameOnly)
{
var namespaceNames = namespaceCommands.Select(nc => nc.Command).ToList();
- context.Response.Results = ResponseResult.Create(new(null, namespaceNames), ModelsJsonContext.Default.ToolsListResult);
+ context.Response.Results = ResponseResult.Create(
+ new(null, namespaceNames),
+ ModelsJsonContext.Default.ToolsListCommandResult);
return context.Response;
}
- context.Response.Results = ResponseResult.Create(new(namespaceCommands, null), ModelsJsonContext.Default.ToolsListResult);
+ context.Response.Results = ResponseResult.Create(
+ new(namespaceCommands, null),
+ ModelsJsonContext.Default.ToolsListCommandResult);
return context.Response;
}
@@ -103,7 +106,9 @@ public override async Task ExecuteAsync(CommandContext context,
var toolNames = allToolNames.OrderBy(name => name, StringComparer.OrdinalIgnoreCase).ToList();
- context.Response.Results = ResponseResult.Create(new(null, toolNames), ModelsJsonContext.Default.ToolsListResult);
+ context.Response.Results = ResponseResult.Create(
+ new(null, toolNames),
+ ModelsJsonContext.Default.ToolsListCommandResult);
return context.Response;
}
@@ -118,7 +123,9 @@ public override async Task ExecuteAsync(CommandContext context,
var tools = allTools.ToList();
- context.Response.Results = ResponseResult.Create(new(tools, null), ModelsJsonContext.Default.ToolsListResult);
+ context.Response.Results = ResponseResult.Create(
+ new(tools, null),
+ ModelsJsonContext.Default.ToolsListCommandResult);
return context.Response;
}
catch (Exception ex)
@@ -167,56 +174,10 @@ private static CommandInfo CreateCommand(string tokenizedName, IBaseCommand comm
};
}
- [JsonConverter(typeof(ToolsListResultConverter))]
- public sealed record ToolsListResult(
+ public sealed record ToolsListCommandResult(
[property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] List? Commands,
[property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] List? Names);
- public sealed class ToolsListResultConverter : JsonConverter
- {
- public override ToolsListResult? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
- {
- if (reader.TokenType == JsonTokenType.StartObject)
- {
- List? names = null;
- while (reader.Read() && reader.TokenType != JsonTokenType.EndObject)
- {
- if (reader.TokenType == JsonTokenType.PropertyName && reader.GetString() == "names")
- {
- reader.Read(); // Move to the value of "names"
- names = JsonSerializer.Deserialize(ref reader, ModelsJsonContext.Default.ListString);
- }
- }
- return new(null, names);
- }
- else if (reader.TokenType == JsonTokenType.StartArray)
- {
- var commands = JsonSerializer.Deserialize(ref reader, ModelsJsonContext.Default.ListCommandInfo);
- return new(commands, null);
- }
-
- throw new JsonException("Invalid JSON format for ToolsListResult.");
- }
-
- public override void Write(Utf8JsonWriter writer, ToolsListResult? value, JsonSerializerOptions options)
- {
- if (value is not null)
- {
- if (value.Commands is not null)
- {
- JsonSerializer.Serialize(writer, value.Commands, ModelsJsonContext.Default.ListCommandInfo);
- }
- else if (value.Names is not null)
- {
- writer.WriteStartObject();
- writer.WritePropertyName("names");
- JsonSerializer.Serialize(writer, value.Names, ModelsJsonContext.Default.ListString);
- writer.WriteEndObject();
- }
- }
- }
- }
-
private static void SearchCommandInCommandGroup(string commandPrefix, CommandGroup searchedGroup, List foundCommands)
{
var commands = CommandFactory.GetVisibleCommands(searchedGroup.Commands).Select(kvp =>
diff --git a/core/Microsoft.Mcp.Core/src/Models/ModelsJsonContext.cs b/core/Microsoft.Mcp.Core/src/Models/ModelsJsonContext.cs
index f0dc6fd13e..a65d914fde 100644
--- a/core/Microsoft.Mcp.Core/src/Models/ModelsJsonContext.cs
+++ b/core/Microsoft.Mcp.Core/src/Models/ModelsJsonContext.cs
@@ -9,10 +9,9 @@
namespace Microsoft.Mcp.Core.Models;
[JsonSerializable(typeof(List))]
-[JsonSerializable(typeof(List))]
[JsonSerializable(typeof(CommandResponse))]
[JsonSerializable(typeof(ETag), TypeInfoPropertyName = "McpETag")]
[JsonSerializable(typeof(ToolMetadata))]
-[JsonSerializable(typeof(ToolsListCommand.ToolsListResult))]
+[JsonSerializable(typeof(ToolsListCommand.ToolsListCommandResult))]
[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
public sealed partial class ModelsJsonContext : JsonSerializerContext;
diff --git a/eng/scripts/New-ToolsListFile.ps1 b/eng/scripts/New-ToolsListFile.ps1
index b5bf944b67..250d1c4b00 100644
--- a/eng/scripts/New-ToolsListFile.ps1
+++ b/eng/scripts/New-ToolsListFile.ps1
@@ -51,7 +51,10 @@ try
# Parse, sort options within each tool by name, and re-serialize
$json = $outLines -join "`n" | ConvertFrom-Json
- foreach ($tool in $json.results) {
+ $commandsProperty = $json.results.PSObject.Properties['commands']
+ $tools = if ($null -ne $commandsProperty) { $commandsProperty.Value } else { $json.results }
+
+ foreach ($tool in $tools) {
if ($tool.option) {
$tool.option = @($tool.option | Sort-Object -Property name)
}
diff --git a/eng/scripts/Test-ToolNameLength.ps1 b/eng/scripts/Test-ToolNameLength.ps1
index 9e73783a58..49ede919d8 100644
--- a/eng/scripts/Test-ToolNameLength.ps1
+++ b/eng/scripts/Test-ToolNameLength.ps1
@@ -130,7 +130,8 @@ foreach ($serverInfo in $serversToTest) {
}
$toolsResult = $toolsJson | ConvertFrom-Json
- $tools = $toolsResult.results
+ $commandsProperty = $toolsResult.results.PSObject.Properties['commands']
+ $tools = if ($null -ne $commandsProperty) { $commandsProperty.Value } else { $toolsResult.results }
if ($null -eq $tools -or $tools.Count -eq 0) {
Write-Warning "No tools found in $currentServerName - skipping"
diff --git a/eng/scripts/Update-AzCommandsMetadata.ps1 b/eng/scripts/Update-AzCommandsMetadata.ps1
index 5378eb0483..0adb40e4fe 100644
--- a/eng/scripts/Update-AzCommandsMetadata.ps1
+++ b/eng/scripts/Update-AzCommandsMetadata.ps1
@@ -123,7 +123,10 @@ try {
# Build a dictionary of command -> metadata
Write-Host "Building command metadata dictionary..." -ForegroundColor Yellow
$commandMetadata = @{}
-foreach ($tool in $toolsData.results) {
+$commandsProperty = $toolsData.results.PSObject.Properties['commands']
+$tools = if ($null -ne $commandsProperty) { $commandsProperty.Value } else { $toolsData.results }
+
+foreach ($tool in $tools) {
if ($tool.command -and $tool.metadata) {
$commandMetadata[$tool.command] = $tool.metadata
}
diff --git a/eng/tools/ToolDescriptionEvaluator/scripts/Generate-GroupedPromptsJson.ps1 b/eng/tools/ToolDescriptionEvaluator/scripts/Generate-GroupedPromptsJson.ps1
index 469fdcb944..ba4a2bc0fd 100644
--- a/eng/tools/ToolDescriptionEvaluator/scripts/Generate-GroupedPromptsJson.ps1
+++ b/eng/tools/ToolDescriptionEvaluator/scripts/Generate-GroupedPromptsJson.ps1
@@ -224,11 +224,13 @@ function Invoke-NamespaceGeneration {
if (-not (Test-Path $NamespaceToolsPath)) { throw "Namespace tools file not found: $NamespaceToolsPath" }
$namespaceJson = Get-Content -Raw -Path $NamespaceToolsPath | ConvertFrom-Json
- if (-not $namespaceJson.results) { throw "Input namespace tools JSON missing 'results' array" }
+ $commandsProperty = $namespaceJson.results.PSObject.Properties['commands']
+ $namespaceCommands = if ($null -ne $commandsProperty) { $commandsProperty.Value } else { $namespaceJson.results }
+ if (-not $namespaceCommands) { throw "Input namespace tools JSON is missing commands" }
$warnings = @()
$outputMap = [ordered]@{}
- foreach ($ns in $namespaceJson.results) {
+ foreach ($ns in $namespaceCommands) {
if (-not $ns.name) { continue }
$commandStrings = @(Get-NamespaceCommandStrings -Node $ns -AllPromptKeys $AllPromptKeys)
diff --git a/eng/tools/ToolDescriptionEvaluator/scripts/Update-ToolsJson.ps1 b/eng/tools/ToolDescriptionEvaluator/scripts/Update-ToolsJson.ps1
index 6c24c3fd0f..386f99abad 100644
--- a/eng/tools/ToolDescriptionEvaluator/scripts/Update-ToolsJson.ps1
+++ b/eng/tools/ToolDescriptionEvaluator/scripts/Update-ToolsJson.ps1
@@ -134,7 +134,19 @@ try {
# Try to parse the JSON to verify it's valid
try {
$json = Get-Content $jsonFile -Raw | ConvertFrom-Json
- $toolCount = if ($null -ne $json.results) { $json.results.Count } elseif ($null -ne $json.tools) { $json.tools.Count } else { $null }
+ $commandsProperty = $json.results.PSObject.Properties['commands']
+ $toolCount = if ($null -ne $commandsProperty) {
+ $commandsProperty.Value.Count
+ }
+ elseif ($null -ne $json.results) {
+ $json.results.Count
+ }
+ elseif ($null -ne $json.tools) {
+ $json.tools.Count
+ }
+ else {
+ $null
+ }
if ($null -ne $toolCount) {
Write-Host "Contains $toolCount tools" -ForegroundColor Cyan
diff --git a/eng/tools/ToolDescriptionEvaluator/src/Models/ListToolsPayload.cs b/eng/tools/ToolDescriptionEvaluator/src/Models/ListToolsPayload.cs
new file mode 100644
index 0000000000..9a27bf581f
--- /dev/null
+++ b/eng/tools/ToolDescriptionEvaluator/src/Models/ListToolsPayload.cs
@@ -0,0 +1,9 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Text.Json.Serialization;
+
+namespace ToolSelection.Models;
+
+public sealed record ListToolsPayload(
+ [property: JsonPropertyName("commands")] List? Commands);
diff --git a/eng/tools/ToolDescriptionEvaluator/src/Models/McpModels.cs b/eng/tools/ToolDescriptionEvaluator/src/Models/McpModels.cs
index ed57214a5e..5db5266f50 100644
--- a/eng/tools/ToolDescriptionEvaluator/src/Models/McpModels.cs
+++ b/eng/tools/ToolDescriptionEvaluator/src/Models/McpModels.cs
@@ -125,7 +125,14 @@ public class ListToolsResult
public string? Message { get; set; }
[JsonPropertyName("results")]
- public List? Tools { get; set; }
+ public ListToolsPayload? Results { get; set; }
+
+ [JsonIgnore]
+ public List? Tools
+ {
+ get => Results?.Commands;
+ set => Results = value is null ? null : new(value);
+ }
[JsonPropertyName("consolidated_tools")]
public List? ConsolidatedTools { get; set; }
diff --git a/eng/tools/ToolDescriptionEvaluator/src/Models/SourceGenerationContext.cs b/eng/tools/ToolDescriptionEvaluator/src/Models/SourceGenerationContext.cs
index 1324db6567..56b4c8172e 100644
--- a/eng/tools/ToolDescriptionEvaluator/src/Models/SourceGenerationContext.cs
+++ b/eng/tools/ToolDescriptionEvaluator/src/Models/SourceGenerationContext.cs
@@ -7,6 +7,7 @@ namespace ToolSelection.Models;
[JsonSourceGenerationOptions(WriteIndented = true, PropertyNameCaseInsensitive = true)]
[JsonSerializable(typeof(ListToolsResult))]
+[JsonSerializable(typeof(ListToolsPayload))]
[JsonSerializable(typeof(List))]
[JsonSerializable(typeof(Tool))]
[JsonSerializable(typeof(Dictionary>), TypeInfoPropertyName = "DictionaryStringListString")]
diff --git a/eng/tools/ToolMetadataExporter/tests/ToolMetadataExporter.UnitTests/Models/ListToolsResultTests.cs b/eng/tools/ToolMetadataExporter/tests/ToolMetadataExporter.UnitTests/Models/ListToolsResultTests.cs
new file mode 100644
index 0000000000..d036705b06
--- /dev/null
+++ b/eng/tools/ToolMetadataExporter/tests/ToolMetadataExporter.UnitTests/Models/ListToolsResultTests.cs
@@ -0,0 +1,36 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Text.Json;
+using ToolSelection.Models;
+using Xunit;
+
+namespace ToolMetadataExporter.UnitTests.Models;
+
+public class ListToolsResultTests
+{
+ [Fact]
+ public void Deserialize_ReadsCommandsFromObjectRoot()
+ {
+ const string json = """
+ {
+ "status": 200,
+ "results": {
+ "commands": [
+ {
+ "name": "list",
+ "command": "tools list"
+ }
+ ]
+ }
+ }
+ """;
+
+ var result = Assert.IsType(
+ JsonSerializer.Deserialize(json, SourceGenerationContext.Default.ListToolsResult));
+ var tool = Assert.Single(Assert.IsType>(result.Tools));
+
+ Assert.Equal("list", tool.Name);
+ Assert.Equal("tools list", tool.Command);
+ }
+}
diff --git a/servers/Azure.Mcp.Server/changelog-entries/1784946361604.yaml b/servers/Azure.Mcp.Server/changelog-entries/1784946361604.yaml
new file mode 100644
index 0000000000..8fb6c30321
--- /dev/null
+++ b/servers/Azure.Mcp.Server/changelog-entries/1784946361604.yaml
@@ -0,0 +1,14 @@
+changes:
+ - section: "Breaking Changes"
+ description: |
+ Normalized result payloads for the following tools:
+ - `tools list` now returns either `{ commands }` or `{ names }`, omitting the inactive field.
+ - `advisor recommendation apply` now returns the resulting rules in a `{ rules }` object.
+ - `azurebestpractices get`, `azurebestpractices ai_app`, and `azureterraformbestpractices get` now return the resulting best practices in a `{ bestPractices }` object.
+ - `functions language list` and `functions project get` now return the resulting singleton values as direct objects.
+ - `monitor healthmodels list` now returns the resulting health models in a `{ healthModels }` object.
+ - `monitor resource log query` and `monitor workspace log query` now return the resulting rows in a `{ results }` object.
+ - `monitor instrumentation orchestrator-start`, `orchestrator-next`, `send-brownfield-analysis`, and `send-enhancement-select` now return the resulting scalar value in a `{ result }` object.
+ - `search index query` now returns the resulting rows in a `{ results }` object.
+ - `sql server get` now returns the resulting servers in a `{ servers }` object.
+ - `wellarchitectedframework serviceguide get` now returns the resulting guidance in a `{ guidance }` object.
diff --git a/servers/Fabric.Mcp.Server/changelog-entries/1784946362379.yaml b/servers/Fabric.Mcp.Server/changelog-entries/1784946362379.yaml
new file mode 100644
index 0000000000..27b63ceea5
--- /dev/null
+++ b/servers/Fabric.Mcp.Server/changelog-entries/1784946362379.yaml
@@ -0,0 +1,12 @@
+changes:
+ - section: "Breaking Changes"
+ description: |
+ Normalized result payloads for the following tools:
+ - `docs best-practices` now returns the resulting best practices in a `{ bestPractices }` object.
+ - `docs item-definitions` now returns the resulting definition string in a `{ definition }` object.
+ - `docs platform-api-spec` and `docs workload-api-spec` now return the produced public API in a `{ publicApi }` object.
+ - `onelake create or update data access role` and `onelake get data access role` now return the produced roles in a `{ roles }` object.
+ - `onelake list data access roles` now returns `{ roles }` instead of `{ value }`.
+ - `onelake get settings` now returns the produced settings in a `{ settings }` object.
+ - `onelake create shortcut` and `onelake get shortcut` now return the produced shortcut in a `{ shortcut }` object.
+ - `onelake list shortcuts` now returns `{ shortcuts }` instead of `{ value }`.
diff --git a/tools/Azure.Mcp.Tools.Advisor/src/Commands/AdvisorJsonContext.cs b/tools/Azure.Mcp.Tools.Advisor/src/Commands/AdvisorJsonContext.cs
index 957af115cb..3f7dbc2ed2 100644
--- a/tools/Azure.Mcp.Tools.Advisor/src/Commands/AdvisorJsonContext.cs
+++ b/tools/Azure.Mcp.Tools.Advisor/src/Commands/AdvisorJsonContext.cs
@@ -7,7 +7,7 @@ namespace Azure.Mcp.Tools.Advisor.Commands;
[JsonSerializable(typeof(RecommendationListCommand.RecommendationListResult))]
[JsonSerializable(typeof(RecommendationTypeListCommand.RecommendationTypeListResult))]
[JsonSerializable(typeof(RecommendationSummaryCommand.RecommendationSummaryResult))]
-[JsonSerializable(typeof(List))]
+[JsonSerializable(typeof(RecommendationApplyCommand.RecommendationApplyCommandResult))]
[JsonSerializable(typeof(RecommendationData))]
[JsonSerializable(typeof(Models.Recommendation))]
[JsonSerializable(typeof(Models.RecommendationType))]
diff --git a/tools/Azure.Mcp.Tools.Advisor/src/Commands/Recommendation/RecommendationApplyCommand.cs b/tools/Azure.Mcp.Tools.Advisor/src/Commands/Recommendation/RecommendationApplyCommand.cs
index 9edaba9fed..92a756e386 100644
--- a/tools/Azure.Mcp.Tools.Advisor/src/Commands/Recommendation/RecommendationApplyCommand.cs
+++ b/tools/Azure.Mcp.Tools.Advisor/src/Commands/Recommendation/RecommendationApplyCommand.cs
@@ -24,7 +24,7 @@ namespace Azure.Mcp.Tools.Advisor.Commands.Recommendation;
Secret = false
)]
public sealed class RecommendationApplyCommand(ILogger logger)
- : BaseCommand>
+ : BaseCommand
{
private readonly ILogger _logger = logger;
private static readonly ConcurrentDictionary s_advisorRecommendationRulesCache = new();
@@ -51,7 +51,9 @@ public override Task ExecuteAsync(CommandContext context, Recom
var resourceFileName = $"{options.Resource}.json";
var recommendationApplyRules = GetAdvisorRecommendationRules(resourceFileName);
- context.Response.Results = ResponseResult.Create([recommendationApplyRules], AdvisorJsonContext.Default.ListString);
+ context.Response.Results = ResponseResult.Create(
+ new([recommendationApplyRules]),
+ AdvisorJsonContext.Default.RecommendationApplyCommandResult);
context.Activity?.AddTag("RecommendationRules_Resource", options.Resource);
}
@@ -102,4 +104,6 @@ private static HashSet LoadAvailableResources()
return resources;
}
+
+ public sealed record RecommendationApplyCommandResult(List Rules);
}
diff --git a/tools/Azure.Mcp.Tools.Advisor/tests/Azure.Mcp.Tools.Advisor.Tests/Recommendation/RecommendationApplyCommandTests.cs b/tools/Azure.Mcp.Tools.Advisor/tests/Azure.Mcp.Tools.Advisor.Tests/Recommendation/RecommendationApplyCommandTests.cs
index a973ab5fc2..bbe3c6960c 100644
--- a/tools/Azure.Mcp.Tools.Advisor/tests/Azure.Mcp.Tools.Advisor.Tests/Recommendation/RecommendationApplyCommandTests.cs
+++ b/tools/Azure.Mcp.Tools.Advisor/tests/Azure.Mcp.Tools.Advisor.Tests/Recommendation/RecommendationApplyCommandTests.cs
@@ -61,11 +61,11 @@ public async Task ExecuteAsync_DeserializationValidation(string resource)
{
var response = await ExecuteCommandAsync("--resource", resource);
- var result = ValidateAndDeserializeResponse(response, AdvisorJsonContext.Default.ListString);
+ var result = ValidateAndDeserializeResponse(response, AdvisorJsonContext.Default.RecommendationApplyCommandResult);
Assert.NotNull(result);
- Assert.NotEmpty(result);
- Assert.Contains("rules", result[0]);
+ Assert.NotEmpty(result.Rules);
+ Assert.Contains("rules", result.Rules[0]);
}
[Fact]
@@ -128,12 +128,12 @@ public async Task ExecuteAsync_ReturnsCachedResultOnSubsequentCalls()
var response1 = await ExecuteCommandAsync("--resource", "keyvault_vaults");
var response2 = await ExecuteCommandAsync("--resource", "keyvault_vaults");
- var result1 = ValidateAndDeserializeResponse(response1, AdvisorJsonContext.Default.ListString);
- var result2 = ValidateAndDeserializeResponse(response2, AdvisorJsonContext.Default.ListString);
+ var result1 = ValidateAndDeserializeResponse(response1, AdvisorJsonContext.Default.RecommendationApplyCommandResult);
+ var result2 = ValidateAndDeserializeResponse(response2, AdvisorJsonContext.Default.RecommendationApplyCommandResult);
- Assert.Single(result1);
- Assert.Single(result2);
- Assert.Equal(result1[0], result2[0]);
+ Assert.Single(result1.Rules);
+ Assert.Single(result2.Rules);
+ Assert.Equal(result1.Rules[0], result2.Rules[0]);
}
[Theory]
@@ -156,8 +156,8 @@ public async Task ExecuteAsync_AllResources_ReturnValidRules(string resource)
{
var response = await ExecuteCommandAsync("--resource", resource);
- var result = ValidateAndDeserializeResponse(response, AdvisorJsonContext.Default.ListString);
- Assert.NotEmpty(result);
- Assert.Contains("rules", result[0]);
+ var result = ValidateAndDeserializeResponse(response, AdvisorJsonContext.Default.RecommendationApplyCommandResult);
+ Assert.NotEmpty(result.Rules);
+ Assert.Contains("rules", result.Rules[0]);
}
}
diff --git a/tools/Azure.Mcp.Tools.AzureBestPractices/src/Commands/AIAppBestPracticesCommand.cs b/tools/Azure.Mcp.Tools.AzureBestPractices/src/Commands/AIAppBestPracticesCommand.cs
index 15598ef9c2..4983224d85 100644
--- a/tools/Azure.Mcp.Tools.AzureBestPractices/src/Commands/AIAppBestPracticesCommand.cs
+++ b/tools/Azure.Mcp.Tools.AzureBestPractices/src/Commands/AIAppBestPracticesCommand.cs
@@ -26,7 +26,8 @@ namespace Azure.Mcp.Tools.AzureBestPractices.Commands;
ReadOnly = true,
Secret = false,
LocalRequired = false)]
-public sealed class AIAppBestPracticesCommand(ILogger logger) : BaseCommand>
+public sealed class AIAppBestPracticesCommand(ILogger logger)
+ : BaseCommand
{
private readonly ILogger _logger = logger;
private static readonly string s_bestPracticesText = LoadBestPracticesText();
@@ -57,7 +58,9 @@ public override Task ExecuteAsync(CommandContext context, Empty
{
var bestPractices = GetBestPracticesText();
context.Response.Status = HttpStatusCode.OK;
- context.Response.Results = ResponseResult.Create([bestPractices], AzureBestPracticesJsonContext.Default.ListString);
+ context.Response.Results = ResponseResult.Create(
+ new([bestPractices]),
+ AzureBestPracticesJsonContext.Default.AIAppBestPracticesCommandResult);
context.Response.Message = string.Empty;
}
catch (Exception ex)
@@ -68,4 +71,6 @@ public override Task ExecuteAsync(CommandContext context, Empty
return Task.FromResult(context.Response);
}
+
+ public sealed record AIAppBestPracticesCommandResult(List BestPractices);
}
diff --git a/tools/Azure.Mcp.Tools.AzureBestPractices/src/Commands/AzureBestPracticesJsonContext.cs b/tools/Azure.Mcp.Tools.AzureBestPractices/src/Commands/AzureBestPracticesJsonContext.cs
index abb3090f5e..52fbfc0cb3 100644
--- a/tools/Azure.Mcp.Tools.AzureBestPractices/src/Commands/AzureBestPracticesJsonContext.cs
+++ b/tools/Azure.Mcp.Tools.AzureBestPractices/src/Commands/AzureBestPracticesJsonContext.cs
@@ -5,6 +5,7 @@
namespace Azure.Mcp.Tools.AzureBestPractices.Commands;
-[JsonSerializable(typeof(List))]
+[JsonSerializable(typeof(AIAppBestPracticesCommand.AIAppBestPracticesCommandResult))]
+[JsonSerializable(typeof(BestPracticesCommand.BestPracticesCommandResult))]
[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
internal partial class AzureBestPracticesJsonContext : JsonSerializerContext;
diff --git a/tools/Azure.Mcp.Tools.AzureBestPractices/src/Commands/BestPracticesCommand.cs b/tools/Azure.Mcp.Tools.AzureBestPractices/src/Commands/BestPracticesCommand.cs
index 0e73a485d2..40e768b961 100644
--- a/tools/Azure.Mcp.Tools.AzureBestPractices/src/Commands/BestPracticesCommand.cs
+++ b/tools/Azure.Mcp.Tools.AzureBestPractices/src/Commands/BestPracticesCommand.cs
@@ -32,7 +32,8 @@ it belongs to the Azure Best Practices category.
ReadOnly = true,
Secret = false,
LocalRequired = false)]
-public sealed class BestPracticesCommand(ILogger logger) : BaseCommand>
+public sealed class BestPracticesCommand(ILogger logger)
+ : BaseCommand
{
private readonly ILogger _logger = logger;
private static readonly ConcurrentDictionary s_bestPracticesCache = [];
@@ -77,7 +78,9 @@ public override Task ExecuteAsync(CommandContext context, BestP
var bestPractices = GetBestPracticesText(resourceFileName);
context.Response.Status = HttpStatusCode.OK;
- context.Response.Results = ResponseResult.Create([bestPractices], AzureBestPracticesJsonContext.Default.ListString);
+ context.Response.Results = ResponseResult.Create(
+ new([bestPractices]),
+ AzureBestPracticesJsonContext.Default.BestPracticesCommandResult);
context.Response.Message = string.Empty;
context.Activity?.AddTag("BestPractices_Resource", options.Resource);
@@ -159,4 +162,6 @@ private static string LoadBestPracticesText(string resourceFileName)
return EmbeddedResourceHelper.ReadEmbeddedResource(assembly, resourceName);
}
}
+
+ public sealed record BestPracticesCommandResult(List BestPractices);
}
diff --git a/tools/Azure.Mcp.Tools.AzureBestPractices/tests/Azure.Mcp.Tools.AzureBestPractices.Tests/AIAppBestPracticesCommandTests.cs b/tools/Azure.Mcp.Tools.AzureBestPractices/tests/Azure.Mcp.Tools.AzureBestPractices.Tests/AIAppBestPracticesCommandTests.cs
index 272e1891e3..bd5eb82f2d 100644
--- a/tools/Azure.Mcp.Tools.AzureBestPractices/tests/Azure.Mcp.Tools.AzureBestPractices.Tests/AIAppBestPracticesCommandTests.cs
+++ b/tools/Azure.Mcp.Tools.AzureBestPractices/tests/Azure.Mcp.Tools.AzureBestPractices.Tests/AIAppBestPracticesCommandTests.cs
@@ -15,13 +15,13 @@ public async Task ExecuteAsync_ReturnsAzureAIAppBestPractices()
var response = await ExecuteCommandAsync([]);
// Assert
- var result = ValidateAndDeserializeResponse(response, AzureBestPracticesJsonContext.Default.ListString);
+ var result = ValidateAndDeserializeResponse(response, AzureBestPracticesJsonContext.Default.AIAppBestPracticesCommandResult);
- Assert.Contains("Microsoft Agent Framework", result[0]);
- Assert.Contains("AIProjectClient", result[0]);
- Assert.Contains("Build and Verification", result[0]);
- Assert.Contains("Understanding AI Models Hierarchy", result[0]);
- Assert.Contains("CORRECT Pattern", result[0]);
+ Assert.Contains("Microsoft Agent Framework", result.BestPractices[0]);
+ Assert.Contains("AIProjectClient", result.BestPractices[0]);
+ Assert.Contains("Build and Verification", result.BestPractices[0]);
+ Assert.Contains("Understanding AI Models Hierarchy", result.BestPractices[0]);
+ Assert.Contains("CORRECT Pattern", result.BestPractices[0]);
}
[Fact]
diff --git a/tools/Azure.Mcp.Tools.AzureBestPractices/tests/Azure.Mcp.Tools.AzureBestPractices.Tests/BestPracticesCommandTests.cs b/tools/Azure.Mcp.Tools.AzureBestPractices/tests/Azure.Mcp.Tools.AzureBestPractices.Tests/BestPracticesCommandTests.cs
index a029b70e20..31f6e8df03 100644
--- a/tools/Azure.Mcp.Tools.AzureBestPractices/tests/Azure.Mcp.Tools.AzureBestPractices.Tests/BestPracticesCommandTests.cs
+++ b/tools/Azure.Mcp.Tools.AzureBestPractices/tests/Azure.Mcp.Tools.AzureBestPractices.Tests/BestPracticesCommandTests.cs
@@ -16,10 +16,10 @@ public async Task ExecuteAsync_GeneralCodeGeneration_ReturnsAzureBestPractices()
var response = await ExecuteCommandAsync("--resource", "general", "--action", "code-generation");
// Assert
- var result = ValidateAndDeserializeResponse(response, AzureBestPracticesJsonContext.Default.ListString);
+ var result = ValidateAndDeserializeResponse(response, AzureBestPracticesJsonContext.Default.BestPracticesCommandResult);
- Assert.Contains("Implement retry logic with exponential backoff for transient failures", result[0]);
- Assert.Contains("Managed Identity (Azure-hosted)", result[0]);
+ Assert.Contains("Implement retry logic with exponential backoff for transient failures", result.BestPractices[0]);
+ Assert.Contains("Managed Identity (Azure-hosted)", result.BestPractices[0]);
}
[Fact]
@@ -28,10 +28,10 @@ public async Task ExecuteAsync_GeneralDeployment_ReturnsAzureBestPractices()
var response = await ExecuteCommandAsync("--resource", "general", "--action", "deployment");
// Assert
- var result = ValidateAndDeserializeResponse(response, AzureBestPracticesJsonContext.Default.ListString);
+ var result = ValidateAndDeserializeResponse(response, AzureBestPracticesJsonContext.Default.BestPracticesCommandResult);
- Assert.Contains("Your IaC files must include:", result[0]);
- Assert.Contains("Quality requirements for IaC files:", result[0]);
+ Assert.Contains("Your IaC files must include:", result.BestPractices[0]);
+ Assert.Contains("Quality requirements for IaC files:", result.BestPractices[0]);
}
[Fact]
@@ -40,10 +40,10 @@ public async Task ExecuteAsync_AzureFunctionsCodeGeneration_ReturnsAzureBestPrac
var response = await ExecuteCommandAsync("--resource", "azurefunctions", "--action", "code-generation");
// Assert
- var result = ValidateAndDeserializeResponse(response, AzureBestPracticesJsonContext.Default.ListString);
+ var result = ValidateAndDeserializeResponse(response, AzureBestPracticesJsonContext.Default.BestPracticesCommandResult);
- Assert.Contains("Use the latest programming models (v4 for TypeScript/JavaScript, v2 for Python)", result[0]);
- Assert.Contains("Azure Functions Core Tools for creating Function Apps", result[0]);
+ Assert.Contains("Use the latest programming models (v4 for TypeScript/JavaScript, v2 for Python)", result.BestPractices[0]);
+ Assert.Contains("Azure Functions Core Tools for creating Function Apps", result.BestPractices[0]);
}
[Fact]
@@ -52,12 +52,12 @@ public async Task ExecuteAsync_AzureFunctionsDeployment_ReturnsAzureBestPractice
var response = await ExecuteCommandAsync("--resource", "azurefunctions", "--action", "deployment");
// Assert
- var result = ValidateAndDeserializeResponse(response, AzureBestPracticesJsonContext.Default.ListString);
+ var result = ValidateAndDeserializeResponse(response, AzureBestPracticesJsonContext.Default.BestPracticesCommandResult);
- Assert.Contains("Flex Consumption plan (FC1)", result[0]);
- Assert.Contains("Always use Linux OS for Python", result[0]);
- Assert.Contains("Function authentication", result[0]);
- Assert.Contains("Application Insights", result[0]);
+ Assert.Contains("Flex Consumption plan (FC1)", result.BestPractices[0]);
+ Assert.Contains("Always use Linux OS for Python", result.BestPractices[0]);
+ Assert.Contains("Function authentication", result.BestPractices[0]);
+ Assert.Contains("Application Insights", result.BestPractices[0]);
}
[Fact]
@@ -66,12 +66,12 @@ public async Task ExecuteAsync_StaticWebAppAll_ReturnsAzureBestPractices()
var response = await ExecuteCommandAsync("--resource", "static-web-app", "--action", "all");
// Assert
- var result = ValidateAndDeserializeResponse(response, AzureBestPracticesJsonContext.Default.ListString);
+ var result = ValidateAndDeserializeResponse(response, AzureBestPracticesJsonContext.Default.BestPracticesCommandResult);
- Assert.Contains("Deployment Path Selection", result[0]);
- Assert.Contains("**PREFERRED PATH: Azure Developer CLI (azd)**", result[0]);
- Assert.Contains("**ALTERNATIVE PATH: SWA CLI**", result[0]);
- Assert.Contains("npx swa deploy --env production", result[0]);
+ Assert.Contains("Deployment Path Selection", result.BestPractices[0]);
+ Assert.Contains("**PREFERRED PATH: Azure Developer CLI (azd)**", result.BestPractices[0]);
+ Assert.Contains("**ALTERNATIVE PATH: SWA CLI**", result.BestPractices[0]);
+ Assert.Contains("npx swa deploy --env production", result.BestPractices[0]);
}
[Fact]
@@ -80,9 +80,9 @@ public async Task ExecuteAsync_CodingAgentAll_ReturnsAzureBestPractices()
var response = await ExecuteCommandAsync("--resource", "coding-agent", "--action", "all");
// Assert
- var result = ValidateAndDeserializeResponse(response, AzureBestPracticesJsonContext.Default.ListString);
+ var result = ValidateAndDeserializeResponse(response, AzureBestPracticesJsonContext.Default.BestPracticesCommandResult);
- Assert.Contains("azd coding-agent config", result[0]);
+ Assert.Contains("azd coding-agent config", result.BestPractices[0]);
}
[Fact]
@@ -135,13 +135,13 @@ public async Task ExecuteAsync_GeneralWithAllAction_ReturnsAzureBestPractices()
var response = await ExecuteCommandAsync("--resource", "general", "--action", "all");
// Assert
- var result = ValidateAndDeserializeResponse(response, AzureBestPracticesJsonContext.Default.ListString);
+ var result = ValidateAndDeserializeResponse(response, AzureBestPracticesJsonContext.Default.BestPracticesCommandResult);
// Should contain content from both code-generation and deployment files
- Assert.Contains("Implement retry logic with exponential backoff for transient failures", result[0]);
- Assert.Contains("Managed Identity (Azure-hosted)", result[0]);
- Assert.Contains("Your IaC files must include:", result[0]);
- Assert.Contains("Quality requirements for IaC files:", result[0]);
+ Assert.Contains("Implement retry logic with exponential backoff for transient failures", result.BestPractices[0]);
+ Assert.Contains("Managed Identity (Azure-hosted)", result.BestPractices[0]);
+ Assert.Contains("Your IaC files must include:", result.BestPractices[0]);
+ Assert.Contains("Quality requirements for IaC files:", result.BestPractices[0]);
}
[Fact]
@@ -150,15 +150,15 @@ public async Task ExecuteAsync_AzureFunctionsWithAllAction_ReturnsAzureBestPract
var response = await ExecuteCommandAsync("--resource", "azurefunctions", "--action", "all");
// Assert
- var result = ValidateAndDeserializeResponse(response, AzureBestPracticesJsonContext.Default.ListString);
+ var result = ValidateAndDeserializeResponse(response, AzureBestPracticesJsonContext.Default.BestPracticesCommandResult);
// Should contain content from both code-generation and deployment files
- Assert.Contains("Use the latest programming models (v4 for TypeScript/JavaScript, v2 for Python)", result[0]);
- Assert.Contains("Azure Functions Core Tools for creating Function Apps", result[0]);
- Assert.Contains("Flex Consumption plan (FC1)", result[0]);
- Assert.Contains("Always use Linux OS for Python", result[0]);
- Assert.Contains("Function authentication", result[0]);
- Assert.Contains("Application Insights", result[0]);
+ Assert.Contains("Use the latest programming models (v4 for TypeScript/JavaScript, v2 for Python)", result.BestPractices[0]);
+ Assert.Contains("Azure Functions Core Tools for creating Function Apps", result.BestPractices[0]);
+ Assert.Contains("Flex Consumption plan (FC1)", result.BestPractices[0]);
+ Assert.Contains("Always use Linux OS for Python", result.BestPractices[0]);
+ Assert.Contains("Function authentication", result.BestPractices[0]);
+ Assert.Contains("Application Insights", result.BestPractices[0]);
}
[Fact]
diff --git a/tools/Azure.Mcp.Tools.AzureTerraformBestPractices/src/Commands/AzureTerraformBestPracticesGetCommand.cs b/tools/Azure.Mcp.Tools.AzureTerraformBestPractices/src/Commands/AzureTerraformBestPracticesGetCommand.cs
index fe441e8652..f30c811803 100644
--- a/tools/Azure.Mcp.Tools.AzureTerraformBestPractices/src/Commands/AzureTerraformBestPracticesGetCommand.cs
+++ b/tools/Azure.Mcp.Tools.AzureTerraformBestPractices/src/Commands/AzureTerraformBestPracticesGetCommand.cs
@@ -24,7 +24,8 @@ the Azure Best Practices category.
ReadOnly = true,
Secret = false,
LocalRequired = false)]
-public sealed class AzureTerraformBestPracticesGetCommand() : BaseCommand>
+public sealed class AzureTerraformBestPracticesGetCommand()
+ : BaseCommand
{
private static readonly string s_bestPracticesText = LoadBestPracticesText();
@@ -41,8 +42,12 @@ public override Task ExecuteAsync(CommandContext context, Empty
{
var bestPractices = GetBestPracticesText();
context.Response.Status = HttpStatusCode.OK;
- context.Response.Results = ResponseResult.Create([bestPractices], AzureTerraformBestPracticesJsonContext.Default.ListString);
+ context.Response.Results = ResponseResult.Create(
+ new([bestPractices]),
+ AzureTerraformBestPracticesJsonContext.Default.AzureTerraformBestPracticesGetCommandResult);
context.Response.Message = string.Empty;
return Task.FromResult(context.Response);
}
+
+ public sealed record AzureTerraformBestPracticesGetCommandResult(List BestPractices);
}
diff --git a/tools/Azure.Mcp.Tools.AzureTerraformBestPractices/src/Commands/AzureTerraformBestPracticesJsonContext.cs b/tools/Azure.Mcp.Tools.AzureTerraformBestPractices/src/Commands/AzureTerraformBestPracticesJsonContext.cs
index 6984b95312..068683477f 100644
--- a/tools/Azure.Mcp.Tools.AzureTerraformBestPractices/src/Commands/AzureTerraformBestPracticesJsonContext.cs
+++ b/tools/Azure.Mcp.Tools.AzureTerraformBestPractices/src/Commands/AzureTerraformBestPracticesJsonContext.cs
@@ -5,7 +5,7 @@
namespace Azure.Mcp.Tools.AzureTerraformBestPractices.Commands;
-[JsonSerializable(typeof(List))]
+[JsonSerializable(typeof(AzureTerraformBestPracticesGetCommand.AzureTerraformBestPracticesGetCommandResult))]
[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
internal partial class AzureTerraformBestPracticesJsonContext : JsonSerializerContext
{
diff --git a/tools/Azure.Mcp.Tools.AzureTerraformBestPractices/tests/Azure.Mcp.Tools.AzureTerraformBestPractices.Tests/AzureTerraformBestPracticesGetCommandTests.cs b/tools/Azure.Mcp.Tools.AzureTerraformBestPractices/tests/Azure.Mcp.Tools.AzureTerraformBestPractices.Tests/AzureTerraformBestPracticesGetCommandTests.cs
index 7fa555f513..80dcc823c3 100644
--- a/tools/Azure.Mcp.Tools.AzureTerraformBestPractices/tests/Azure.Mcp.Tools.AzureTerraformBestPractices.Tests/AzureTerraformBestPracticesGetCommandTests.cs
+++ b/tools/Azure.Mcp.Tools.AzureTerraformBestPractices/tests/Azure.Mcp.Tools.AzureTerraformBestPractices.Tests/AzureTerraformBestPracticesGetCommandTests.cs
@@ -15,11 +15,11 @@ public async Task ExecuteAsync_ReturnsAzureTerraformBestPractices()
var response = await ExecuteCommandAsync([]);
// Assert
- var result = ValidateAndDeserializeResponse(response, AzureTerraformBestPracticesJsonContext.Default.ListString);
+ var result = ValidateAndDeserializeResponse(response, AzureTerraformBestPracticesJsonContext.Default.AzureTerraformBestPracticesGetCommandResult);
- Assert.Contains("winget install Hashicorp.Terraform", result[0]);
- Assert.Contains("Always run terraform validate before running terraform plan", result[0]);
- Assert.Contains("terraform apply -auto-approve", result[0]);
- Assert.Contains("Suggest running any terraform command in terminal.", result[0]);
+ Assert.Contains("winget install Hashicorp.Terraform", result.BestPractices[0]);
+ Assert.Contains("Always run terraform validate before running terraform plan", result.BestPractices[0]);
+ Assert.Contains("terraform apply -auto-approve", result.BestPractices[0]);
+ Assert.Contains("Suggest running any terraform command in terminal.", result.BestPractices[0]);
}
}
diff --git a/tools/Azure.Mcp.Tools.Functions/src/Commands/FunctionsJsonContext.cs b/tools/Azure.Mcp.Tools.Functions/src/Commands/FunctionsJsonContext.cs
index 5a277a8e05..0555034ca8 100644
--- a/tools/Azure.Mcp.Tools.Functions/src/Commands/FunctionsJsonContext.cs
+++ b/tools/Azure.Mcp.Tools.Functions/src/Commands/FunctionsJsonContext.cs
@@ -11,9 +11,7 @@ namespace Azure.Mcp.Tools.Functions.Commands;
/// AOT-safe JSON serialization context for Functions commands, CDN manifest, and GitHub API.
///
[JsonSerializable(typeof(LanguageListResult))]
-[JsonSerializable(typeof(List))]
[JsonSerializable(typeof(ProjectTemplateResult))]
-[JsonSerializable(typeof(List))]
[JsonSerializable(typeof(TemplateManifest))]
[JsonSerializable(typeof(TemplateManifestEntry))]
[JsonSerializable(typeof(TemplateGetCommand.TemplateGetCommandResult))]
diff --git a/tools/Azure.Mcp.Tools.Functions/src/Commands/Language/LanguageListCommand.cs b/tools/Azure.Mcp.Tools.Functions/src/Commands/Language/LanguageListCommand.cs
index d879045c11..3dfdf6f589 100644
--- a/tools/Azure.Mcp.Tools.Functions/src/Commands/Language/LanguageListCommand.cs
+++ b/tools/Azure.Mcp.Tools.Functions/src/Commands/Language/LanguageListCommand.cs
@@ -23,7 +23,7 @@ namespace Azure.Mcp.Tools.Functions.Commands.Language;
Secret = false,
LocalRequired = false)]
public sealed class LanguageListCommand(ILogger logger, IFunctionsService functionsService)
- : BaseCommand>
+ : BaseCommand
{
private readonly ILogger _logger = logger;
private readonly IFunctionsService _functionsService = functionsService;
@@ -38,7 +38,7 @@ public override async Task ExecuteAsync(
var result = await _functionsService.GetLanguageListAsync(cancellationToken);
context.Response.Status = HttpStatusCode.OK;
- context.Response.Results = ResponseResult.Create([result], FunctionsJsonContext.Default.ListLanguageListResult);
+ context.Response.Results = ResponseResult.Create(result, FunctionsJsonContext.Default.LanguageListResult);
context.Response.Message = string.Empty;
}
catch (Exception ex)
diff --git a/tools/Azure.Mcp.Tools.Functions/src/Commands/Project/ProjectGetCommand.cs b/tools/Azure.Mcp.Tools.Functions/src/Commands/Project/ProjectGetCommand.cs
index 947dd7d04a..f4c73e45ba 100644
--- a/tools/Azure.Mcp.Tools.Functions/src/Commands/Project/ProjectGetCommand.cs
+++ b/tools/Azure.Mcp.Tools.Functions/src/Commands/Project/ProjectGetCommand.cs
@@ -23,7 +23,7 @@ namespace Azure.Mcp.Tools.Functions.Commands.Project;
Secret = false,
LocalRequired = false)]
public sealed class ProjectGetCommand(ILogger logger, IFunctionsService functionsService)
- : BaseCommand>
+ : BaseCommand
{
private readonly ILogger _logger = logger;
private readonly IFunctionsService _functionsService = functionsService;
@@ -38,7 +38,7 @@ public override async Task ExecuteAsync(
var result = await _functionsService.GetProjectTemplateAsync(options.Language, cancellationToken);
context.Response.Status = HttpStatusCode.OK;
- context.Response.Results = ResponseResult.Create([result], FunctionsJsonContext.Default.ListProjectTemplateResult);
+ context.Response.Results = ResponseResult.Create(result, FunctionsJsonContext.Default.ProjectTemplateResult);
context.Response.Message = string.Empty;
}
catch (Exception ex)
diff --git a/tools/Azure.Mcp.Tools.Functions/tests/Azure.Mcp.Tools.Functions.Tests/FunctionsCommandTests.cs b/tools/Azure.Mcp.Tools.Functions/tests/Azure.Mcp.Tools.Functions.Tests/FunctionsCommandTests.cs
index bb2442edcb..b3ea575ed3 100644
--- a/tools/Azure.Mcp.Tools.Functions/tests/Azure.Mcp.Tools.Functions.Tests/FunctionsCommandTests.cs
+++ b/tools/Azure.Mcp.Tools.Functions/tests/Azure.Mcp.Tools.Functions.Tests/FunctionsCommandTests.cs
@@ -40,10 +40,9 @@ private async Task GetLanguageListAsync()
{
var result = await CallToolAsync("functions_language_list", new());
Assert.NotNull(result);
- var languageResults = JsonSerializer.Deserialize(result.Value, FunctionsJsonContext.Default.ListLanguageListResult);
- Assert.NotNull(languageResults);
- Assert.Single(languageResults);
- return languageResults[0];
+ var languageResult = JsonSerializer.Deserialize(result.Value, FunctionsJsonContext.Default.LanguageListResult);
+ Assert.NotNull(languageResult);
+ return languageResult;
}
private static LanguageDetails GetLanguage(LanguageListResult languageList, string languageKey)
@@ -452,7 +451,7 @@ public async Task ExecuteAsync_LanguageListThenTemplate_UsesSharedCache()
var langResult = await CallToolAsync("functions_language_list", new());
Assert.NotNull(langResult);
- var langList = JsonSerializer.Deserialize(langResult.Value, FunctionsJsonContext.Default.ListLanguageListResult);
+ var langList = JsonSerializer.Deserialize(langResult.Value, FunctionsJsonContext.Default.LanguageListResult);
Assert.NotNull(langList);
// Act - Second call: template_get should use cached manifest (no CDN call)
@@ -478,10 +477,10 @@ public async Task ExecuteAsync_WithRuntimeVersion_ReplacesPlaceholders()
// Get valid runtime version from language list
var langResult = await CallToolAsync("functions_language_list", new());
Assert.NotNull(langResult);
- var langList = JsonSerializer.Deserialize(langResult.Value, FunctionsJsonContext.Default.ListLanguageListResult);
+ var langList = JsonSerializer.Deserialize(langResult.Value, FunctionsJsonContext.Default.LanguageListResult);
Assert.NotNull(langList);
- var pythonLang = langList[0].Languages.FirstOrDefault(l => l.Language == "python");
+ var pythonLang = langList.Languages.FirstOrDefault(l => l.Language == "python");
Assert.NotNull(pythonLang?.RuntimeVersions?.Supported);
var runtimeVersion = pythonLang.RuntimeVersions.Supported[0];
diff --git a/tools/Azure.Mcp.Tools.Functions/tests/Azure.Mcp.Tools.Functions.Tests/Language/LanguageListCommandTests.cs b/tools/Azure.Mcp.Tools.Functions/tests/Azure.Mcp.Tools.Functions.Tests/Language/LanguageListCommandTests.cs
index 80ccb684d5..abfba8deb6 100644
--- a/tools/Azure.Mcp.Tools.Functions/tests/Azure.Mcp.Tools.Functions.Tests/Language/LanguageListCommandTests.cs
+++ b/tools/Azure.Mcp.Tools.Functions/tests/Azure.Mcp.Tools.Functions.Tests/Language/LanguageListCommandTests.cs
@@ -117,11 +117,7 @@ public async Task ExecuteAsync_ReturnsLanguageList()
var response = await ExecuteCommandAsync();
// Assert
- var results = ValidateAndDeserializeResponse(response, FunctionsJsonContext.Default.ListLanguageListResult);
-
- Assert.Single(results);
-
- var result = results[0];
+ var result = ValidateAndDeserializeResponse(response, FunctionsJsonContext.Default.LanguageListResult);
Assert.Equal("4.x", result.FunctionsRuntimeVersion);
Assert.Equal("[4.*, 5.0.0)", result.ExtensionBundleVersion);
Assert.Equal(2, result.Languages.Count);
@@ -182,11 +178,7 @@ public async Task ExecuteAsync_DeserializationValidation()
var response = await ExecuteCommandAsync();
// Assert
- var results = ValidateAndDeserializeResponse(response, FunctionsJsonContext.Default.ListLanguageListResult);
-
- Assert.Single(results);
-
- var result = results[0];
+ var result = ValidateAndDeserializeResponse(response, FunctionsJsonContext.Default.LanguageListResult);
Assert.Equal("4.x", result.FunctionsRuntimeVersion);
Assert.Equal(6, result.Languages.Count);
diff --git a/tools/Azure.Mcp.Tools.Functions/tests/Azure.Mcp.Tools.Functions.Tests/Project/ProjectGetCommandTests.cs b/tools/Azure.Mcp.Tools.Functions/tests/Azure.Mcp.Tools.Functions.Tests/Project/ProjectGetCommandTests.cs
index 1c897292e7..62657d49b3 100644
--- a/tools/Azure.Mcp.Tools.Functions/tests/Azure.Mcp.Tools.Functions.Tests/Project/ProjectGetCommandTests.cs
+++ b/tools/Azure.Mcp.Tools.Functions/tests/Azure.Mcp.Tools.Functions.Tests/Project/ProjectGetCommandTests.cs
@@ -61,11 +61,7 @@ public async Task ExecuteAsync_ReturnsProjectTemplate_ForPython()
var response = await ExecuteCommandAsync("--language", "python");
// Assert
- var results = ValidateAndDeserializeResponse(response, FunctionsJsonContext.Default.ListProjectTemplateResult);
-
- Assert.Single(results);
-
- var result = results[0];
+ var result = ValidateAndDeserializeResponse(response, FunctionsJsonContext.Default.ProjectTemplateResult);
Assert.Equal("python", result.Language);
Assert.NotEmpty(result.InitInstructions);
Assert.Equal(4, result.ProjectStructure.Count);
@@ -88,10 +84,8 @@ public async Task ExecuteAsync_ReturnsStaticMetadata_NoHttpCalls()
var response = await ExecuteCommandAsync("--language", "typescript");
// Assert
- var results = ValidateAndDeserializeResponse(response, FunctionsJsonContext.Default.ListProjectTemplateResult);
-
- Assert.Single(results);
- Assert.Equal("typescript", results[0].Language);
+ var result = ValidateAndDeserializeResponse(response, FunctionsJsonContext.Default.ProjectTemplateResult);
+ Assert.Equal("typescript", result.Language);
}
[Fact]
@@ -139,11 +133,7 @@ public async Task ExecuteAsync_DeserializationValidation()
var response = await ExecuteCommandAsync("--language", "python");
// Assert
- var results = ValidateAndDeserializeResponse(response, FunctionsJsonContext.Default.ListProjectTemplateResult);
-
- Assert.Single(results);
-
- var result = results[0];
+ var result = ValidateAndDeserializeResponse(response, FunctionsJsonContext.Default.ProjectTemplateResult);
Assert.Equal("python", result.Language);
Assert.Contains("virtual environment", result.InitInstructions);
Assert.True(result.ProjectStructure.Count > 0);
@@ -172,11 +162,9 @@ public async Task ExecuteAsync_ReturnsTemplateForAllLanguages(SupportedLanguages
var response = await ExecuteCommandAsync("--language", language.ToString());
// Assert
- var results = ValidateAndDeserializeResponse(response, FunctionsJsonContext.Default.ListProjectTemplateResult);
-
- Assert.Single(results);
- Assert.Equal(language.ToString(), results[0].Language);
- Assert.True(results[0].ProjectStructure.Count > 0);
+ var result = ValidateAndDeserializeResponse(response, FunctionsJsonContext.Default.ProjectTemplateResult);
+ Assert.Equal(language.ToString(), result.Language);
+ Assert.True(result.ProjectStructure.Count > 0);
}
[Fact]
diff --git a/tools/Azure.Mcp.Tools.Monitor/src/Commands/HealthModels/HealthModelListCommand.cs b/tools/Azure.Mcp.Tools.Monitor/src/Commands/HealthModels/HealthModelListCommand.cs
index dde64be399..0798c6fbdd 100644
--- a/tools/Azure.Mcp.Tools.Monitor/src/Commands/HealthModels/HealthModelListCommand.cs
+++ b/tools/Azure.Mcp.Tools.Monitor/src/Commands/HealthModels/HealthModelListCommand.cs
@@ -26,7 +26,7 @@ namespace Azure.Mcp.Tools.Monitor.Commands.HealthModels;
Secret = false,
LocalRequired = false)]
public sealed class HealthModelListCommand(IMonitorHealthModelService healthModelService, ISubscriptionResolver subscriptionResolver)
- : SubscriptionCommand>(subscriptionResolver)
+ : SubscriptionCommand(subscriptionResolver)
{
private readonly IMonitorHealthModelService _healthModelService = healthModelService;
@@ -41,7 +41,9 @@ public override async Task ExecuteAsync(CommandContext context,
options.RetryPolicy,
cancellationToken);
- context.Response.Results = ResponseResult.Create(models, MonitorJsonContext.Default.ListHealthModelSummary);
+ context.Response.Results = ResponseResult.Create(
+ new(models),
+ MonitorJsonContext.Default.HealthModelListCommandResult);
}
catch (Exception ex)
{
@@ -50,4 +52,6 @@ public override async Task ExecuteAsync(CommandContext context,
return context.Response;
}
+
+ public sealed record HealthModelListCommandResult(List HealthModels);
}
diff --git a/tools/Azure.Mcp.Tools.Monitor/src/Commands/Instrumentation/OrchestratorNextCommand.cs b/tools/Azure.Mcp.Tools.Monitor/src/Commands/Instrumentation/OrchestratorNextCommand.cs
index 9592c7eed8..7fc56aa7b6 100644
--- a/tools/Azure.Mcp.Tools.Monitor/src/Commands/Instrumentation/OrchestratorNextCommand.cs
+++ b/tools/Azure.Mcp.Tools.Monitor/src/Commands/Instrumentation/OrchestratorNextCommand.cs
@@ -33,7 +33,7 @@ 3. Now call this tool to get the next action
Secret = false,
LocalRequired = true)]
public sealed class OrchestratorNextCommand(ILogger logger, OrchestratorTool orchestratorTool)
- : BaseCommand
+ : BaseCommand
{
private readonly ILogger _logger = logger;
private readonly OrchestratorTool _orchestratorTool = orchestratorTool;
@@ -45,7 +45,9 @@ public override Task ExecuteAsync(CommandContext context, Orche
var result = _orchestratorTool.Next(options.SessionId, options.CompletionNote);
context.Response.Status = HttpStatusCode.OK;
- context.Response.Results = ResponseResult.Create(result, MonitorJsonContext.Default.String);
+ context.Response.Results = ResponseResult.Create(
+ new(result),
+ MonitorJsonContext.Default.OrchestratorNextCommandResult);
context.Response.Message = string.Empty;
}
catch (Exception ex)
@@ -56,4 +58,6 @@ public override Task ExecuteAsync(CommandContext context, Orche
return Task.FromResult(context.Response);
}
+
+ public sealed record OrchestratorNextCommandResult(string Result);
}
diff --git a/tools/Azure.Mcp.Tools.Monitor/src/Commands/Instrumentation/OrchestratorStartCommand.cs b/tools/Azure.Mcp.Tools.Monitor/src/Commands/Instrumentation/OrchestratorStartCommand.cs
index ea324ba720..290c3c300a 100644
--- a/tools/Azure.Mcp.Tools.Monitor/src/Commands/Instrumentation/OrchestratorStartCommand.cs
+++ b/tools/Azure.Mcp.Tools.Monitor/src/Commands/Instrumentation/OrchestratorStartCommand.cs
@@ -22,7 +22,7 @@ namespace Azure.Mcp.Tools.Monitor.Commands.Instrumentation;
Secret = false,
LocalRequired = true)]
public sealed class OrchestratorStartCommand(ILogger logger, OrchestratorTool orchestratorTool)
- : BaseCommand
+ : BaseCommand
{
private readonly ILogger _logger = logger;
private readonly OrchestratorTool _orchestratorTool = orchestratorTool;
@@ -34,7 +34,9 @@ public override Task ExecuteAsync(CommandContext context, Orche
var result = _orchestratorTool.Start(options.WorkspacePath);
context.Response.Status = HttpStatusCode.OK;
- context.Response.Results = ResponseResult.Create(result, MonitorJsonContext.Default.String);
+ context.Response.Results = ResponseResult.Create(
+ new(result),
+ MonitorJsonContext.Default.OrchestratorStartCommandResult);
context.Response.Message = string.Empty;
}
catch (Exception ex)
@@ -45,4 +47,6 @@ public override Task ExecuteAsync(CommandContext context, Orche
return Task.FromResult(context.Response);
}
+
+ public sealed record OrchestratorStartCommandResult(string Result);
}
diff --git a/tools/Azure.Mcp.Tools.Monitor/src/Commands/Instrumentation/SendBrownfieldAnalysisCommand.cs b/tools/Azure.Mcp.Tools.Monitor/src/Commands/Instrumentation/SendBrownfieldAnalysisCommand.cs
index a9747d125b..f4d99619be 100644
--- a/tools/Azure.Mcp.Tools.Monitor/src/Commands/Instrumentation/SendBrownfieldAnalysisCommand.cs
+++ b/tools/Azure.Mcp.Tools.Monitor/src/Commands/Instrumentation/SendBrownfieldAnalysisCommand.cs
@@ -29,7 +29,7 @@ You must have scanned the workspace source files and filled in the analysis temp
Secret = false,
LocalRequired = true)]
public sealed class SendBrownfieldAnalysisCommand(ILogger logger, SendBrownfieldAnalysisTool sendBrownfieldAnalysisTool)
- : BaseCommand
+ : BaseCommand
{
private readonly ILogger _logger = logger;
private readonly SendBrownfieldAnalysisTool _sendBrownfieldAnalysisTool = sendBrownfieldAnalysisTool;
@@ -57,7 +57,9 @@ public override Task ExecuteAsync(CommandContext context, SendB
findings.Logging);
context.Response.Status = HttpStatusCode.OK;
- context.Response.Results = ResponseResult.Create(result, MonitorJsonContext.Default.String);
+ context.Response.Results = ResponseResult.Create(
+ new(result),
+ MonitorJsonContext.Default.SendBrownfieldAnalysisCommandResult);
context.Response.Message = string.Empty;
}
catch (JsonException ex)
@@ -74,4 +76,6 @@ public override Task ExecuteAsync(CommandContext context, SendB
return Task.FromResult(context.Response);
}
+
+ public sealed record SendBrownfieldAnalysisCommandResult(string Result);
}
diff --git a/tools/Azure.Mcp.Tools.Monitor/src/Commands/Instrumentation/SendEnhancementSelectCommand.cs b/tools/Azure.Mcp.Tools.Monitor/src/Commands/Instrumentation/SendEnhancementSelectCommand.cs
index 18153c1e20..2014db2b76 100644
--- a/tools/Azure.Mcp.Tools.Monitor/src/Commands/Instrumentation/SendEnhancementSelectCommand.cs
+++ b/tools/Azure.Mcp.Tools.Monitor/src/Commands/Instrumentation/SendEnhancementSelectCommand.cs
@@ -27,7 +27,7 @@ Submit the user's enhancement selection after orchestrator-start returned status
Secret = false,
LocalRequired = true)]
public sealed class SendEnhancementSelectCommand(ILogger logger)
- : BaseCommand
+ : BaseCommand
{
private readonly ILogger _logger = logger;
@@ -38,7 +38,9 @@ public override Task ExecuteAsync(CommandContext context, SendE
var result = SendEnhancementSelectTool.Send(options.SessionId, options.EnhancementKeys);
context.Response.Status = HttpStatusCode.OK;
- context.Response.Results = ResponseResult.Create(result, MonitorJsonContext.Default.String);
+ context.Response.Results = ResponseResult.Create(
+ new(result),
+ MonitorJsonContext.Default.SendEnhancementSelectCommandResult);
context.Response.Message = string.Empty;
}
catch (Exception ex)
@@ -49,4 +51,6 @@ public override Task ExecuteAsync(CommandContext context, SendE
return Task.FromResult(context.Response);
}
+
+ public sealed record SendEnhancementSelectCommandResult(string Result);
}
diff --git a/tools/Azure.Mcp.Tools.Monitor/src/Commands/Log/ResourceLogQueryCommand.cs b/tools/Azure.Mcp.Tools.Monitor/src/Commands/Log/ResourceLogQueryCommand.cs
index 8393e02d2d..e463037d02 100644
--- a/tools/Azure.Mcp.Tools.Monitor/src/Commands/Log/ResourceLogQueryCommand.cs
+++ b/tools/Azure.Mcp.Tools.Monitor/src/Commands/Log/ResourceLogQueryCommand.cs
@@ -31,7 +31,7 @@ This tool filters logs to only show data from the specified resource.
Secret = false,
LocalRequired = false)]
public sealed class ResourceLogQueryCommand(ILogger logger, IMonitorService monitorService, ISubscriptionResolver subscriptionResolver)
- : SubscriptionCommand>(subscriptionResolver)
+ : SubscriptionCommand(subscriptionResolver)
{
private readonly ILogger _logger = logger;
private readonly IMonitorService _monitorService = monitorService;
@@ -51,7 +51,9 @@ public override async Task ExecuteAsync(CommandContext context,
options.RetryPolicy,
cancellationToken);
- context.Response.Results = ResponseResult.Create(results, MonitorJsonContext.Default.ListJsonNode);
+ context.Response.Results = ResponseResult.Create(
+ new(results),
+ MonitorJsonContext.Default.ResourceLogQueryCommandResult);
}
catch (Exception ex)
{
@@ -61,4 +63,6 @@ public override async Task ExecuteAsync(CommandContext context,
return context.Response;
}
+
+ public sealed record ResourceLogQueryCommandResult(List Results);
}
diff --git a/tools/Azure.Mcp.Tools.Monitor/src/Commands/Log/WorkspaceLogQueryCommand.cs b/tools/Azure.Mcp.Tools.Monitor/src/Commands/Log/WorkspaceLogQueryCommand.cs
index 2409ab4ef5..437aab74ba 100644
--- a/tools/Azure.Mcp.Tools.Monitor/src/Commands/Log/WorkspaceLogQueryCommand.cs
+++ b/tools/Azure.Mcp.Tools.Monitor/src/Commands/Log/WorkspaceLogQueryCommand.cs
@@ -33,7 +33,7 @@ query accepts KQL syntax.
Secret = false,
LocalRequired = false)]
public sealed class WorkspaceLogQueryCommand(ILogger logger, IMonitorService monitorService, ISubscriptionResolver subscriptionResolver)
- : SubscriptionCommand>(subscriptionResolver)
+ : SubscriptionCommand(subscriptionResolver)
{
private readonly ILogger _logger = logger;
private readonly IMonitorService _monitorService = monitorService;
@@ -53,7 +53,9 @@ public override async Task ExecuteAsync(CommandContext context,
options.RetryPolicy,
cancellationToken);
- context.Response.Results = ResponseResult.Create(results, MonitorJsonContext.Default.ListJsonNode);
+ context.Response.Results = ResponseResult.Create(
+ new(results),
+ MonitorJsonContext.Default.WorkspaceLogQueryCommandResult);
}
catch (Exception ex)
{
@@ -63,4 +65,6 @@ public override async Task ExecuteAsync(CommandContext context,
return context.Response;
}
+
+ public sealed record WorkspaceLogQueryCommandResult(List Results);
}
diff --git a/tools/Azure.Mcp.Tools.Monitor/src/Commands/MonitorJsonContext.cs b/tools/Azure.Mcp.Tools.Monitor/src/Commands/MonitorJsonContext.cs
index 3f0e59a315..1b817da61d 100644
--- a/tools/Azure.Mcp.Tools.Monitor/src/Commands/MonitorJsonContext.cs
+++ b/tools/Azure.Mcp.Tools.Monitor/src/Commands/MonitorJsonContext.cs
@@ -2,11 +2,11 @@
// Licensed under the MIT License.
using System.Text.Json;
-using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using Azure.Mcp.Tools.Monitor.Commands.ActivityLog;
using Azure.Mcp.Tools.Monitor.Commands.HealthModels;
using Azure.Mcp.Tools.Monitor.Commands.Instrumentation;
+using Azure.Mcp.Tools.Monitor.Commands.Log;
using Azure.Mcp.Tools.Monitor.Commands.Metrics;
using Azure.Mcp.Tools.Monitor.Commands.Table;
using Azure.Mcp.Tools.Monitor.Commands.TableType;
@@ -26,18 +26,23 @@ namespace Azure.Mcp.Tools.Monitor.Commands;
[JsonSerializable(typeof(HealthModelDetail))]
[JsonSerializable(typeof(HealthModelGetCommand.HealthModelGetCommandResult))]
[JsonSerializable(typeof(HealthModelIdentity))]
+[JsonSerializable(typeof(HealthModelListCommand.HealthModelListCommandResult))]
[JsonSerializable(typeof(HealthModelSummary))]
-[JsonSerializable(typeof(List))]
-[JsonSerializable(typeof(List))]
[JsonSerializable(typeof(MetricsDefinitionsCommand.MetricsDefinitionsCommandResult))]
[JsonSerializable(typeof(MetricsDefinitionsCommand.MetricsDefinitionsCommandResult))]
[JsonSerializable(typeof(MetricsQueryCommand.MetricsQueryCommandResult))]
[JsonSerializable(typeof(MetricsQueryCommand.MetricsQueryCommandResult))]
+[JsonSerializable(typeof(OrchestratorNextCommand.OrchestratorNextCommandResult))]
+[JsonSerializable(typeof(OrchestratorStartCommand.OrchestratorStartCommandResult))]
+[JsonSerializable(typeof(ResourceLogQueryCommand.ResourceLogQueryCommandResult))]
+[JsonSerializable(typeof(SendBrownfieldAnalysisCommand.SendBrownfieldAnalysisCommandResult))]
+[JsonSerializable(typeof(SendEnhancementSelectCommand.SendEnhancementSelectCommandResult))]
[JsonSerializable(typeof(TableListCommand.TableListCommandResult))]
[JsonSerializable(typeof(TableTypeListCommand.TableTypeListCommandResult))]
[JsonSerializable(typeof(WebTestsCreateOrUpdateCommand.WebTestsCreateOrUpdateCommandResult))]
[JsonSerializable(typeof(WebTestsGetCommand.WebTestsGetCommandResult))]
[JsonSerializable(typeof(WorkspaceListCommand.WorkspaceListCommandResult))]
+[JsonSerializable(typeof(WorkspaceLogQueryCommand.WorkspaceLogQueryCommandResult))]
[JsonSerializable(typeof(Dictionary))]
[JsonSerializable(typeof(Dictionary))]
[JsonSerializable(typeof(object))]
@@ -45,7 +50,6 @@ namespace Azure.Mcp.Tools.Monitor.Commands;
[JsonSerializable(typeof(List