Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
changes:
- section: "Features Added"
description: "Added `azmcp containerapps list` command to list Azure Container Apps in a subscription"
13 changes: 13 additions & 0 deletions servers/Azure.Mcp.Server/docs/azmcp-commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -1258,6 +1258,19 @@ azmcp confidentialledger entries get --ledger <ledger-name> \
- `--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 list --subscription <subscription>

# List Azure Container Apps in a specific resource group
# ❌ Destructive | ✅ Idempotent | ❌ OpenWorld | ✅ ReadOnly | ❌ Secret | ❌ LocalRequired
azmcp containerapps list --subscription <subscription> \
[--resource-group <resource-group>]
```

### Azure Container Registry (ACR) Operations

```bash
Expand Down
9 changes: 9 additions & 0 deletions servers/Azure.Mcp.Server/docs/e2eTestPrompts.md
Original file line number Diff line number Diff line change
Expand Up @@ -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_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 <resource_group_name> |
| containerapps_list | Show me the container apps in resource group <resource_group_name> |

## Azure Container Registry (ACR)

| Tool Name | Test Prompt |
Expand Down
1 change: 1 addition & 0 deletions servers/Azure.Mcp.Server/src/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -2258,7 +2258,8 @@
"acr_registry_list",
"acr_registry_repository_list",
"aks_cluster_get",
"aks_nodepool_get"
"aks_nodepool_get",
"containerapps_list"
]
},
{
Expand Down
7 changes: 7 additions & 0 deletions tools/Azure.Mcp.Tools.ContainerApps/src/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -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")]
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<IsAotCompatible>true</IsAotCompatible>
</PropertyGroup>
<ItemGroup>
<EmbeddedResource Include="**\Resources\*.txt" />
<EmbeddedResource Include="**\Resources\*.json" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\core\Azure.Mcp.Core\src\Azure.Mcp.Core.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Azure.ResourceManager" />
<PackageReference Include="Azure.ResourceManager.AppContainers" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
<PackageReference Include="ModelContextProtocol" />
<PackageReference Include="System.CommandLine" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -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<TOptions> 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<string>(OptionDefinitions.Common.ResourceGroup.Name);
return options;
}
}
Original file line number Diff line number Diff line change
@@ -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<ContainerAppListCommand> logger, IContainerAppsService containerAppsService) : BaseContainerAppsCommand<ContainerAppListOptions>
{
private const string CommandTitle = "List Container Apps";
private readonly ILogger<ContainerAppListCommand> _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<CommandResponse> 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<Models.ContainerAppInfo> ContainerApps, bool AreResultsTruncated);
}
Original file line number Diff line number Diff line change
@@ -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
{
}
34 changes: 34 additions & 0 deletions tools/Azure.Mcp.Tools.ContainerApps/src/ContainerAppsSetup.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// 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<IContainerAppsService, ContainerAppsService>();

services.AddSingleton<ContainerAppListCommand>();
}

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 containerAppList = serviceProvider.GetRequiredService<ContainerAppListCommand>();
containerapps.AddCommand(containerAppList.Name, containerAppList);

return containerapps;
}
}
6 changes: 6 additions & 0 deletions tools/Azure.Mcp.Tools.ContainerApps/src/GlobalUsings.cs
Original file line number Diff line number Diff line change
@@ -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;
Original file line number Diff line number Diff line change
@@ -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);
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

namespace Azure.Mcp.Tools.ContainerApps.Options.ContainerApp;

public class ContainerAppListOptions : SubscriptionOptions
{
}
Original file line number Diff line number Diff line change
@@ -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<ResourceQueryResults<ContainerAppInfo>> 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);
}
}
Original file line number Diff line number Diff line change
@@ -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<ResourceQueryResults<ContainerAppInfo>> ListContainerApps(
string subscription,
string? resourceGroup = null,
string? tenant = null,
RetryPolicyOptions? retryPolicy = null,
CancellationToken cancellationToken = default);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="NSubstitute" />
<PackageReference Include="NSubstitute.Analyzers.CSharp" />
<PackageReference Include="xunit.v3" />
<PackageReference Include="xunit.v3.assert" />
<PackageReference Include="xunit.runner.visualstudio" />
<PackageReference Include="coverlet.collector" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\Azure.Mcp.Tools.ContainerApps.csproj" />
<ProjectReference Include="$(RepoRoot)core\Azure.Mcp.Core\tests\Azure.Mcp.Tests\Azure.Mcp.Tests.csproj" />
</ItemGroup>
</Project>
Loading
Loading