From 0280abd2133e309c47d714bcdf0d89fc260ff4ea Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Mar 2026 07:49:41 +0000 Subject: [PATCH 01/10] Initial plan From af27706a68ae2e65040751edea26aea0404b8a81 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Mar 2026 07:59:30 +0000 Subject: [PATCH 02/10] Add azmcp containerapps containerapp list command Implements issue #200 - adds a new ContainerApps toolset using Azure.ResourceManager.AppContainers to list container apps in a subscription. Includes: - ContainerAppListCommand with resource group filtering - ContainerAppsService using BaseAzureResourceService with Resource Graph - Unit tests (8 passing) - Documentation updates (azmcp-commands.md, e2eTestPrompts.md) - Changelog entry Co-authored-by: ArthurMa1978 <20514459+ArthurMa1978@users.noreply.github.com> --- .../changelog-entries/1773129554713.yaml | 3 + .../Azure.Mcp.Server/docs/azmcp-commands.md | 13 ++ .../Azure.Mcp.Server/docs/e2eTestPrompts.md | 9 + servers/Azure.Mcp.Server/src/Program.cs | 1 + .../src/AssemblyInfo.cs | 7 + .../src/Azure.Mcp.Tools.ContainerApps.csproj | 19 ++ .../src/Commands/BaseContainerAppsCommand.cs | 28 +++ .../ContainerApp/ContainerAppListCommand.cs | 73 ++++++++ .../src/Commands/ContainerAppsJsonContext.cs | 16 ++ .../src/ContainerAppsSetup.cs | 37 ++++ .../src/GlobalUsings.cs | 6 + .../src/Models/ContainerAppInfo.cs | 13 ++ .../ContainerApp/ContainerAppListOptions.cs | 8 + .../src/Services/ContainerAppsService.cs | 59 +++++++ .../src/Services/IContainerAppsService.cs | 17 ++ ...e.Mcp.Tools.ContainerApps.UnitTests.csproj | 22 +++ .../ContainerAppListCommandTests.cs | 163 ++++++++++++++++++ 17 files changed, 494 insertions(+) create mode 100644 servers/Azure.Mcp.Server/changelog-entries/1773129554713.yaml create mode 100644 tools/Azure.Mcp.Tools.ContainerApps/src/AssemblyInfo.cs create mode 100644 tools/Azure.Mcp.Tools.ContainerApps/src/Azure.Mcp.Tools.ContainerApps.csproj create mode 100644 tools/Azure.Mcp.Tools.ContainerApps/src/Commands/BaseContainerAppsCommand.cs create mode 100644 tools/Azure.Mcp.Tools.ContainerApps/src/Commands/ContainerApp/ContainerAppListCommand.cs create mode 100644 tools/Azure.Mcp.Tools.ContainerApps/src/Commands/ContainerAppsJsonContext.cs create mode 100644 tools/Azure.Mcp.Tools.ContainerApps/src/ContainerAppsSetup.cs create mode 100644 tools/Azure.Mcp.Tools.ContainerApps/src/GlobalUsings.cs create mode 100644 tools/Azure.Mcp.Tools.ContainerApps/src/Models/ContainerAppInfo.cs create mode 100644 tools/Azure.Mcp.Tools.ContainerApps/src/Options/ContainerApp/ContainerAppListOptions.cs create mode 100644 tools/Azure.Mcp.Tools.ContainerApps/src/Services/ContainerAppsService.cs create mode 100644 tools/Azure.Mcp.Tools.ContainerApps/src/Services/IContainerAppsService.cs create mode 100644 tools/Azure.Mcp.Tools.ContainerApps/tests/Azure.Mcp.Tools.ContainerApps.UnitTests/Azure.Mcp.Tools.ContainerApps.UnitTests.csproj create mode 100644 tools/Azure.Mcp.Tools.ContainerApps/tests/Azure.Mcp.Tools.ContainerApps.UnitTests/ContainerApp/ContainerAppListCommandTests.cs diff --git a/servers/Azure.Mcp.Server/changelog-entries/1773129554713.yaml b/servers/Azure.Mcp.Server/changelog-entries/1773129554713.yaml new file mode 100644 index 0000000000..4cbcd5357d --- /dev/null +++ b/servers/Azure.Mcp.Server/changelog-entries/1773129554713.yaml @@ -0,0 +1,3 @@ +changes: + - section: "Features Added" + description: "Added `azmcp containerapps containerapp list` command to list Azure Container Apps in a subscription" diff --git a/servers/Azure.Mcp.Server/docs/azmcp-commands.md b/servers/Azure.Mcp.Server/docs/azmcp-commands.md index 82c7f6a270..473fd88bdf 100644 --- a/servers/Azure.Mcp.Server/docs/azmcp-commands.md +++ b/servers/Azure.Mcp.Server/docs/azmcp-commands.md @@ -1258,6 +1258,19 @@ azmcp confidentialledger entries get --ledger \ - `--collection-id`: Collection ID to store the data with (optional) - `--transaction-id`: Ledger transaction identifier to retrieve (required for the get command) +### Azure Container Apps Operations + +```bash +# List Azure Container Apps in a subscription +# ❌ Destructive | ✅ Idempotent | ❌ OpenWorld | ✅ ReadOnly | ❌ Secret | ❌ LocalRequired +azmcp containerapps containerapp list --subscription + +# List Azure Container Apps in a specific resource group +# ❌ Destructive | ✅ Idempotent | ❌ OpenWorld | ✅ ReadOnly | ❌ Secret | ❌ LocalRequired +azmcp containerapps containerapp list --subscription \ + [--resource-group ] +``` + ### Azure Container Registry (ACR) Operations ```bash diff --git a/servers/Azure.Mcp.Server/docs/e2eTestPrompts.md b/servers/Azure.Mcp.Server/docs/e2eTestPrompts.md index 3c49ac7bbc..a24e40a657 100644 --- a/servers/Azure.Mcp.Server/docs/e2eTestPrompts.md +++ b/servers/Azure.Mcp.Server/docs/e2eTestPrompts.md @@ -139,6 +139,15 @@ This file contains prompts used for end-to-end testing to ensure each tool is in | extension_cli_install | How to install azd | | extension_cli_install | What is Azure Functions Core tools and how to install it | +## Azure Container Apps + +| Tool Name | Test Prompt | +|:----------|:----------| +| containerapps_containerapp_list | List all Azure Container Apps in my subscription | +| containerapps_containerapp_list | Show me my Azure Container Apps | +| containerapps_containerapp_list | List container apps in resource group | +| containerapps_containerapp_list | Show me the container apps in my subscription | + ## Azure Container Registry (ACR) | Tool Name | Test Prompt | diff --git a/servers/Azure.Mcp.Server/src/Program.cs b/servers/Azure.Mcp.Server/src/Program.cs index d6d36ea515..1cab870900 100644 --- a/servers/Azure.Mcp.Server/src/Program.cs +++ b/servers/Azure.Mcp.Server/src/Program.cs @@ -111,6 +111,7 @@ private static IAreaSetup[] RegisterAreas() new Azure.Mcp.Tools.CloudArchitect.CloudArchitectSetup(), new Azure.Mcp.Tools.Communication.CommunicationSetup(), new Azure.Mcp.Tools.Compute.ComputeSetup(), + new Azure.Mcp.Tools.ContainerApps.ContainerAppsSetup(), new Azure.Mcp.Tools.ConfidentialLedger.ConfidentialLedgerSetup(), new Azure.Mcp.Tools.EventHubs.EventHubsSetup(), new Azure.Mcp.Tools.FileShares.FileSharesSetup(), diff --git a/tools/Azure.Mcp.Tools.ContainerApps/src/AssemblyInfo.cs b/tools/Azure.Mcp.Tools.ContainerApps/src/AssemblyInfo.cs new file mode 100644 index 0000000000..fb713746da --- /dev/null +++ b/tools/Azure.Mcp.Tools.ContainerApps/src/AssemblyInfo.cs @@ -0,0 +1,7 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Azure.Mcp.Tools.ContainerApps.UnitTests")] +[assembly: InternalsVisibleTo("Azure.Mcp.Tools.ContainerApps.LiveTests")] diff --git a/tools/Azure.Mcp.Tools.ContainerApps/src/Azure.Mcp.Tools.ContainerApps.csproj b/tools/Azure.Mcp.Tools.ContainerApps/src/Azure.Mcp.Tools.ContainerApps.csproj new file mode 100644 index 0000000000..7154740818 --- /dev/null +++ b/tools/Azure.Mcp.Tools.ContainerApps/src/Azure.Mcp.Tools.ContainerApps.csproj @@ -0,0 +1,19 @@ + + + true + + + + + + + + + + + + + + + + diff --git a/tools/Azure.Mcp.Tools.ContainerApps/src/Commands/BaseContainerAppsCommand.cs b/tools/Azure.Mcp.Tools.ContainerApps/src/Commands/BaseContainerAppsCommand.cs new file mode 100644 index 0000000000..046b16cea6 --- /dev/null +++ b/tools/Azure.Mcp.Tools.ContainerApps/src/Commands/BaseContainerAppsCommand.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Diagnostics.CodeAnalysis; +using Azure.Mcp.Core.Commands.Subscription; +using Azure.Mcp.Core.Extensions; +using Azure.Mcp.Core.Models.Option; +using Microsoft.Mcp.Core.Models.Option; + +namespace Azure.Mcp.Tools.ContainerApps.Commands; + +public abstract class BaseContainerAppsCommand< + [DynamicallyAccessedMembers(TrimAnnotations.CommandAnnotations)] TOptions> + : SubscriptionCommand where TOptions : SubscriptionOptions, new() +{ + protected override void RegisterOptions(Command command) + { + base.RegisterOptions(command); + command.Options.Add(OptionDefinitions.Common.ResourceGroup.AsOptional()); + } + + protected override TOptions BindOptions(ParseResult parseResult) + { + var options = base.BindOptions(parseResult); + options.ResourceGroup ??= parseResult.GetValueOrDefault(OptionDefinitions.Common.ResourceGroup.Name); + return options; + } +} diff --git a/tools/Azure.Mcp.Tools.ContainerApps/src/Commands/ContainerApp/ContainerAppListCommand.cs b/tools/Azure.Mcp.Tools.ContainerApps/src/Commands/ContainerApp/ContainerAppListCommand.cs new file mode 100644 index 0000000000..2fe4779cf4 --- /dev/null +++ b/tools/Azure.Mcp.Tools.ContainerApps/src/Commands/ContainerApp/ContainerAppListCommand.cs @@ -0,0 +1,73 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Azure.Mcp.Tools.ContainerApps.Options.ContainerApp; +using Azure.Mcp.Tools.ContainerApps.Services; +using Microsoft.Extensions.Logging; +using Microsoft.Mcp.Core.Commands; +using Microsoft.Mcp.Core.Models.Command; + +namespace Azure.Mcp.Tools.ContainerApps.Commands.ContainerApp; + +public sealed class ContainerAppListCommand(ILogger logger, IContainerAppsService containerAppsService) : BaseContainerAppsCommand +{ + private const string CommandTitle = "List Container Apps"; + private readonly ILogger _logger = logger; + private readonly IContainerAppsService _containerAppsService = containerAppsService; + + public override string Id => "d4e5f6a7-b8c9-0d1e-2f3a-4b5c6d7e8f90"; + + public override string Name => "list"; + + public override string Description => + $""" + List Azure Container Apps in a subscription. Optionally filter by resource group. Each container app result + includes: name, location, resourceGroup, managedEnvironmentId, provisioningState. If no container apps are + found the tool returns null results (consistent with other list commands). + """; + + public override string Title => CommandTitle; + + public override ToolMetadata Metadata => new() + { + Destructive = false, + Idempotent = true, + OpenWorld = false, + ReadOnly = true, + LocalRequired = false, + Secret = false + }; + + public override async Task ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken) + { + if (!Validate(parseResult.CommandResult, context.Response).IsValid) + { + return context.Response; + } + + var options = BindOptions(parseResult); + + try + { + var containerApps = await _containerAppsService.ListContainerApps( + options.Subscription!, + options.ResourceGroup, + options.Tenant, + options.RetryPolicy, + cancellationToken); + + context.Response.Results = ResponseResult.Create(new(containerApps?.Results ?? [], containerApps?.AreResultsTruncated ?? false), ContainerAppsJsonContext.Default.ContainerAppListCommandResult); + } + catch (Exception ex) + { + _logger.LogError(ex, + "Error listing container apps. Subscription: {Subscription}, ResourceGroup: {ResourceGroup}, Options: {@Options}", + options.Subscription, options.ResourceGroup, options); + HandleException(context, ex); + } + + return context.Response; + } + + internal record ContainerAppListCommandResult(List ContainerApps, bool AreResultsTruncated); +} diff --git a/tools/Azure.Mcp.Tools.ContainerApps/src/Commands/ContainerAppsJsonContext.cs b/tools/Azure.Mcp.Tools.ContainerApps/src/Commands/ContainerAppsJsonContext.cs new file mode 100644 index 0000000000..3dfe7035dc --- /dev/null +++ b/tools/Azure.Mcp.Tools.ContainerApps/src/Commands/ContainerAppsJsonContext.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Text.Json.Serialization; +using Azure.Mcp.Tools.ContainerApps.Commands.ContainerApp; + +namespace Azure.Mcp.Tools.ContainerApps.Commands; + +[JsonSerializable(typeof(ContainerAppListCommand.ContainerAppListCommandResult))] +[JsonSerializable(typeof(Models.ContainerAppInfo))] +[JsonSourceGenerationOptions( + PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)] +internal sealed partial class ContainerAppsJsonContext : JsonSerializerContext +{ +} diff --git a/tools/Azure.Mcp.Tools.ContainerApps/src/ContainerAppsSetup.cs b/tools/Azure.Mcp.Tools.ContainerApps/src/ContainerAppsSetup.cs new file mode 100644 index 0000000000..3890144ddb --- /dev/null +++ b/tools/Azure.Mcp.Tools.ContainerApps/src/ContainerAppsSetup.cs @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Azure.Mcp.Tools.ContainerApps.Commands.ContainerApp; +using Azure.Mcp.Tools.ContainerApps.Services; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Mcp.Core.Areas; +using Microsoft.Mcp.Core.Commands; + +namespace Azure.Mcp.Tools.ContainerApps; + +public class ContainerAppsSetup : IAreaSetup +{ + public string Name => "containerapps"; + + public string Title => "Azure Container Apps Management"; + + public void ConfigureServices(IServiceCollection services) + { + services.AddSingleton(); + + services.AddSingleton(); + } + + public CommandGroup RegisterCommands(IServiceProvider serviceProvider) + { + var containerapps = new CommandGroup(Name, "Azure Container Apps operations - Commands for managing Azure Container Apps resources. Includes operations for listing container apps and managing container app configurations.", Title); + + var containerapp = new CommandGroup("containerapp", "Container App resource operations - Commands for listing and managing Container App resources in your Azure subscription."); + containerapps.AddSubGroup(containerapp); + + var containerAppList = serviceProvider.GetRequiredService(); + containerapp.AddCommand(containerAppList.Name, containerAppList); + + return containerapps; + } +} diff --git a/tools/Azure.Mcp.Tools.ContainerApps/src/GlobalUsings.cs b/tools/Azure.Mcp.Tools.ContainerApps/src/GlobalUsings.cs new file mode 100644 index 0000000000..cb8de3ef35 --- /dev/null +++ b/tools/Azure.Mcp.Tools.ContainerApps/src/GlobalUsings.cs @@ -0,0 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +global using System.CommandLine; +global using Azure.Mcp.Core.Commands; +global using Azure.Mcp.Core.Options; diff --git a/tools/Azure.Mcp.Tools.ContainerApps/src/Models/ContainerAppInfo.cs b/tools/Azure.Mcp.Tools.ContainerApps/src/Models/ContainerAppInfo.cs new file mode 100644 index 0000000000..4c9898447e --- /dev/null +++ b/tools/Azure.Mcp.Tools.ContainerApps/src/Models/ContainerAppInfo.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Text.Json.Serialization; + +namespace Azure.Mcp.Tools.ContainerApps.Models; + +public sealed record ContainerAppInfo( + [property: JsonPropertyName("name")] string Name, + [property: JsonPropertyName("location")] string? Location, + [property: JsonPropertyName("resourceGroup")] string? ResourceGroup, + [property: JsonPropertyName("managedEnvironmentId")] string? ManagedEnvironmentId, + [property: JsonPropertyName("provisioningState")] string? ProvisioningState); diff --git a/tools/Azure.Mcp.Tools.ContainerApps/src/Options/ContainerApp/ContainerAppListOptions.cs b/tools/Azure.Mcp.Tools.ContainerApps/src/Options/ContainerApp/ContainerAppListOptions.cs new file mode 100644 index 0000000000..1cd09e0ef3 --- /dev/null +++ b/tools/Azure.Mcp.Tools.ContainerApps/src/Options/ContainerApp/ContainerAppListOptions.cs @@ -0,0 +1,8 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Azure.Mcp.Tools.ContainerApps.Options.ContainerApp; + +public class ContainerAppListOptions : SubscriptionOptions +{ +} diff --git a/tools/Azure.Mcp.Tools.ContainerApps/src/Services/ContainerAppsService.cs b/tools/Azure.Mcp.Tools.ContainerApps/src/Services/ContainerAppsService.cs new file mode 100644 index 0000000000..f5273c340e --- /dev/null +++ b/tools/Azure.Mcp.Tools.ContainerApps/src/Services/ContainerAppsService.cs @@ -0,0 +1,59 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Text.Json; +using Azure.Mcp.Core.Services.Azure; +using Azure.Mcp.Core.Services.Azure.Subscription; +using Azure.Mcp.Core.Services.Azure.Tenant; +using Azure.Mcp.Tools.ContainerApps.Models; + +namespace Azure.Mcp.Tools.ContainerApps.Services; + +public sealed class ContainerAppsService(ISubscriptionService subscriptionService, ITenantService tenantService) + : BaseAzureResourceService(subscriptionService, tenantService), IContainerAppsService +{ + public async Task> ListContainerApps( + string subscription, + string? resourceGroup = null, + string? tenant = null, + RetryPolicyOptions? retryPolicy = null, + CancellationToken cancellationToken = default) + { + ValidateRequiredParameters((nameof(subscription), subscription)); + + try + { + var containerApps = await ExecuteResourceQueryAsync( + "Microsoft.App/containerApps", + resourceGroup, + subscription, + retryPolicy, + ConvertToContainerAppInfoModel, + cancellationToken: cancellationToken); + + return containerApps; + } + catch (Exception ex) + { + throw new Exception($"Error retrieving container apps: {ex.Message}", ex); + } + } + + private static ContainerAppInfo ConvertToContainerAppInfoModel(JsonElement item) + { + var name = item.TryGetProperty("name", out var nameElement) ? nameElement.GetString() ?? string.Empty : string.Empty; + var location = item.TryGetProperty("location", out var locationElement) ? locationElement.GetString() : null; + var resourceGroup = item.TryGetProperty("resourceGroup", out var rgElement) ? rgElement.GetString() : null; + + string? managedEnvironmentId = null; + string? provisioningState = null; + + if (item.TryGetProperty("properties", out var properties)) + { + managedEnvironmentId = properties.TryGetProperty("managedEnvironmentId", out var envElement) ? envElement.GetString() : null; + provisioningState = properties.TryGetProperty("provisioningState", out var stateElement) ? stateElement.GetString() : null; + } + + return new ContainerAppInfo(name, location, resourceGroup, managedEnvironmentId, provisioningState); + } +} diff --git a/tools/Azure.Mcp.Tools.ContainerApps/src/Services/IContainerAppsService.cs b/tools/Azure.Mcp.Tools.ContainerApps/src/Services/IContainerAppsService.cs new file mode 100644 index 0000000000..1d90029524 --- /dev/null +++ b/tools/Azure.Mcp.Tools.ContainerApps/src/Services/IContainerAppsService.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Azure.Mcp.Core.Services.Azure; +using Azure.Mcp.Tools.ContainerApps.Models; + +namespace Azure.Mcp.Tools.ContainerApps.Services; + +public interface IContainerAppsService +{ + Task> ListContainerApps( + string subscription, + string? resourceGroup = null, + string? tenant = null, + RetryPolicyOptions? retryPolicy = null, + CancellationToken cancellationToken = default); +} diff --git a/tools/Azure.Mcp.Tools.ContainerApps/tests/Azure.Mcp.Tools.ContainerApps.UnitTests/Azure.Mcp.Tools.ContainerApps.UnitTests.csproj b/tools/Azure.Mcp.Tools.ContainerApps/tests/Azure.Mcp.Tools.ContainerApps.UnitTests/Azure.Mcp.Tools.ContainerApps.UnitTests.csproj new file mode 100644 index 0000000000..d00e463e54 --- /dev/null +++ b/tools/Azure.Mcp.Tools.ContainerApps/tests/Azure.Mcp.Tools.ContainerApps.UnitTests/Azure.Mcp.Tools.ContainerApps.UnitTests.csproj @@ -0,0 +1,22 @@ + + + enable + enable + false + + + + + + + + + + + + + + + + + diff --git a/tools/Azure.Mcp.Tools.ContainerApps/tests/Azure.Mcp.Tools.ContainerApps.UnitTests/ContainerApp/ContainerAppListCommandTests.cs b/tools/Azure.Mcp.Tools.ContainerApps/tests/Azure.Mcp.Tools.ContainerApps.UnitTests/ContainerApp/ContainerAppListCommandTests.cs new file mode 100644 index 0000000000..49ad6bdbf3 --- /dev/null +++ b/tools/Azure.Mcp.Tools.ContainerApps/tests/Azure.Mcp.Tools.ContainerApps.UnitTests/ContainerApp/ContainerAppListCommandTests.cs @@ -0,0 +1,163 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.CommandLine; +using System.Net; +using System.Text.Json; +using Azure.Mcp.Core.Helpers; +using Azure.Mcp.Core.Options; +using Azure.Mcp.Core.Services.Azure; +using Azure.Mcp.Tools.ContainerApps.Commands; +using Azure.Mcp.Tools.ContainerApps.Commands.ContainerApp; +using Azure.Mcp.Tools.ContainerApps.Models; +using Azure.Mcp.Tools.ContainerApps.Services; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Mcp.Core.Models.Command; +using NSubstitute; +using NSubstitute.ExceptionExtensions; +using Xunit; + +namespace Azure.Mcp.Tools.ContainerApps.UnitTests.ContainerApp; + +public class ContainerAppListCommandTests +{ + private readonly IContainerAppsService _service; + private readonly ILogger _logger; + private readonly ContainerAppListCommand _command; + private readonly CommandContext _context; + private readonly Command _commandDefinition; + + public ContainerAppListCommandTests() + { + _service = Substitute.For(); + _logger = Substitute.For>(); + + _command = new(_logger, _service); + _context = new(new ServiceCollection().BuildServiceProvider()); + _commandDefinition = _command.GetCommand(); + } + + [Fact] + public void Constructor_InitializesCommandCorrectly() + { + var command = _command.GetCommand(); + Assert.Equal("list", command.Name); + Assert.NotNull(command.Description); + Assert.NotEmpty(command.Description); + } + + [Theory] + [InlineData("--subscription sub", true)] + [InlineData("--subscription sub --resource-group rg", true)] + [InlineData("", false)] + public async Task ExecuteAsync_ValidatesInputCorrectly(string args, bool shouldSucceed) + { + // Ensure environment variable fallback does not interfere with validation tests + EnvironmentHelpers.SetAzureSubscriptionId(null); + // Arrange + if (shouldSucceed) + { + _service.ListContainerApps(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) + .Returns(new ResourceQueryResults( + [ + new("app1", "eastus", "rg1", "/subscriptions/sub/resourceGroups/rg1/providers/Microsoft.App/managedEnvironments/env1", "Succeeded"), + new("app2", "eastus2", "rg2", "/subscriptions/sub/resourceGroups/rg2/providers/Microsoft.App/managedEnvironments/env2", "Succeeded") + ], false)); + } + + var parseResult = _commandDefinition.Parse(args); + + // Act + var response = await _command.ExecuteAsync(_context, parseResult, TestContext.Current.CancellationToken); + + // Assert + Assert.Equal(shouldSucceed ? HttpStatusCode.OK : HttpStatusCode.BadRequest, response.Status); + if (shouldSucceed) + { + Assert.NotNull(response.Results); + } + else + { + Assert.Contains("required", response.Message.ToLower()); + } + } + + [Fact] + public async Task ExecuteAsync_HandlesServiceErrors() + { + // Arrange + _service.ListContainerApps(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) + .ThrowsAsync(new Exception("Test error")); + + var parseResult = _commandDefinition.Parse(["--subscription", "sub"]); + + // Act + var response = await _command.ExecuteAsync(_context, parseResult, TestContext.Current.CancellationToken); + + // Assert + Assert.Equal(HttpStatusCode.InternalServerError, response.Status); + Assert.Contains("Test error", response.Message); + Assert.Contains("troubleshooting", response.Message); + } + + [Fact] + public async Task ExecuteAsync_FiltersById_ReturnsFilteredContainerApps() + { + // Arrange + var expectedApps = new ResourceQueryResults([new("app1", null, null, null, null)], false); + _service.ListContainerApps("sub", "rg", Arg.Any(), Arg.Any(), Arg.Any()) + .Returns(expectedApps); + + var parseResult = _commandDefinition.Parse(["--subscription", "sub", "--resource-group", "rg"]); + + // Act + var response = await _command.ExecuteAsync(_context, parseResult, TestContext.Current.CancellationToken); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.Status); + Assert.NotNull(response.Results); + await _service.Received(1).ListContainerApps("sub", "rg", Arg.Any(), Arg.Any(), Arg.Any()); + } + + [Fact] + public async Task ExecuteAsync_EmptyList_ReturnsEmptyResults() + { + // Arrange + _service.ListContainerApps("sub", null, Arg.Any(), Arg.Any(), Arg.Any()) + .Returns(new ResourceQueryResults([], false)); + + var parseResult = _commandDefinition.Parse(["--subscription", "sub"]); + + // Act + var response = await _command.ExecuteAsync(_context, parseResult, TestContext.Current.CancellationToken); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.Status); + Assert.NotNull(response.Results); + + var json = JsonSerializer.Serialize(response.Results); + var result = JsonSerializer.Deserialize(json, ContainerAppsJsonContext.Default.ContainerAppListCommandResult); + + Assert.NotNull(result); + Assert.Empty(result.ContainerApps); + } + + [Fact] + public async Task ExecuteAsync_ReturnsExpectedContainerAppProperties() + { + // Arrange + var containerApp = new ContainerAppInfo("myapp", "eastus", "myrg", "/subscriptions/sub/resourceGroups/myrg/providers/Microsoft.App/managedEnvironments/myenv", "Succeeded"); + _service.ListContainerApps("sub", null, Arg.Any(), Arg.Any(), Arg.Any()) + .Returns(new ResourceQueryResults([containerApp], false)); + + var parseResult = _commandDefinition.Parse(["--subscription", "sub"]); + + // Act + var response = await _command.ExecuteAsync(_context, parseResult, TestContext.Current.CancellationToken); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.Status); + Assert.NotNull(response.Results); + } +} From 60affb55160ef836ecc76be21b009860487c60c0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Mar 2026 08:00:22 +0000 Subject: [PATCH 03/10] Address code review feedback: rename test method and improve e2e prompts Co-authored-by: ArthurMa1978 <20514459+ArthurMa1978@users.noreply.github.com> --- servers/Azure.Mcp.Server/docs/e2eTestPrompts.md | 2 +- .../ContainerApp/ContainerAppListCommandTests.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/servers/Azure.Mcp.Server/docs/e2eTestPrompts.md b/servers/Azure.Mcp.Server/docs/e2eTestPrompts.md index a24e40a657..79ab3fed2c 100644 --- a/servers/Azure.Mcp.Server/docs/e2eTestPrompts.md +++ b/servers/Azure.Mcp.Server/docs/e2eTestPrompts.md @@ -146,7 +146,7 @@ This file contains prompts used for end-to-end testing to ensure each tool is in | containerapps_containerapp_list | List all Azure Container Apps in my subscription | | containerapps_containerapp_list | Show me my Azure Container Apps | | containerapps_containerapp_list | List container apps in resource group | -| containerapps_containerapp_list | Show me the container apps in my subscription | +| containerapps_containerapp_list | Show me the container apps in resource group | ## Azure Container Registry (ACR) diff --git a/tools/Azure.Mcp.Tools.ContainerApps/tests/Azure.Mcp.Tools.ContainerApps.UnitTests/ContainerApp/ContainerAppListCommandTests.cs b/tools/Azure.Mcp.Tools.ContainerApps/tests/Azure.Mcp.Tools.ContainerApps.UnitTests/ContainerApp/ContainerAppListCommandTests.cs index 49ad6bdbf3..f1cbf1391f 100644 --- a/tools/Azure.Mcp.Tools.ContainerApps/tests/Azure.Mcp.Tools.ContainerApps.UnitTests/ContainerApp/ContainerAppListCommandTests.cs +++ b/tools/Azure.Mcp.Tools.ContainerApps/tests/Azure.Mcp.Tools.ContainerApps.UnitTests/ContainerApp/ContainerAppListCommandTests.cs @@ -102,7 +102,7 @@ public async Task ExecuteAsync_HandlesServiceErrors() } [Fact] - public async Task ExecuteAsync_FiltersById_ReturnsFilteredContainerApps() + public async Task ExecuteAsync_FiltersByResourceGroup_ReturnsFilteredContainerApps() { // Arrange var expectedApps = new ResourceQueryResults([new("app1", null, null, null, null)], false); From 40e6c16a41d371f95d5021b7bdd0fcc72534f079 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Mar 2026 10:16:00 +0000 Subject: [PATCH 04/10] Change command from containerapps containerapp list to containerapps list Removes the intermediate 'containerapp' subgroup so the command path is `azmcp containerapps list` instead of `azmcp containerapps containerapp list`. Updates Setup.cs, azmcp-commands.md, e2eTestPrompts.md, and changelog entry. Co-authored-by: ArthurMa1978 <20514459+ArthurMa1978@users.noreply.github.com> --- .../Azure.Mcp.Server/changelog-entries/1773129554713.yaml | 2 +- servers/Azure.Mcp.Server/docs/azmcp-commands.md | 6 +++--- servers/Azure.Mcp.Server/docs/e2eTestPrompts.md | 8 ++++---- .../src/ContainerAppsSetup.cs | 5 +---- 4 files changed, 9 insertions(+), 12 deletions(-) diff --git a/servers/Azure.Mcp.Server/changelog-entries/1773129554713.yaml b/servers/Azure.Mcp.Server/changelog-entries/1773129554713.yaml index 4cbcd5357d..4d723f36f4 100644 --- a/servers/Azure.Mcp.Server/changelog-entries/1773129554713.yaml +++ b/servers/Azure.Mcp.Server/changelog-entries/1773129554713.yaml @@ -1,3 +1,3 @@ changes: - section: "Features Added" - description: "Added `azmcp containerapps containerapp list` command to list Azure Container Apps in a subscription" + description: "Added `azmcp containerapps list` command to list Azure Container Apps in a subscription" diff --git a/servers/Azure.Mcp.Server/docs/azmcp-commands.md b/servers/Azure.Mcp.Server/docs/azmcp-commands.md index 473fd88bdf..31202631f7 100644 --- a/servers/Azure.Mcp.Server/docs/azmcp-commands.md +++ b/servers/Azure.Mcp.Server/docs/azmcp-commands.md @@ -1263,12 +1263,12 @@ azmcp confidentialledger entries get --ledger \ ```bash # List Azure Container Apps in a subscription # ❌ Destructive | ✅ Idempotent | ❌ OpenWorld | ✅ ReadOnly | ❌ Secret | ❌ LocalRequired -azmcp containerapps containerapp list --subscription +azmcp containerapps list --subscription # List Azure Container Apps in a specific resource group # ❌ Destructive | ✅ Idempotent | ❌ OpenWorld | ✅ ReadOnly | ❌ Secret | ❌ LocalRequired -azmcp containerapps containerapp list --subscription \ - [--resource-group ] +azmcp containerapps list --subscription \ + [--resource-group ] ``` ### Azure Container Registry (ACR) Operations diff --git a/servers/Azure.Mcp.Server/docs/e2eTestPrompts.md b/servers/Azure.Mcp.Server/docs/e2eTestPrompts.md index 79ab3fed2c..5dadede44d 100644 --- a/servers/Azure.Mcp.Server/docs/e2eTestPrompts.md +++ b/servers/Azure.Mcp.Server/docs/e2eTestPrompts.md @@ -143,10 +143,10 @@ This file contains prompts used for end-to-end testing to ensure each tool is in | Tool Name | Test Prompt | |:----------|:----------| -| containerapps_containerapp_list | List all Azure Container Apps in my subscription | -| containerapps_containerapp_list | Show me my Azure Container Apps | -| containerapps_containerapp_list | List container apps in resource group | -| containerapps_containerapp_list | Show me the container apps in resource group | +| containerapps_list | List all Azure Container Apps in my subscription | +| containerapps_list | Show me my Azure Container Apps | +| containerapps_list | List container apps in resource group | +| containerapps_list | Show me the container apps in resource group | ## Azure Container Registry (ACR) diff --git a/tools/Azure.Mcp.Tools.ContainerApps/src/ContainerAppsSetup.cs b/tools/Azure.Mcp.Tools.ContainerApps/src/ContainerAppsSetup.cs index 3890144ddb..07f1a06508 100644 --- a/tools/Azure.Mcp.Tools.ContainerApps/src/ContainerAppsSetup.cs +++ b/tools/Azure.Mcp.Tools.ContainerApps/src/ContainerAppsSetup.cs @@ -26,11 +26,8 @@ public CommandGroup RegisterCommands(IServiceProvider serviceProvider) { var containerapps = new CommandGroup(Name, "Azure Container Apps operations - Commands for managing Azure Container Apps resources. Includes operations for listing container apps and managing container app configurations.", Title); - var containerapp = new CommandGroup("containerapp", "Container App resource operations - Commands for listing and managing Container App resources in your Azure subscription."); - containerapps.AddSubGroup(containerapp); - var containerAppList = serviceProvider.GetRequiredService(); - containerapp.AddCommand(containerAppList.Name, containerAppList); + containerapps.AddCommand(containerAppList.Name, containerAppList); return containerapps; } From 66a2ab8d3d0538db6c7af2c0c6cf26bf5c1ddd91 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Mar 2026 03:47:59 +0000 Subject: [PATCH 05/10] Plan: Fix ConsolidatedModeTests failure by adding containerapps_list to consolidated-tools.json Co-authored-by: ArthurMa1978 <20514459+ArthurMa1978@users.noreply.github.com> --- global.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/global.json b/global.json index ed07ad8f74..067010d797 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "10.0.103", + "version": "10.0.102", "rollForward": "latestFeature" } } From fe35e7f01c3ff4ce493243a6a70291bd60088dfd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Mar 2026 03:56:46 +0000 Subject: [PATCH 06/10] Add containerapps_list to consolidated-tools.json and restore global.json The containerapps_list tool was not mapped in consolidated-tools.json, causing ConsolidatedModeTests to fail in DEBUG builds. Added the tool to the get_azure_container_details consolidated tool alongside other container service tools (ACR, AKS). Also restores global.json SDK version to 10.0.103 (was accidentally committed as 10.0.102 in the previous commit). Co-authored-by: ArthurMa1978 <20514459+ArthurMa1978@users.noreply.github.com> --- global.json | 2 +- .../Azure.Mcp.Server/src/Resources/consolidated-tools.json | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/global.json b/global.json index 067010d797..ed07ad8f74 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "10.0.102", + "version": "10.0.103", "rollForward": "latestFeature" } } diff --git a/servers/Azure.Mcp.Server/src/Resources/consolidated-tools.json b/servers/Azure.Mcp.Server/src/Resources/consolidated-tools.json index 8f3221b933..5f7400b994 100644 --- a/servers/Azure.Mcp.Server/src/Resources/consolidated-tools.json +++ b/servers/Azure.Mcp.Server/src/Resources/consolidated-tools.json @@ -2227,7 +2227,7 @@ }, { "name": "get_azure_container_details", - "description": "Get details about Azure container services including Azure Container Registry (ACR) and Azure Kubernetes Service (AKS). View registries, repositories, nodepools, clusters, cluster configurations, and individual nodepool details.", + "description": "Get details about Azure container services including Azure Container Registry (ACR), Azure Kubernetes Service (AKS), and Azure Container Apps. View registries, repositories, nodepools, clusters, cluster configurations, individual nodepool details, and container apps.", "toolMetadata": { "destructive": { "value": false, @@ -2258,7 +2258,8 @@ "acr_registry_list", "acr_registry_repository_list", "aks_cluster_get", - "aks_nodepool_get" + "aks_nodepool_get", + "containerapps_list" ] }, { From 13c02fd437de0c32d90b8af0294c76d092a51426 Mon Sep 17 00:00:00 2001 From: Arthur Ma Date: Fri, 13 Mar 2026 15:13:08 +0800 Subject: [PATCH 07/10] Update tools/Azure.Mcp.Tools.ContainerApps/src/Services/ContainerAppsService.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../src/Services/ContainerAppsService.cs | 25 +++++++------------ 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/tools/Azure.Mcp.Tools.ContainerApps/src/Services/ContainerAppsService.cs b/tools/Azure.Mcp.Tools.ContainerApps/src/Services/ContainerAppsService.cs index f5273c340e..c47d32ac8f 100644 --- a/tools/Azure.Mcp.Tools.ContainerApps/src/Services/ContainerAppsService.cs +++ b/tools/Azure.Mcp.Tools.ContainerApps/src/Services/ContainerAppsService.cs @@ -21,22 +21,15 @@ public async Task> ListContainerApps( { ValidateRequiredParameters((nameof(subscription), subscription)); - try - { - var containerApps = await ExecuteResourceQueryAsync( - "Microsoft.App/containerApps", - resourceGroup, - subscription, - retryPolicy, - ConvertToContainerAppInfoModel, - cancellationToken: cancellationToken); - - return containerApps; - } - catch (Exception ex) - { - throw new Exception($"Error retrieving container apps: {ex.Message}", ex); - } + var containerApps = await ExecuteResourceQueryAsync( + "Microsoft.App/containerApps", + resourceGroup, + subscription, + retryPolicy, + ConvertToContainerAppInfoModel, + cancellationToken: cancellationToken); + + return containerApps; } private static ContainerAppInfo ConvertToContainerAppInfoModel(JsonElement item) From 192566f4d3ea78f3793d7f5110c689f69e924a4d Mon Sep 17 00:00:00 2001 From: Arthur Ma Date: Fri, 13 Mar 2026 15:13:17 +0800 Subject: [PATCH 08/10] Update tools/Azure.Mcp.Tools.ContainerApps/src/Commands/ContainerApp/ContainerAppListCommand.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../src/Commands/ContainerApp/ContainerAppListCommand.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/Azure.Mcp.Tools.ContainerApps/src/Commands/ContainerApp/ContainerAppListCommand.cs b/tools/Azure.Mcp.Tools.ContainerApps/src/Commands/ContainerApp/ContainerAppListCommand.cs index 2fe4779cf4..340b2bd547 100644 --- a/tools/Azure.Mcp.Tools.ContainerApps/src/Commands/ContainerApp/ContainerAppListCommand.cs +++ b/tools/Azure.Mcp.Tools.ContainerApps/src/Commands/ContainerApp/ContainerAppListCommand.cs @@ -23,7 +23,7 @@ public sealed class ContainerAppListCommand(ILogger log $""" List Azure Container Apps in a subscription. Optionally filter by resource group. Each container app result includes: name, location, resourceGroup, managedEnvironmentId, provisioningState. If no container apps are - found the tool returns null results (consistent with other list commands). + found the tool returns an empty list of results (consistent with other list commands). """; public override string Title => CommandTitle; From b11ffce189962a7630711ef89476115a8ad08b74 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Mar 2026 07:31:12 +0000 Subject: [PATCH 09/10] Address review: save/restore AZURE_SUBSCRIPTION_ID env var in test and add ClearEnvironmentVariablesBeforeTest assembly attribute Co-authored-by: ArthurMa1978 <20514459+ArthurMa1978@users.noreply.github.com> --- .../AssemblyAttributes.cs | 2 + .../ContainerAppListCommandTests.cs | 56 +++++++++++-------- 2 files changed, 34 insertions(+), 24 deletions(-) create mode 100644 tools/Azure.Mcp.Tools.ContainerApps/tests/Azure.Mcp.Tools.ContainerApps.UnitTests/AssemblyAttributes.cs diff --git a/tools/Azure.Mcp.Tools.ContainerApps/tests/Azure.Mcp.Tools.ContainerApps.UnitTests/AssemblyAttributes.cs b/tools/Azure.Mcp.Tools.ContainerApps/tests/Azure.Mcp.Tools.ContainerApps.UnitTests/AssemblyAttributes.cs new file mode 100644 index 0000000000..69da1d7967 --- /dev/null +++ b/tools/Azure.Mcp.Tools.ContainerApps/tests/Azure.Mcp.Tools.ContainerApps.UnitTests/AssemblyAttributes.cs @@ -0,0 +1,2 @@ +[assembly: Microsoft.Mcp.Tests.Helpers.ClearEnvironmentVariablesBeforeTest] +[assembly: Xunit.CollectionBehavior(Xunit.CollectionBehavior.CollectionPerAssembly)] diff --git a/tools/Azure.Mcp.Tools.ContainerApps/tests/Azure.Mcp.Tools.ContainerApps.UnitTests/ContainerApp/ContainerAppListCommandTests.cs b/tools/Azure.Mcp.Tools.ContainerApps/tests/Azure.Mcp.Tools.ContainerApps.UnitTests/ContainerApp/ContainerAppListCommandTests.cs index f1cbf1391f..b16da54a83 100644 --- a/tools/Azure.Mcp.Tools.ContainerApps/tests/Azure.Mcp.Tools.ContainerApps.UnitTests/ContainerApp/ContainerAppListCommandTests.cs +++ b/tools/Azure.Mcp.Tools.ContainerApps/tests/Azure.Mcp.Tools.ContainerApps.UnitTests/ContainerApp/ContainerAppListCommandTests.cs @@ -53,33 +53,41 @@ public void Constructor_InitializesCommandCorrectly() [InlineData("", false)] public async Task ExecuteAsync_ValidatesInputCorrectly(string args, bool shouldSucceed) { - // Ensure environment variable fallback does not interfere with validation tests - EnvironmentHelpers.SetAzureSubscriptionId(null); - // Arrange - if (shouldSucceed) - { - _service.ListContainerApps(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) - .Returns(new ResourceQueryResults( - [ - new("app1", "eastus", "rg1", "/subscriptions/sub/resourceGroups/rg1/providers/Microsoft.App/managedEnvironments/env1", "Succeeded"), - new("app2", "eastus2", "rg2", "/subscriptions/sub/resourceGroups/rg2/providers/Microsoft.App/managedEnvironments/env2", "Succeeded") - ], false)); - } - - var parseResult = _commandDefinition.Parse(args); - - // Act - var response = await _command.ExecuteAsync(_context, parseResult, TestContext.Current.CancellationToken); - - // Assert - Assert.Equal(shouldSucceed ? HttpStatusCode.OK : HttpStatusCode.BadRequest, response.Status); - if (shouldSucceed) + var originalSubscriptionId = Environment.GetEnvironmentVariable("AZURE_SUBSCRIPTION_ID"); + try { - Assert.NotNull(response.Results); + // Ensure environment variable fallback does not interfere with validation tests + EnvironmentHelpers.SetAzureSubscriptionId(null); + // Arrange + if (shouldSucceed) + { + _service.ListContainerApps(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) + .Returns(new ResourceQueryResults( + [ + new("app1", "eastus", "rg1", "/subscriptions/sub/resourceGroups/rg1/providers/Microsoft.App/managedEnvironments/env1", "Succeeded"), + new("app2", "eastus2", "rg2", "/subscriptions/sub/resourceGroups/rg2/providers/Microsoft.App/managedEnvironments/env2", "Succeeded") + ], false)); + } + + var parseResult = _commandDefinition.Parse(args); + + // Act + var response = await _command.ExecuteAsync(_context, parseResult, TestContext.Current.CancellationToken); + + // Assert + Assert.Equal(shouldSucceed ? HttpStatusCode.OK : HttpStatusCode.BadRequest, response.Status); + if (shouldSucceed) + { + Assert.NotNull(response.Results); + } + else + { + Assert.Contains("required", response.Message.ToLower()); + } } - else + finally { - Assert.Contains("required", response.Message.ToLower()); + EnvironmentHelpers.SetAzureSubscriptionId(originalSubscriptionId); } } From ca36c8686a6adb88724f0cf28c217c07819414d8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 17 Mar 2026 09:38:14 +0000 Subject: [PATCH 10/10] Address PR review comments: fix ordering, remove unused dependency, remove redundant JsonPropertyName annotations, remove unused tenant parameter, add copyright header, add PR number to changelog Co-authored-by: ArthurMa1978 <20514459+ArthurMa1978@users.noreply.github.com> --- .../changelog-entries/1773129554713.yaml | 1 + servers/Azure.Mcp.Server/src/Program.cs | 2 +- .../src/Azure.Mcp.Tools.ContainerApps.csproj | 1 - .../Commands/ContainerApp/ContainerAppListCommand.cs | 1 - .../src/Models/ContainerAppInfo.cs | 12 +++++------- .../src/Services/ContainerAppsService.cs | 1 - .../src/Services/IContainerAppsService.cs | 1 - .../AssemblyAttributes.cs | 5 ++++- .../ContainerApp/ContainerAppListCommandTests.cs | 12 ++++++------ 9 files changed, 17 insertions(+), 19 deletions(-) diff --git a/servers/Azure.Mcp.Server/changelog-entries/1773129554713.yaml b/servers/Azure.Mcp.Server/changelog-entries/1773129554713.yaml index 4d723f36f4..c50f282ce9 100644 --- a/servers/Azure.Mcp.Server/changelog-entries/1773129554713.yaml +++ b/servers/Azure.Mcp.Server/changelog-entries/1773129554713.yaml @@ -1,3 +1,4 @@ +pr: 1981 changes: - section: "Features Added" description: "Added `azmcp containerapps list` command to list Azure Container Apps in a subscription" diff --git a/servers/Azure.Mcp.Server/src/Program.cs b/servers/Azure.Mcp.Server/src/Program.cs index 1cab870900..b533dce6fc 100644 --- a/servers/Azure.Mcp.Server/src/Program.cs +++ b/servers/Azure.Mcp.Server/src/Program.cs @@ -111,8 +111,8 @@ private static IAreaSetup[] RegisterAreas() new Azure.Mcp.Tools.CloudArchitect.CloudArchitectSetup(), new Azure.Mcp.Tools.Communication.CommunicationSetup(), new Azure.Mcp.Tools.Compute.ComputeSetup(), - new Azure.Mcp.Tools.ContainerApps.ContainerAppsSetup(), new Azure.Mcp.Tools.ConfidentialLedger.ConfidentialLedgerSetup(), + new Azure.Mcp.Tools.ContainerApps.ContainerAppsSetup(), new Azure.Mcp.Tools.EventHubs.EventHubsSetup(), new Azure.Mcp.Tools.FileShares.FileSharesSetup(), new Azure.Mcp.Tools.FoundryExtensions.FoundryExtensionsSetup(), diff --git a/tools/Azure.Mcp.Tools.ContainerApps/src/Azure.Mcp.Tools.ContainerApps.csproj b/tools/Azure.Mcp.Tools.ContainerApps/src/Azure.Mcp.Tools.ContainerApps.csproj index 7154740818..8e8f7b14ba 100644 --- a/tools/Azure.Mcp.Tools.ContainerApps/src/Azure.Mcp.Tools.ContainerApps.csproj +++ b/tools/Azure.Mcp.Tools.ContainerApps/src/Azure.Mcp.Tools.ContainerApps.csproj @@ -11,7 +11,6 @@ - diff --git a/tools/Azure.Mcp.Tools.ContainerApps/src/Commands/ContainerApp/ContainerAppListCommand.cs b/tools/Azure.Mcp.Tools.ContainerApps/src/Commands/ContainerApp/ContainerAppListCommand.cs index 340b2bd547..d5549decf9 100644 --- a/tools/Azure.Mcp.Tools.ContainerApps/src/Commands/ContainerApp/ContainerAppListCommand.cs +++ b/tools/Azure.Mcp.Tools.ContainerApps/src/Commands/ContainerApp/ContainerAppListCommand.cs @@ -52,7 +52,6 @@ public override async Task ExecuteAsync(CommandContext context, var containerApps = await _containerAppsService.ListContainerApps( options.Subscription!, options.ResourceGroup, - options.Tenant, options.RetryPolicy, cancellationToken); diff --git a/tools/Azure.Mcp.Tools.ContainerApps/src/Models/ContainerAppInfo.cs b/tools/Azure.Mcp.Tools.ContainerApps/src/Models/ContainerAppInfo.cs index 4c9898447e..9748584b9d 100644 --- a/tools/Azure.Mcp.Tools.ContainerApps/src/Models/ContainerAppInfo.cs +++ b/tools/Azure.Mcp.Tools.ContainerApps/src/Models/ContainerAppInfo.cs @@ -1,13 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Text.Json.Serialization; - namespace Azure.Mcp.Tools.ContainerApps.Models; public sealed record ContainerAppInfo( - [property: JsonPropertyName("name")] string Name, - [property: JsonPropertyName("location")] string? Location, - [property: JsonPropertyName("resourceGroup")] string? ResourceGroup, - [property: JsonPropertyName("managedEnvironmentId")] string? ManagedEnvironmentId, - [property: JsonPropertyName("provisioningState")] string? ProvisioningState); + string Name, + string? Location, + string? ResourceGroup, + string? ManagedEnvironmentId, + string? ProvisioningState); diff --git a/tools/Azure.Mcp.Tools.ContainerApps/src/Services/ContainerAppsService.cs b/tools/Azure.Mcp.Tools.ContainerApps/src/Services/ContainerAppsService.cs index c47d32ac8f..0067461c1f 100644 --- a/tools/Azure.Mcp.Tools.ContainerApps/src/Services/ContainerAppsService.cs +++ b/tools/Azure.Mcp.Tools.ContainerApps/src/Services/ContainerAppsService.cs @@ -15,7 +15,6 @@ public sealed class ContainerAppsService(ISubscriptionService subscriptionServic public async Task> ListContainerApps( string subscription, string? resourceGroup = null, - string? tenant = null, RetryPolicyOptions? retryPolicy = null, CancellationToken cancellationToken = default) { diff --git a/tools/Azure.Mcp.Tools.ContainerApps/src/Services/IContainerAppsService.cs b/tools/Azure.Mcp.Tools.ContainerApps/src/Services/IContainerAppsService.cs index 1d90029524..c71789843d 100644 --- a/tools/Azure.Mcp.Tools.ContainerApps/src/Services/IContainerAppsService.cs +++ b/tools/Azure.Mcp.Tools.ContainerApps/src/Services/IContainerAppsService.cs @@ -11,7 +11,6 @@ public interface IContainerAppsService Task> ListContainerApps( string subscription, string? resourceGroup = null, - string? tenant = null, RetryPolicyOptions? retryPolicy = null, CancellationToken cancellationToken = default); } diff --git a/tools/Azure.Mcp.Tools.ContainerApps/tests/Azure.Mcp.Tools.ContainerApps.UnitTests/AssemblyAttributes.cs b/tools/Azure.Mcp.Tools.ContainerApps/tests/Azure.Mcp.Tools.ContainerApps.UnitTests/AssemblyAttributes.cs index 69da1d7967..92cc1acc9f 100644 --- a/tools/Azure.Mcp.Tools.ContainerApps/tests/Azure.Mcp.Tools.ContainerApps.UnitTests/AssemblyAttributes.cs +++ b/tools/Azure.Mcp.Tools.ContainerApps/tests/Azure.Mcp.Tools.ContainerApps.UnitTests/AssemblyAttributes.cs @@ -1,2 +1,5 @@ -[assembly: Microsoft.Mcp.Tests.Helpers.ClearEnvironmentVariablesBeforeTest] +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +[assembly: Microsoft.Mcp.Tests.Helpers.ClearEnvironmentVariablesBeforeTest] [assembly: Xunit.CollectionBehavior(Xunit.CollectionBehavior.CollectionPerAssembly)] diff --git a/tools/Azure.Mcp.Tools.ContainerApps/tests/Azure.Mcp.Tools.ContainerApps.UnitTests/ContainerApp/ContainerAppListCommandTests.cs b/tools/Azure.Mcp.Tools.ContainerApps/tests/Azure.Mcp.Tools.ContainerApps.UnitTests/ContainerApp/ContainerAppListCommandTests.cs index b16da54a83..28ff0c2950 100644 --- a/tools/Azure.Mcp.Tools.ContainerApps/tests/Azure.Mcp.Tools.ContainerApps.UnitTests/ContainerApp/ContainerAppListCommandTests.cs +++ b/tools/Azure.Mcp.Tools.ContainerApps/tests/Azure.Mcp.Tools.ContainerApps.UnitTests/ContainerApp/ContainerAppListCommandTests.cs @@ -61,7 +61,7 @@ public async Task ExecuteAsync_ValidatesInputCorrectly(string args, bool shouldS // Arrange if (shouldSucceed) { - _service.ListContainerApps(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) + _service.ListContainerApps(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) .Returns(new ResourceQueryResults( [ new("app1", "eastus", "rg1", "/subscriptions/sub/resourceGroups/rg1/providers/Microsoft.App/managedEnvironments/env1", "Succeeded"), @@ -95,7 +95,7 @@ public async Task ExecuteAsync_ValidatesInputCorrectly(string args, bool shouldS public async Task ExecuteAsync_HandlesServiceErrors() { // Arrange - _service.ListContainerApps(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) + _service.ListContainerApps(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) .ThrowsAsync(new Exception("Test error")); var parseResult = _commandDefinition.Parse(["--subscription", "sub"]); @@ -114,7 +114,7 @@ public async Task ExecuteAsync_FiltersByResourceGroup_ReturnsFilteredContainerAp { // Arrange var expectedApps = new ResourceQueryResults([new("app1", null, null, null, null)], false); - _service.ListContainerApps("sub", "rg", Arg.Any(), Arg.Any(), Arg.Any()) + _service.ListContainerApps("sub", "rg", Arg.Any(), Arg.Any()) .Returns(expectedApps); var parseResult = _commandDefinition.Parse(["--subscription", "sub", "--resource-group", "rg"]); @@ -125,14 +125,14 @@ public async Task ExecuteAsync_FiltersByResourceGroup_ReturnsFilteredContainerAp // Assert Assert.Equal(HttpStatusCode.OK, response.Status); Assert.NotNull(response.Results); - await _service.Received(1).ListContainerApps("sub", "rg", Arg.Any(), Arg.Any(), Arg.Any()); + await _service.Received(1).ListContainerApps("sub", "rg", Arg.Any(), Arg.Any()); } [Fact] public async Task ExecuteAsync_EmptyList_ReturnsEmptyResults() { // Arrange - _service.ListContainerApps("sub", null, Arg.Any(), Arg.Any(), Arg.Any()) + _service.ListContainerApps("sub", null, Arg.Any(), Arg.Any()) .Returns(new ResourceQueryResults([], false)); var parseResult = _commandDefinition.Parse(["--subscription", "sub"]); @@ -156,7 +156,7 @@ public async Task ExecuteAsync_ReturnsExpectedContainerAppProperties() { // Arrange var containerApp = new ContainerAppInfo("myapp", "eastus", "myrg", "/subscriptions/sub/resourceGroups/myrg/providers/Microsoft.App/managedEnvironments/myenv", "Succeeded"); - _service.ListContainerApps("sub", null, Arg.Any(), Arg.Any(), Arg.Any()) + _service.ListContainerApps("sub", null, Arg.Any(), Arg.Any()) .Returns(new ResourceQueryResults([containerApp], false)); var parseResult = _commandDefinition.Parse(["--subscription", "sub"]);