Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 3 additions & 1 deletion docs/azmcp-commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,9 @@ azmcp aks cluster get --subscription <subscription> \

# List AKS clusters in a subscription
azmcp aks cluster list --subscription <subscription>

# List AKS cluster's nodepools
azmcp aks nodepool list --subscription <subscription> --resource-group <resource-group> --cluster <cluster>
```

### Azure Load Testing Operations
Expand Down Expand Up @@ -1200,4 +1203,3 @@ The CLI returns structured JSON responses for errors, including:

- Service availability issues
- Authentication errors

3 changes: 3 additions & 0 deletions docs/e2eTestPrompts.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,9 @@ This file contains prompts used for end-to-end testing to ensure each tool is in
| azmcp_aks_cluster_list | List all AKS clusters in my subscription |
| azmcp_aks_cluster_list | Show me my Azure Kubernetes Service clusters |
| azmcp_aks_cluster_list | What AKS clusters do I have? |
| azmcp_aks_nodepool_list | List nodepools for AKS cluster \<cluster-name> in \<resource-group> |
| azmcp_aks_nodepool_list | Show me the nodepool list for AKS cluster \<cluster-name> in \<resource-group> |
| azmcp_aks_nodepool_list | What nodepools do I have for AKS cluster \<cluster-name> in \<resource-group> |

## Azure Load Testing

Expand Down
10 changes: 9 additions & 1 deletion servers/Azure.Mcp.Server/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ The Azure MCP Server updates automatically by default whenever a new release com
- Added support for listing all Event Grid topics in a subscription via the command `azmcp_eventgrid_topic_list`. [[#43](https://github.com/microsoft/mcp/pull/43)]
- Added support for retrieving knowledge index schema information in Azure AI Foundry projects via the command `azmcp_foundry_knowledge_index_schema`. [[#41](https://github.com/microsoft/mcp/pull/41)]
- Added support for listing service health events in a subscription via the command `azmcp_resourcehealth_service-health-events_list`. [[#367](https://github.com/microsoft/mcp/pull/367)]
- Added nodepool list tool for AKS managed cluster: [[#360](https://github.com/microsoft/mcp/pull/360)]

### Breaking Changes

Expand All @@ -28,11 +29,18 @@ The Azure MCP Server updates automatically by default whenever a new release com

#### Dependency updates

<<<<<<< HEAD
- Updated the following dependencies:
- Azure.Identity: `1.14.0` → `1.15.0` [[#352](https://github.com/microsoft/mcp/pull/352)]
- Azure.Identity.Broker: `1.2.0` → `1.3.0` [[#352](https://github.com/microsoft/mcp/pull/352)]
- Microsoft.Azure.Cosmos.Aot: `0.1.1-preview.1` → `0.1.2-preview.1` [[#383](https://github.com/microsoft/mcp/pull/383)]
- Updated the following dependencies to improve .NET Ahead-of-Time (AOT) compilation support: [[#363](https://github.com/microsoft/mcp/pull/363)]
=======
- Updated the following dependencies: [[#352](https://github.com/microsoft/mcp/pull/352)]
- Azure.Identity: `1.14.0` → `1.15.0`
- Azure.Identity.Broker: `1.2.0` → `1.3.0`
- Updated the following dependencies to improve .NET Ahead-of-Time (AOT) compilation support:
>>>>>>> 94163e4f (Update CHANGELOG for rebase)
- Azure.ResourceManager.StorageCache: `1.3.1` → `1.3.2`

## 0.5.12 (2025-09-04)
Expand Down Expand Up @@ -69,7 +77,7 @@ AOT- Added a verb to the namespace name for bestpractices [[#109](https://github

#### Dependency Updates

- Updated the following dependencies to improve .NET Ahead-of-Time (AOT) compilation support:
- Updated the following dependencies to improve .NET Ahead-of-Time (AOT) compilation support:
- Microsoft.Azure.Cosmos `3.51.0` → Microsoft.Azure.Cosmos.Aot `0.1.1-preview.1`. [[#37](https://github.com/microsoft/mcp/pull/37)]

## 0.5.8 (2025-08-21)
Expand Down
1 change: 1 addition & 0 deletions servers/Azure.Mcp.Server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ The Azure MCP Server supercharges your agents with Azure context. Here are some

* "List my AKS clusters in my subscription"
* "Show me all my Azure Kubernetes Service clusters"
* "List the node pools for my AKS cluster 'my-aks-cluster' in the 'my-resource-group' resource group"

### 📊 Azure Cosmos DB

Expand Down
4 changes: 4 additions & 0 deletions tools/Azure.Mcp.Tools.Aks/src/AksSetup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Azure.Mcp.Core.Areas;
using Azure.Mcp.Core.Commands;
using Azure.Mcp.Tools.Aks.Commands.Cluster;
using Azure.Mcp.Tools.Aks.Commands.Nodepool;
using Azure.Mcp.Tools.Aks.Services;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
Expand All @@ -28,9 +29,12 @@ public void RegisterCommands(CommandGroup rootGroup, ILoggerFactory loggerFactor
// Create AKS subgroups
var cluster = new CommandGroup("cluster", "AKS cluster operations - Commands for listing and managing AKS clusters in your Azure subscription.");
aks.AddSubGroup(cluster);
var nodepool = new CommandGroup("nodepool", "AKS node pool operations - Commands for listing and managing AKS node pools for an AKS cluster.");
Comment thread
feiskyer marked this conversation as resolved.
aks.AddSubGroup(nodepool);

// Register AKS commands
cluster.AddCommand("list", new ClusterListCommand(loggerFactory.CreateLogger<ClusterListCommand>()));
cluster.AddCommand("get", new ClusterGetCommand(loggerFactory.CreateLogger<ClusterGetCommand>()));
nodepool.AddCommand("list", new NodepoolListCommand(loggerFactory.CreateLogger<NodepoolListCommand>()));
}
}
3 changes: 3 additions & 0 deletions tools/Azure.Mcp.Tools.Aks/src/Commands/AksJsonContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@

using System.Text.Json.Serialization;
using Azure.Mcp.Tools.Aks.Commands.Cluster;
using Azure.Mcp.Tools.Aks.Commands.Nodepool;

namespace Azure.Mcp.Tools.Aks.Commands;

[JsonSerializable(typeof(ClusterListCommand.ClusterListCommandResult))]
[JsonSerializable(typeof(ClusterGetCommand.ClusterGetCommandResult))]
[JsonSerializable(typeof(Models.Cluster))]
[JsonSerializable(typeof(NodepoolListCommand.NodepoolListCommandResult))]
[JsonSerializable(typeof(Models.NodePool))]
[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
internal sealed partial class AksJsonContext : JsonSerializerContext;
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using Azure.Mcp.Core.Commands;
using Azure.Mcp.Tools.Aks.Options;
using Azure.Mcp.Tools.Aks.Options.Nodepool;
using Azure.Mcp.Tools.Aks.Services;
using Microsoft.Extensions.Logging;

namespace Azure.Mcp.Tools.Aks.Commands.Nodepool;

public sealed class NodepoolListCommand(ILogger<NodepoolListCommand> logger) : BaseAksCommand<NodepoolListOptions>
{
private const string CommandTitle = "List AKS Node Pools";
private readonly ILogger<NodepoolListCommand> _logger = logger;

private readonly Option<string> _clusterNameOption = AksOptionDefinitions.Cluster;

public override string Name => "list";

public override string Description =>
"""
List all node pools for a specific Azure Kubernetes Service (AKS) cluster.
Returns key node pool details including sizing, count, OS type, mode, and autoscaling settings.
""";

public override string Title => CommandTitle;

public override ToolMetadata Metadata => new() { Destructive = false, ReadOnly = true };

protected override void RegisterOptions(Command command)
{
base.RegisterOptions(command);
RequireResourceGroup();
command.Options.Add(_clusterNameOption);
}

protected override NodepoolListOptions BindOptions(ParseResult parseResult)
{
var options = base.BindOptions(parseResult);
options.ClusterName = parseResult.GetValue(_clusterNameOption);
return options;
}

public override async Task<CommandResponse> ExecuteAsync(CommandContext context, ParseResult parseResult)
{
if (!Validate(parseResult.CommandResult, context.Response).IsValid)
{
return context.Response;
}

var options = BindOptions(parseResult);

try
{
var aksService = context.GetService<IAksService>();
var nodePools = await aksService.ListNodePools(
options.Subscription!,
options.ResourceGroup!,
options.ClusterName!,
options.Tenant,
options.RetryPolicy);

context.Response.Results = nodePools?.Count > 0 ?
ResponseResult.Create(
new NodepoolListCommandResult(nodePools),
AksJsonContext.Default.NodepoolListCommandResult) :
null;
}
catch (Exception ex)
{
_logger.LogError(ex,
"Error listing AKS node pools. Subscription: {Subscription}, ResourceGroup: {ResourceGroup}, ClusterName: {ClusterName}, Options: {@Options}",
options.Subscription, options.ResourceGroup, options.ClusterName, options);
HandleException(context, ex);
}

return context.Response;
}

protected override string GetErrorMessage(Exception ex) => ex switch
{
RequestFailedException reqEx when reqEx.Status == 404 =>
"AKS cluster or node pools not found. Verify the cluster name, resource group, and subscription, and ensure you have access.",
RequestFailedException reqEx when reqEx.Status == 403 =>
$"Authorization failed accessing AKS node pools. Details: {reqEx.Message}",
RequestFailedException reqEx => reqEx.Message,
_ => base.GetErrorMessage(ex)
};

protected override int GetStatusCode(Exception ex) => ex switch
{
RequestFailedException reqEx => reqEx.Status,
_ => base.GetStatusCode(ex)
};

internal record NodepoolListCommandResult(List<Models.NodePool> NodePools);
}

38 changes: 38 additions & 0 deletions tools/Azure.Mcp.Tools.Aks/src/Models/NodePool.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

namespace Azure.Mcp.Tools.Aks.Models;

public class NodePool
{
/// <summary> Name of the node pool (agent pool). </summary>
public string? Name { get; set; }

/// <summary> Number of nodes in the pool. </summary>
public int? NodeCount { get; set; }

/// <summary> VM size of the agent nodes. </summary>
public string? NodeVmSize { get; set; }

/// <summary> OS type of the node pool. </summary>
public string? OsType { get; set; }

/// <summary> Pool mode (System/User). </summary>
public string? Mode { get; set; }

/// <summary> Kubernetes/orchestrator version for the pool. </summary>
public string? OrchestratorVersion { get; set; }

/// <summary> Whether cluster autoscaler is enabled for this pool. </summary>
public bool? EnableAutoScaling { get; set; }

/// <summary> Minimum node count when autoscaling is enabled. </summary>
public int? MinCount { get; set; }

/// <summary> Maximum node count when autoscaling is enabled. </summary>
public int? MaxCount { get; set; }

/// <summary> Provisioning state of the node pool resource. </summary>
public string? ProvisioningState { get; set; }
}

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.Aks.Options.Nodepool;

public class NodepoolListOptions : BaseAksOptions
{
[JsonPropertyName(AksOptionDefinitions.ClusterName)]
public string? ClusterName { get; set; }
}

82 changes: 82 additions & 0 deletions tools/Azure.Mcp.Tools.Aks/src/Services/AksService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public sealed class AksService(

private const string CacheGroup = "aks";
private const string AksClustersCacheKey = "clusters";
private const string AksNodePoolsCacheKey = "nodepools";
private static readonly TimeSpan s_cacheDuration = TimeSpan.FromHours(1);

public async Task<List<Cluster>> ListClusters(
Expand Down Expand Up @@ -121,6 +122,68 @@ public async Task<List<Cluster>> ListClusters(
}
}

public async Task<List<NodePool>> ListNodePools(
string subscription,
string resourceGroup,
string clusterName,
string? tenant = null,
RetryPolicyOptions? retryPolicy = null)
{
ValidateRequiredParameters(subscription, resourceGroup, clusterName);
Comment thread
feiskyer marked this conversation as resolved.

// Create cache key
var cacheKey = string.IsNullOrEmpty(tenant)
? $"{AksNodePoolsCacheKey}_{subscription}_{resourceGroup}_{clusterName}"
: $"{AksNodePoolsCacheKey}_{subscription}_{resourceGroup}_{clusterName}_{tenant}";

// Try to get from cache first
var cachedNodePools = await _cacheService.GetAsync<List<NodePool>>(CacheGroup, cacheKey, s_cacheDuration);
if (cachedNodePools != null)
{
return cachedNodePools;
}

var subscriptionResource = await _subscriptionService.GetSubscription(subscription, tenant, retryPolicy);
var nodePools = new List<NodePool>();

try
{
var resourceGroupResource = await subscriptionResource.GetResourceGroupAsync(resourceGroup);
if (resourceGroupResource?.Value == null)
{
return nodePools;
}

var clusterResource = await resourceGroupResource.Value
.GetContainerServiceManagedClusters()
.GetAsync(clusterName);

if (clusterResource?.Value == null)
{
return nodePools;
}

await foreach (var agentPool in clusterResource.Value
.GetContainerServiceAgentPools()
.GetAllAsync())
{
if (agentPool?.Data != null)
{
nodePools.Add(ConvertToNodePoolModel(agentPool));
}
}

// Cache the results
await _cacheService.SetAsync(CacheGroup, cacheKey, nodePools, s_cacheDuration);
}
catch (Exception ex)
{
throw new Exception($"Error retrieving AKS node pools for cluster '{clusterName}': {ex.Message}", ex);
}

return nodePools;
}

private static Cluster ConvertToClusterModel(ContainerServiceManagedClusterResource clusterResource)
{
var data = clusterResource.Data;
Expand Down Expand Up @@ -149,4 +212,23 @@ private static Cluster ConvertToClusterModel(ContainerServiceManagedClusterResou
Tags = data.Tags?.ToDictionary(kvp => kvp.Key, kvp => kvp.Value)
};
}

private static NodePool ConvertToNodePoolModel(ContainerServiceAgentPoolResource agentPoolResource)
{
var data = agentPoolResource.Data;

return new NodePool
{
Name = data.Name,
NodeCount = data.Count,
NodeVmSize = data.VmSize?.ToString(),
OsType = data.OSType?.ToString(),
Mode = data.Mode?.ToString(),
OrchestratorVersion = data.OrchestratorVersion,
EnableAutoScaling = data.EnableAutoScaling,
MinCount = data.MinCount,
MaxCount = data.MaxCount,
ProvisioningState = data.ProvisioningState?.ToString()
};
}
}
7 changes: 7 additions & 0 deletions tools/Azure.Mcp.Tools.Aks/src/Services/IAksService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,11 @@ Task<List<Cluster>> ListClusters(
string resourceGroup,
string? tenant = null,
RetryPolicyOptions? retryPolicy = null);

Task<List<NodePool>> ListNodePools(
string subscription,
string resourceGroup,
string clusterName,
string? tenant = null,
RetryPolicyOptions? retryPolicy = null);
}
Loading