diff --git a/.vscode/cspell.json b/.vscode/cspell.json
index abd6d6a3f..aba55eb6e 100644
--- a/.vscode/cspell.json
+++ b/.vscode/cspell.json
@@ -6,7 +6,7 @@
"csharp",
"node",
"powershell",
- "softwareTerms",
+ "softwareterms",
"typescript",
"users"
],
@@ -61,25 +61,10 @@
{
"name": "baseline",
"words": [
- "%2Fmcp",
- "AADSTS",
- "ACCESSTOKEN",
- "Commmand",
- "Commitish",
- "Fmcp",
- "Groq",
- "HKCU",
- "HKEY_CURRENT_USER",
- "Hyperscale",
- "Intune",
- "LASTEXITCODE",
- "LPUTF8Str",
- "Ollama",
- "Roboto",
- "Segoe",
- "VSTEST",
- "Xunit",
+ "%2fmcp",
+ "aadsts",
"accessibilities",
+ "accesstoken",
"adadmin",
"addattachment",
"adminprovider",
@@ -109,14 +94,16 @@
"cobertura",
"codeql",
"codesign",
+ "commitish",
+ "commmand",
"contentfiles",
"credscan",
"cslschema",
"cutover",
"datatable",
+ "datetime",
"datistemplate",
"datname",
- "datetime",
"descired",
"devcert",
"deviceid",
@@ -130,17 +117,25 @@
"existingaccount",
"fabmcp",
"filestorage",
+ "fmcp",
"fname",
"funkyfoo",
"gdnbaselines",
"globaltool",
"glsl",
+ "groq",
+ "hkcu",
+ "hkey_current_user",
"hotmail",
+ "hyperscale",
+ "intune",
"kcsb",
+ "lastexitcode",
"libc",
"libgcc",
"locproj",
"logissue",
+ "lputf8str",
"maxdepth",
"mcptestadmin",
"mgmt",
@@ -166,6 +161,7 @@
"notcontains",
"notlike",
"nslookup",
+ "ollama",
"otel",
"otlp",
"pipefail",
@@ -175,9 +171,11 @@
"quickstart",
"reportgenerator",
"reporttypes",
- "resx",
"resourcegroups",
+ "resx",
+ "roboto",
"securestring",
+ "segoe",
"setvariable",
"skus",
"storageaccount",
@@ -201,32 +199,35 @@
"vectorizable",
"vectorizer",
"vsix",
- "vsixtarget"
+ "vsixtarget",
+ "vstest",
+ "xunit"
]
}
],
"words": [
"1espt",
"aarch",
- "accesspolicy",
"acaenvironment",
- "ADMINPROVIDER",
+ "accesspolicy",
+ "adminprovider",
"agentic",
"aisearch",
"akscluster",
"aksservice",
"alcoop",
- "AOAI",
"amlfs",
- "Apim",
+ "aoai",
+ "apim",
"appconfig",
"applens",
"appservice",
- "ASPNETCORE",
+ "aspnetcore",
"australiacentral",
"australiaeast",
"australiasoutheast",
- "Autorenewable",
+ "autorenewable",
+ "autoscaler",
"azapi",
"azcli",
"azext",
@@ -275,12 +276,13 @@
"bdylan",
"bestpractices",
"bicepschema",
- "BINLOG",
+ "binlog",
"binutils",
+ "blazor",
"brazilsouth",
"brazilsoutheast",
"breathability",
- "Byol",
+ "byol",
"canadacentral",
"canadaeast",
"centralindia",
@@ -290,18 +292,18 @@
"cloudarchitect",
"codegen",
"codeium",
- "CODEOWNERS",
+ "codeowners",
"codesign",
- "Codespace",
+ "codespace",
"cognitiveservices",
"containerapp",
"containerapps",
- "CONTENTAZUREFILECONNECTIONSTRING",
- "CONTENTSHARE",
+ "contentazurefileconnectionstring",
+ "contentshare",
"contoso",
- "CONV",
+ "conv",
"copilotmd",
- "Cosell",
+ "cosell",
"csdevkit",
"cslschema",
"cvzf",
@@ -312,22 +314,22 @@
"dataverse",
"dbforpostgresql",
"deallocate",
- "DEBUGTELEMETRY",
+ "debugtelemetry",
"devbox",
"devcontainers",
"discoverability",
- "Distributedtask",
- "dotnettools",
+ "distributedtask",
"dotenv",
+ "dotnettools",
"drawcord",
- "DUMPFILE",
+ "dumpfile",
"eastasia",
"eastus",
"eastus2euap",
"enumerables",
"eslintcache",
"esrp",
- "ESRPRELPACMANTEST",
+ "esrprelpacmantest",
"eventgrid",
"eventhouse",
"exfiltration",
@@ -345,9 +347,9 @@
"germanynorth",
"gethealth",
"grpcio",
- "Gsaascend",
- "Gsamas",
- "GZRS",
+ "gsaascend",
+ "gsamas",
+ "gzrs",
"healthmodels",
"heatmaps",
"hnsw",
@@ -357,7 +359,9 @@
"idempotency",
"idtyp",
"indonesiacentral",
- "INFILE",
+ "infile",
+ "intelli",
+ "intellij",
"israelcentral",
"italynorth",
"japaneast",
@@ -370,69 +374,73 @@
"keyvault",
"koreacentral",
"koreasouth",
- "Kusto",
+ "kusto",
"kvps",
"lakehouse",
+ "laskewitz",
"ligar",
"linkedservices",
- "Linq",
- "LINUXOS",
- "LINUXPOOL",
- "LINUXVMIMAGE",
- "LLM",
+ "linq",
+ "linuxos",
+ "linuxpool",
+ "linuxvmimage",
+ "llm",
"loadtest",
"loadtesting",
"loadtestrun",
"loadtests",
"lucene",
- "MACOS",
- "MACPOOL",
- "MACVMIMAGE",
+ "macos",
+ "macpool",
+ "macvmimage",
"malaysiawest",
"markitdown",
"mcpserver",
"mcptmp",
"mexicocentral",
- "midsole",
- "Microbundle",
+ "microbundle",
"microsoftdocs",
+ "midsole",
"monitoredresources",
"msal",
- "MSRP",
+ "msrp",
"myaccount",
"myacr",
"myapp",
"mycluster",
"myfilesystem",
"mygroup",
- "myworkbook",
"mysvc",
+ "myworkbook",
"netstandard",
+ "newtonsoft",
"newzealandnorth",
- "Newtonsoft",
- "Npgsql",
- "nupkg",
+ "nodepool",
+ "nodepools",
"norequired",
"northcentralus",
"northeurope",
"norwayeast",
"norwaywest",
+ "npgsql",
"npmjs",
+ "nugets",
+ "nupkg",
"nuxt",
- "Occured",
+ "occured",
"odata",
"oidc",
"onboarded",
"openai",
"operationalinsights",
- "OUTFILE",
+ "outfile",
"packability",
"pageable",
"payg",
"paygo",
"pgrep",
- "piechart",
"pids",
+ "piechart",
"polandcentral",
"portalsettings",
"predeploy",
@@ -441,17 +449,19 @@
"pscore",
"pscustomobject",
"pullrequest",
+ "ragrs",
+ "ragzrs",
"rainfly",
- "RAGRS",
- "RAGZRS",
- "RediSearch",
+ "redisearch",
"resourcegroup",
"resourcegroups",
"resourcehealth",
+ "rhtest",
"rhvm",
- "Runtimes",
+ "runtimes",
"searchdocs",
"serverfarms",
+ "serverjson",
"servicebus",
"sessionhost",
"setparam",
@@ -465,11 +475,13 @@
"southeastasia",
"southindia",
"spaincentral",
+ "systempool",
"staticwebapp",
"staticwebapps",
"storageaccount",
"storageaccounts",
"submode",
+ "subresource",
"swedencentral",
"swedensouth",
"switzerlandnorth",
@@ -492,9 +504,10 @@
"uaenorth",
"uksouth",
"ukwest",
- "UNCOMPRESS",
- "UNHEX",
- "Upns",
+ "uncompress",
+ "unhex",
+ "upns",
+ "userpool",
"usersession",
"vectorizable",
"vectorizer",
@@ -502,7 +515,7 @@
"versionsuffix",
"virtualdesktop",
"virtualmachines",
- "Vnet",
+ "vnet",
"vscodeignore",
"vsmarketplace",
"vsts",
@@ -512,12 +525,12 @@
"westus",
"westus2",
"westus3",
+ "windowsos",
+ "windowspool",
+ "windowsvmimage",
"winget",
- "WINDOWSOS",
- "WINDOWSPOOL",
- "WINDOWSVMIMAGE",
"wscript",
- "xvfb",
- "Xunit"
+ "xunit",
+ "xvfb"
]
}
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 37953169b..d1d15c78b 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -26,7 +26,6 @@
-
diff --git a/core/Azure.Mcp.Core/src/Services/Azure/BaseAzureResourceService.cs b/core/Azure.Mcp.Core/src/Services/Azure/BaseAzureResourceService.cs
index 12db14ba3..b14b48023 100644
--- a/core/Azure.Mcp.Core/src/Services/Azure/BaseAzureResourceService.cs
+++ b/core/Azure.Mcp.Core/src/Services/Azure/BaseAzureResourceService.cs
@@ -52,12 +52,26 @@ private async Task GetTenantResourceAsync(Guid? tenantId, Cancel
return tenantResource;
}
+ ///
+ /// Validates that the specified resource group exists within the given subscription.
+ ///
+ /// The subscription resource to check against.
+ /// The name of the resource group to validate.
+ /// Cancellation token.
+ /// True if the resource group exists; otherwise, false.
+ private async Task ValidateResourceGroupExistsAsync(SubscriptionResource subscriptionResource, string resourceGroupName, CancellationToken cancellationToken = default)
+ {
+ var resourceGroupCollection = subscriptionResource.GetResourceGroups();
+ var result = await resourceGroupCollection.ExistsAsync(resourceGroupName, cancellationToken).ConfigureAwait(false);
+ return result.Value;
+ }
+
///
/// Executes a Resource Graph query and returns a list of resources of the specified type.
///
/// The type to convert each resource to
/// The Azure resource type to query for (e.g., "Microsoft.Sql/servers/databases")
- /// The resource group name to filter by
+ /// The resource group name to filter by (null to query all resource groups)
/// The subscription ID or name
/// Optional retry policy configuration
/// Function to convert JsonElement to the target type
@@ -67,7 +81,7 @@ private async Task GetTenantResourceAsync(Guid? tenantId, Cancel
/// List of resources converted to the specified type
protected async Task> ExecuteResourceQueryAsync(
string resourceType,
- string resourceGroup,
+ string? resourceGroup,
string subscription,
RetryPolicyOptions? retryPolicy,
Func converter,
@@ -75,7 +89,7 @@ protected async Task> ExecuteResourceQueryAsync(
int limit = 50,
CancellationToken cancellationToken = default)
{
- ValidateRequiredParameters(resourceType, resourceGroup, subscription);
+ ValidateRequiredParameters(resourceType, subscription);
ArgumentNullException.ThrowIfNull(converter);
var results = new List();
@@ -83,7 +97,15 @@ protected async Task> ExecuteResourceQueryAsync(
var subscriptionResource = await _subscriptionService.GetSubscription(subscription, null, retryPolicy);
var tenantResource = await GetTenantResourceAsync(subscriptionResource.Data.TenantId, cancellationToken);
- var queryFilter = $"Resources | where type =~ '{EscapeKqlString(resourceType)}' and resourceGroup =~ '{EscapeKqlString(resourceGroup)}'";
+ var queryFilter = $"Resources | where type =~ '{EscapeKqlString(resourceType)}'";
+ if (!string.IsNullOrEmpty(resourceGroup))
+ {
+ if (!await ValidateResourceGroupExistsAsync(subscriptionResource, resourceGroup, cancellationToken))
+ {
+ throw new KeyNotFoundException($"Resource group '{resourceGroup}' does not exist in subscription '{subscriptionResource.Data.SubscriptionId}'");
+ }
+ queryFilter += $" and resourceGroup =~ '{EscapeKqlString(resourceGroup)}'";
+ }
if (!string.IsNullOrEmpty(additionalFilter))
{
queryFilter += $" and {additionalFilter}";
@@ -117,7 +139,7 @@ protected async Task> ExecuteResourceQueryAsync(
///
/// The type to convert the resource to
/// The Azure resource type to query for (e.g., "Microsoft.Sql/servers/databases")
- /// The resource group name to filter by
+ /// The resource group name to filter by (null to query all resource groups)
/// The subscription ID or name
/// Optional retry policy configuration
/// Function to convert JsonElement to the target type
@@ -126,20 +148,28 @@ protected async Task> ExecuteResourceQueryAsync(
/// Single resource converted to the specified type, or null if not found
protected async Task ExecuteSingleResourceQueryAsync(
string resourceType,
- string resourceGroup,
+ string? resourceGroup,
string subscription,
RetryPolicyOptions? retryPolicy,
Func converter,
string? additionalFilter = null,
CancellationToken cancellationToken = default) where T : class
{
- ValidateRequiredParameters(resourceType, resourceGroup, subscription);
+ ValidateRequiredParameters(resourceType, subscription);
ArgumentNullException.ThrowIfNull(converter);
var subscriptionResource = await _subscriptionService.GetSubscription(subscription, null, retryPolicy);
var tenantResource = await GetTenantResourceAsync(subscriptionResource.Data.TenantId, cancellationToken);
- var queryFilter = $"Resources | where type =~ '{EscapeKqlString(resourceType)}' and resourceGroup =~ '{EscapeKqlString(resourceGroup)}'";
+ var queryFilter = $"Resources | where type =~ '{EscapeKqlString(resourceType)}'";
+ if (!string.IsNullOrEmpty(resourceGroup))
+ {
+ if (!await ValidateResourceGroupExistsAsync(subscriptionResource, resourceGroup, cancellationToken))
+ {
+ throw new KeyNotFoundException($"Resource group '{resourceGroup}' does not exist in subscription '{subscriptionResource.Data.SubscriptionId}'");
+ }
+ queryFilter += $" and resourceGroup =~ '{EscapeKqlString(resourceGroup)}'";
+ }
if (!string.IsNullOrEmpty(additionalFilter))
{
queryFilter += $" and {additionalFilter}";
diff --git a/docs/new-command.md b/docs/new-command.md
index a7a8215d4..2b46a8cae 100644
--- a/docs/new-command.md
+++ b/docs/new-command.md
@@ -2119,7 +2119,6 @@ Before submitting:
- [ ] **AOT compilation verified** with `./eng/scripts/Build-Local.ps1 -BuildNative`
- [ ] **Clean up unused using statements**: Run `dotnet format --include="tools/Azure.Mcp.Tools.{Toolset}/**/*.cs"` to remove unnecessary imports and ensure consistent formatting
- [ ] Fix formatting issues with `dotnet format ./AzureMcp.sln` and ensure no warnings
-- [ ] Identify unused properties for Azure Resource with `.\eng\scripts\Check-Unused-ResourceProperties.ps1`
### Azure SDK Integration
- [ ] All Azure SDK property names verified and correct
diff --git a/eng/scripts/Check-Unused-ResourceProperties.ps1 b/eng/scripts/Check-Unused-ResourceProperties.ps1
deleted file mode 100644
index efb00b045..000000000
--- a/eng/scripts/Check-Unused-ResourceProperties.ps1
+++ /dev/null
@@ -1,297 +0,0 @@
-# PowerShell script to check for unused properties in Services/Models that are not used in Models mapping
-param(
- [string]$AreaName = "",
- [switch]$Verbose = $false
-)
-
-function Get-PropertyUsageFromMapping {
- param(
- [string]$ServiceFilePath,
- [string]$ServicesModelClassName
- )
-
- if (-not (Test-Path $ServiceFilePath)) {
- return @()
- }
-
- $content = Get-Content $ServiceFilePath -Raw
-
- # Handle special cases based on class name patterns
- if ($ServicesModelClassName -match "Properties$") {
- # For Properties classes, we need to find the specific variable that uses this Properties type
- # First, find the corresponding Data class name (e.g., SqlDatabaseProperties -> SqlDatabaseData)
- $dataClassName = $ServicesModelClassName -replace "Properties$", "Data"
-
- # Look for variable declaration pattern: DataType? variableName = DataType.FromJson(...)
- $variablePattern = "\b$dataClassName\?\s+(\w+)\s*=\s*$dataClassName\.FromJson"
- $variableMatch = [regex]::Match($content, $variablePattern)
-
- if (-not $variableMatch.Success) {
- # If no variable uses this Data class, then all Properties are unused
- return @()
- }
-
- $variableName = $variableMatch.Groups[1].Value
-
- # Look for nested property access: variableName.Properties?.PropertyName
- $propertyPattern = "\b$variableName\.Properties\?\.\s*([A-Z][a-zA-Z0-9_]*)"
- $propertyMatches = [regex]::Matches($content, $propertyPattern)
-
- $usedProperties = @()
- foreach ($match in $propertyMatches) {
- $propName = $match.Groups[1].Value
- if ($propName -notmatch '^(ToString|GetHashCode|Equals|GetType|Split|LastOrDefault)$') {
- $usedProperties += $propName
- }
- }
-
- return $usedProperties | Sort-Object -Unique
- }
- elseif ($ServicesModelClassName -eq "SqlSku") {
- # Look for Sku property access: variable.Sku.PropertyName
- $propertyPattern = "\w+\.Sku\.\s*([A-Z][a-zA-Z0-9_]*)"
- $propertyMatches = [regex]::Matches($content, $propertyPattern)
-
- $usedProperties = @()
- foreach ($match in $propertyMatches) {
- $propName = $match.Groups[1].Value
- if ($propName -notmatch '^(ToString|GetHashCode|Equals|GetType|Split|LastOrDefault)$') {
- $usedProperties += $propName
- }
- }
-
- return $usedProperties | Sort-Object -Unique
- }
- elseif ($ServicesModelClassName -eq "SqlElasticPoolPerDatabaseSettings") {
- # Look for PerDatabaseSettings property access: variable.Properties.PerDatabaseSettings.PropertyName
- $propertyPattern = "\w+\.Properties\.PerDatabaseSettings\.\s*([A-Z][a-zA-Z0-9_]*)"
- $propertyMatches = [regex]::Matches($content, $propertyPattern)
-
- $usedProperties = @()
- foreach ($match in $propertyMatches) {
- $propName = $match.Groups[1].Value
- if ($propName -notmatch '^(ToString|GetHashCode|Equals|GetType|Split|LastOrDefault)$') {
- $usedProperties += $propName
- }
- }
-
- return $usedProperties | Sort-Object -Unique
- }
-
- # For regular classes, look for variable assignment pattern: Type? variableName = Type.FromJson(...)
- $variablePattern = "\b$ServicesModelClassName\?\s+(\w+)\s*=\s*$ServicesModelClassName\.FromJson"
- $variableMatch = [regex]::Match($content, $variablePattern)
-
- if (-not $variableMatch.Success) {
- # If no variable uses this Services/Models class, then all its properties are unused
- return @()
- }
-
- $variableName = $variableMatch.Groups[1].Value
-
- # Look for usage of this variable's properties: variableName.PropertyName or variableName?.PropertyName
- $propertyPattern = "\b$variableName(?:\.|->|\?\.)\s*([A-Z][a-zA-Z0-9_]*)"
- $propertyMatches = [regex]::Matches($content, $propertyPattern)
-
- $usedProperties = @()
- foreach ($match in $propertyMatches) {
- $propName = $match.Groups[1].Value
- if ($propName -notmatch '^(ToString|GetHashCode|Equals|GetType|Split|LastOrDefault)$') {
- $usedProperties += $propName
- }
- }
-
- return $usedProperties | Sort-Object -Unique
-}
-
-function Get-PropertiesFromServicesModel {
- param(
- [string]$FilePath
- )
-
- if (-not (Test-Path $FilePath)) {
- return @()
- }
-
- $content = Get-Content $FilePath -Raw
-
- # Extract public properties using regex
- $propertyPattern = '\s*(?:public|internal)\s+[^{};]+?\s+([A-Z][a-zA-Z0-9_]*)\s*\{\s*get;?\s*set;?\s*\}'
- $propertyMatches = [regex]::Matches($content, $propertyPattern)
-
- $properties = @()
- foreach ($match in $propertyMatches) {
- $properties += $match.Groups[1].Value
- }
-
- return $properties | Sort-Object -Unique
-}
-
-function Check-AreaPropertyUsage {
- param(
- [string]$AreaPath
- )
-
- $areaName = Split-Path $AreaPath -Leaf
- Write-Host "`n=== Checking Area: $areaName ===" -ForegroundColor Cyan
-
- $srcPath = Join-Path $AreaPath "src"
- if (-not (Test-Path $srcPath)) {
- Write-Warning "No src folder found in $AreaPath"
- return
- }
-
- $projectFolders = Get-ChildItem $srcPath -Directory | Where-Object { $_.Name -like "AzureMcp.*" }
-
- if ($projectFolders.Count -eq 0) {
- Write-Warning "No AzureMcp project folder found in $srcPath"
- return
- }
-
- $projectPath = $projectFolders[0].FullName
- $servicesPath = Join-Path $projectPath "Services"
- $modelsPath = Join-Path $projectPath "Models"
- $servicesModelsPath = Join-Path $servicesPath "Models"
-
- if (-not (Test-Path $servicesModelsPath)) {
- Write-Host "No Services/Models folder found in $projectPath" -ForegroundColor Yellow
- return
- }
-
- if (-not (Test-Path $modelsPath)) {
- Write-Host "No Models folder found in $projectPath" -ForegroundColor Yellow
- return
- }
-
- # Find the service file
- $serviceFiles = Get-ChildItem $servicesPath -Filter "*Service.cs" | Where-Object { $_.Name -notlike "I*Service.cs" }
- if ($serviceFiles.Count -eq 0) {
- Write-Warning "No service implementation file found in $servicesPath"
- return
- }
-
- $serviceFilePath = $serviceFiles[0].FullName
-
- # Get all model files in the Models folder
- $modelFiles = Get-ChildItem $modelsPath -Filter "*.cs"
-
- $allUnusedProperties = @{}
- $totalServicesModelFiles = 0
- $totalUnusedProperties = 0
-
- # Get all Services/Models files
- $servicesModelFiles = Get-ChildItem $servicesModelsPath -Filter "*.cs"
-
- foreach ($servicesModelFile in $servicesModelFiles) {
- $totalServicesModelFiles++
- $fileName = $servicesModelFile.BaseName
-
- if ($Verbose) {
- Write-Host " Checking Services/Models/$fileName.cs" -ForegroundColor Gray
- }
-
- # Get properties from Services/Models file
- $servicesProperties = Get-PropertiesFromServicesModel $servicesModelFile.FullName
-
- if ($servicesProperties.Count -eq 0) {
- if ($Verbose) {
- Write-Host " No properties found in $fileName" -ForegroundColor Gray
- }
- continue
- }
-
- # Check usage for this specific Services/Models class
- $usedProperties = Get-PropertyUsageFromMapping $serviceFilePath $fileName
-
- if ($Verbose) {
- Write-Host " Properties in $fileName`: $($servicesProperties -join ', ')" -ForegroundColor Gray
- Write-Host " Used properties for $fileName`: $($usedProperties -join ', ')" -ForegroundColor Gray
- }
-
- # Find unused properties
- $unusedProperties = $servicesProperties | Where-Object { $_ -notin $usedProperties }
-
- if ($unusedProperties.Count -gt 0) {
- $allUnusedProperties[$fileName] = $unusedProperties
- $totalUnusedProperties += $unusedProperties.Count
-
- Write-Host " $fileName.cs:" -ForegroundColor Yellow
- foreach ($prop in $unusedProperties) {
- Write-Host " - $prop" -ForegroundColor Red
- }
- }
- elseif ($Verbose) {
- Write-Host " All properties are used" -ForegroundColor Green
- }
- }
-
- # Summary for this area
- Write-Host "`n Summary for ${areaName}:" -ForegroundColor Cyan
- Write-Host " Services/Models files checked: $totalServicesModelFiles"
- Write-Host " Files with unused properties: $($allUnusedProperties.Count)"
- Write-Host " Total unused properties: $totalUnusedProperties"
-
- return @{
- AreaName = $areaName
- UnusedProperties = $allUnusedProperties
- TotalFiles = $totalServicesModelFiles
- FilesWithUnused = $allUnusedProperties.Count
- TotalUnused = $totalUnusedProperties
- }
-}
-
-# Main execution
-$rootPath = Join-Path $PSScriptRoot ".." ".."
-$areasPath = Join-Path $rootPath "areas"
-
-if (-not (Test-Path $areasPath)) {
- Write-Error "Areas folder not found at $areasPath"
- exit 1
-}
-
-$results = @()
-
-if ($AreaName) {
- $areaPath = Join-Path $areasPath $AreaName
- if (Test-Path $areaPath) {
- $results += Check-AreaPropertyUsage $areaPath
- } else {
- Write-Error "Area '$AreaName' not found"
- exit 1
- }
-} else {
- $areas = Get-ChildItem $areasPath -Directory | Sort-Object Name
-
- foreach ($area in $areas) {
- $results += Check-AreaPropertyUsage $area.FullName
- }
-}
-
-# Overall summary
-Write-Host "`n === OVERALL SUMMARY ===" -ForegroundColor Cyan
-
-$totalAreas = $results.Count
-$totalServicesModelFiles = ($results | Measure-Object -Property TotalFiles -Sum).Sum
-$totalFilesWithUnused = ($results | Measure-Object -Property FilesWithUnused -Sum).Sum
-$totalUnusedProperties = ($results | Measure-Object -Property TotalUnused -Sum).Sum
-
-Write-Host "Areas checked: $totalAreas"
-Write-Host "Total Services/Models files: $totalServicesModelFiles"
-Write-Host "Files with unused properties: $totalFilesWithUnused"
-Write-Host "Total unused properties: $totalUnusedProperties"
-
-if ($totalUnusedProperties -gt 0) {
- Write-Host "`nAreas with unused properties:" -ForegroundColor Yellow
- foreach ($result in $results | Where-Object { $_.TotalUnused -gt 0 }) {
- Write-Host " $($result.AreaName): $($result.TotalUnused) unused properties in $($result.FilesWithUnused) files" -ForegroundColor Yellow
-
- foreach ($fileName in $result.UnusedProperties.Keys) {
- Write-Host " $fileName.cs:" -ForegroundColor Gray
- foreach ($prop in $result.UnusedProperties[$fileName]) {
- Write-Host " - $prop" -ForegroundColor Red
- }
- }
- }
-} else {
- Write-Host "`nAll properties are being used! ✅" -ForegroundColor Green
-}
diff --git a/servers/Azure.Mcp.Server/CHANGELOG.md b/servers/Azure.Mcp.Server/CHANGELOG.md
index 624663d07..745f1c4fe 100644
--- a/servers/Azure.Mcp.Server/CHANGELOG.md
+++ b/servers/Azure.Mcp.Server/CHANGELOG.md
@@ -16,8 +16,14 @@ The Azure MCP Server updates automatically by default whenever a new release com
### Other Changes
+- Refactored AKS service implementation to use Azure Resource Graph queries instead of direct ARM API calls. [[#424](https://github.com/microsoft/mcp/pull/424)]
- Update the Foundry tool to use GenericResource for deploying models to Azure AI Services. [[#456](https://github.com/microsoft/mcp/pull/456)]
+#### Dependency Updates
+
+- Removed the following dependencies:
+ - Azure.ResourceManager.ContainerService [[#424](https://github.com/microsoft/mcp/pull/424)]
+
## 0.7.0 (2025-09-16)
### Features Added
diff --git a/tools/Azure.Mcp.Tools.Aks/src/Azure.Mcp.Tools.Aks.csproj b/tools/Azure.Mcp.Tools.Aks/src/Azure.Mcp.Tools.Aks.csproj
index 516c62f99..374869120 100644
--- a/tools/Azure.Mcp.Tools.Aks/src/Azure.Mcp.Tools.Aks.csproj
+++ b/tools/Azure.Mcp.Tools.Aks/src/Azure.Mcp.Tools.Aks.csproj
@@ -12,7 +12,6 @@
-
diff --git a/tools/Azure.Mcp.Tools.Aks/src/Commands/AksJsonContext.cs b/tools/Azure.Mcp.Tools.Aks/src/Commands/AksJsonContext.cs
index 585497096..4d6a0625b 100644
--- a/tools/Azure.Mcp.Tools.Aks/src/Commands/AksJsonContext.cs
+++ b/tools/Azure.Mcp.Tools.Aks/src/Commands/AksJsonContext.cs
@@ -4,15 +4,21 @@
using System.Text.Json.Serialization;
using Azure.Mcp.Tools.Aks.Commands.Cluster;
using Azure.Mcp.Tools.Aks.Commands.Nodepool;
+using Azure.Mcp.Tools.Aks.Services.Models;
namespace Azure.Mcp.Tools.Aks.Commands;
[JsonSerializable(typeof(ClusterListCommand.ClusterListCommandResult))]
[JsonSerializable(typeof(ClusterGetCommand.ClusterGetCommandResult))]
[JsonSerializable(typeof(Models.Cluster))]
+[JsonSerializable(typeof(AksAgentPoolProfile))]
+[JsonSerializable(typeof(AksClusterData))]
+[JsonSerializable(typeof(AksClusterProperties))]
+[JsonSerializable(typeof(AksManagedClusterSku))]
+[JsonSerializable(typeof(AksNetworkProfile))]
+[JsonSerializable(typeof(AksPowerState))]
[JsonSerializable(typeof(NodepoolListCommand.NodepoolListCommandResult))]
[JsonSerializable(typeof(NodepoolGetCommand.NodepoolGetCommandResult))]
[JsonSerializable(typeof(Models.NodePool))]
-[JsonSerializable(typeof(Models.NodePoolPowerState))]
[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
internal sealed partial class AksJsonContext : JsonSerializerContext;
diff --git a/tools/Azure.Mcp.Tools.Aks/src/Commands/Cluster/ClusterGetCommand.cs b/tools/Azure.Mcp.Tools.Aks/src/Commands/Cluster/ClusterGetCommand.cs
index 7f4662edd..e1a612965 100644
--- a/tools/Azure.Mcp.Tools.Aks/src/Commands/Cluster/ClusterGetCommand.cs
+++ b/tools/Azure.Mcp.Tools.Aks/src/Commands/Cluster/ClusterGetCommand.cs
@@ -63,7 +63,7 @@ public override async Task ExecuteAsync(CommandContext context,
try
{
var aksService = context.GetService();
- var cluster = await aksService.GetCluster(
+ var cluster = await aksService.GetClusterAsync(
options.Subscription!,
options.ClusterName!,
options.ResourceGroup!,
@@ -88,6 +88,7 @@ public override async Task ExecuteAsync(CommandContext context,
protected override string GetErrorMessage(Exception ex) => ex switch
{
+ KeyNotFoundException => $"AKS cluster not found. Verify the cluster name, resource group, and that you have access.",
RequestFailedException reqEx when reqEx.Status == 404 =>
"AKS cluster not found. Verify the cluster name, resource group, and subscription, and ensure you have access.",
RequestFailedException reqEx when reqEx.Status == 403 =>
@@ -96,5 +97,12 @@ public override async Task ExecuteAsync(CommandContext context,
_ => base.GetErrorMessage(ex)
};
+ protected override int GetStatusCode(Exception ex) => ex switch
+ {
+ KeyNotFoundException => 404,
+ RequestFailedException reqEx => reqEx.Status,
+ _ => base.GetStatusCode(ex)
+ };
+
internal record ClusterGetCommandResult(Models.Cluster Cluster);
}
diff --git a/tools/Azure.Mcp.Tools.Aks/src/Commands/Cluster/ClusterListCommand.cs b/tools/Azure.Mcp.Tools.Aks/src/Commands/Cluster/ClusterListCommand.cs
index 1caa096af..218eca3bd 100644
--- a/tools/Azure.Mcp.Tools.Aks/src/Commands/Cluster/ClusterListCommand.cs
+++ b/tools/Azure.Mcp.Tools.Aks/src/Commands/Cluster/ClusterListCommand.cs
@@ -45,7 +45,7 @@ public override async Task ExecuteAsync(CommandContext context,
try
{
var aksService = context.GetService();
- var clusters = await aksService.ListClusters(
+ var clusters = await aksService.ListClustersAsync(
options.Subscription!,
options.Tenant,
options.RetryPolicy);
diff --git a/tools/Azure.Mcp.Tools.Aks/src/Commands/Nodepool/NodepoolGetCommand.cs b/tools/Azure.Mcp.Tools.Aks/src/Commands/Nodepool/NodepoolGetCommand.cs
index 454da6f1e..61908be9f 100644
--- a/tools/Azure.Mcp.Tools.Aks/src/Commands/Nodepool/NodepoolGetCommand.cs
+++ b/tools/Azure.Mcp.Tools.Aks/src/Commands/Nodepool/NodepoolGetCommand.cs
@@ -65,7 +65,7 @@ public override async Task ExecuteAsync(CommandContext context,
try
{
var aksService = context.GetService();
- var nodePool = await aksService.GetNodePool(
+ var nodePool = await aksService.GetNodePoolAsync(
options.Subscription!,
options.ResourceGroup!,
options.ClusterName!,
@@ -91,6 +91,7 @@ public override async Task ExecuteAsync(CommandContext context,
protected override string GetErrorMessage(Exception ex) => ex switch
{
+ KeyNotFoundException => $"AKS node pool not found. Verify the cluster name, resource group, and that you have access.",
RequestFailedException reqEx when reqEx.Status == 404 =>
"AKS node pool not found. Verify the node pool name, cluster, resource group, and subscription, and ensure you have access.",
RequestFailedException reqEx when reqEx.Status == 403 =>
@@ -99,6 +100,13 @@ public override async Task ExecuteAsync(CommandContext context,
_ => base.GetErrorMessage(ex)
};
+ protected override int GetStatusCode(Exception ex) => ex switch
+ {
+ KeyNotFoundException => 404,
+ RequestFailedException reqEx => reqEx.Status,
+ _ => base.GetStatusCode(ex)
+ };
+
internal record NodepoolGetCommandResult(Models.NodePool NodePool);
}
diff --git a/tools/Azure.Mcp.Tools.Aks/src/Commands/Nodepool/NodepoolListCommand.cs b/tools/Azure.Mcp.Tools.Aks/src/Commands/Nodepool/NodepoolListCommand.cs
index db1b3d6d0..4cfe1bf91 100644
--- a/tools/Azure.Mcp.Tools.Aks/src/Commands/Nodepool/NodepoolListCommand.cs
+++ b/tools/Azure.Mcp.Tools.Aks/src/Commands/Nodepool/NodepoolListCommand.cs
@@ -63,7 +63,7 @@ public override async Task ExecuteAsync(CommandContext context,
try
{
var aksService = context.GetService();
- var nodePools = await aksService.ListNodePools(
+ var nodePools = await aksService.ListNodePoolsAsync(
options.Subscription!,
options.ResourceGroup!,
options.ClusterName!,
diff --git a/tools/Azure.Mcp.Tools.Aks/src/Models/NodePool.cs b/tools/Azure.Mcp.Tools.Aks/src/Models/NodePool.cs
index 13bae4c8c..92540eb9f 100644
--- a/tools/Azure.Mcp.Tools.Aks/src/Models/NodePool.cs
+++ b/tools/Azure.Mcp.Tools.Aks/src/Models/NodePool.cs
@@ -45,7 +45,7 @@ public class NodePool
public string? ProvisioningState { get; set; }
/// Power state of the node pool.
- public NodePoolPowerState? PowerState { get; set; }
+ public string? PowerState { get; set; }
/// Target orchestrator (Kubernetes) version.
public string? OrchestratorVersion { get; set; }
@@ -81,9 +81,3 @@ public class NodePool
/// Node image version.
public string? NodeImageVersion { get; set; }
}
-
-public sealed class NodePoolPowerState
-{
- /// Power state code (e.g., Running, Stopped).
- public string? Code { get; set; }
-}
diff --git a/tools/Azure.Mcp.Tools.Aks/src/Services/AksService.cs b/tools/Azure.Mcp.Tools.Aks/src/Services/AksService.cs
index edebf2948..e9ffa8a67 100644
--- a/tools/Azure.Mcp.Tools.Aks/src/Services/AksService.cs
+++ b/tools/Azure.Mcp.Tools.Aks/src/Services/AksService.cs
@@ -1,30 +1,34 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
+using System.Text.Json;
+using Azure.Core;
using Azure.Mcp.Core.Options;
using Azure.Mcp.Core.Services.Azure;
using Azure.Mcp.Core.Services.Azure.Subscription;
using Azure.Mcp.Core.Services.Azure.Tenant;
using Azure.Mcp.Core.Services.Caching;
using Azure.Mcp.Tools.Aks.Models;
-using Azure.ResourceManager.ContainerService;
+using Microsoft.Extensions.Logging;
namespace Azure.Mcp.Tools.Aks.Services;
public sealed class AksService(
ISubscriptionService subscriptionService,
ITenantService tenantService,
- ICacheService cacheService) : BaseAzureService(tenantService), IAksService
+ ICacheService cacheService,
+ ILogger logger) : BaseAzureResourceService(subscriptionService, tenantService), IAksService
{
private readonly ISubscriptionService _subscriptionService = subscriptionService ?? throw new ArgumentNullException(nameof(subscriptionService));
private readonly ICacheService _cacheService = cacheService ?? throw new ArgumentNullException(nameof(cacheService));
+ private readonly ILogger _logger = logger;
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> ListClusters(
+ public async Task> ListClustersAsync(
string subscription,
string? tenant = null,
RetryPolicyOptions? retryPolicy = null)
@@ -43,31 +47,27 @@ public async Task> ListClusters(
return cachedClusters;
}
- var subscriptionResource = await _subscriptionService.GetSubscription(subscription, tenant, retryPolicy);
- var clusters = new List();
-
try
{
- await foreach (var cluster in subscriptionResource.GetContainerServiceManagedClustersAsync())
- {
- if (cluster?.Data != null)
- {
- clusters.Add(ConvertToClusterModel(cluster));
- }
- }
+ var clusters = await ExecuteResourceQueryAsync(
+ "Microsoft.ContainerService/managedClusters",
+ resourceGroup: null, // all resource groups
+ subscription,
+ retryPolicy,
+ ConvertToClusterModel,
+ cancellationToken: default);
// Cache the results
await _cacheService.SetAsync(CacheGroup, cacheKey, clusters, s_cacheDuration);
+ return clusters;
}
catch (Exception ex)
{
throw new Exception($"Error retrieving AKS clusters: {ex.Message}", ex);
}
-
- return clusters;
}
- public async Task GetCluster(
+ public async Task GetClusterAsync(
string subscription,
string clusterName,
string resourceGroup,
@@ -88,29 +88,21 @@ public async Task> ListClusters(
return cachedCluster;
}
- var subscriptionResource = await _subscriptionService.GetSubscription(subscription, tenant, retryPolicy);
-
try
{
- var resourceGroupResource = await subscriptionResource
- .GetResourceGroupAsync(resourceGroup);
-
- if (resourceGroupResource?.Value == null)
- {
- return null;
- }
-
- var clusterResource = await resourceGroupResource.Value
- .GetContainerServiceManagedClusters()
- .GetAsync(clusterName);
-
- if (clusterResource?.Value?.Data == null)
+ var cluster = await ExecuteSingleResourceQueryAsync(
+ "Microsoft.ContainerService/managedClusters",
+ resourceGroup,
+ subscription,
+ retryPolicy,
+ ConvertToClusterModel,
+ $"name =~ '{EscapeKqlString(clusterName)}'");
+
+ if (cluster == null)
{
- return null;
+ throw new KeyNotFoundException($"AKS cluster '{clusterName}' not found in resource group '{resourceGroup}' for subscription '{subscription}'.");
}
- var cluster = ConvertToClusterModel(clusterResource.Value);
-
// Cache the result
await _cacheService.SetAsync(CacheGroup, cacheKey, cluster, s_cacheDuration);
@@ -118,11 +110,14 @@ public async Task> ListClusters(
}
catch (Exception ex)
{
- throw new Exception($"Error retrieving AKS cluster '{clusterName}': {ex.Message}", ex);
+ _logger.LogError(ex,
+ "Error retrieving AKS cluster '{ClusterName}' in resource group '{ResourceGroup}' for subscription '{Subscription}'",
+ clusterName, resourceGroup, subscription);
+ throw;
}
}
- public async Task> ListNodePools(
+ public async Task> ListNodePoolsAsync(
string subscription,
string resourceGroup,
string clusterName,
@@ -143,48 +138,30 @@ public async Task> ListNodePools(
return cachedNodePools;
}
- var subscriptionResource = await _subscriptionService.GetSubscription(subscription, tenant, retryPolicy);
- var nodePools = new List();
-
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())
+ var nodePools = await ExecuteSingleResourceQueryAsync(
+ "Microsoft.ContainerService/managedClusters",
+ resourceGroup,
+ subscription,
+ retryPolicy,
+ ConvertToClusterNodePoolModel,
+ $"name =~ '{EscapeKqlString(clusterName)}'");
+ if (nodePools == null)
{
- if (agentPool?.Data != null)
- {
- nodePools.Add(ConvertToNodePoolModel(agentPool));
- }
+ throw new KeyNotFoundException($"No node pools found for cluster '{clusterName}' in resource group '{resourceGroup}' for subscription '{subscription}'");
}
-
// Cache the results
await _cacheService.SetAsync(CacheGroup, cacheKey, nodePools, s_cacheDuration);
+ return nodePools;
}
catch (Exception ex)
{
throw new Exception($"Error retrieving AKS node pools for cluster '{clusterName}': {ex.Message}", ex);
}
-
- return nodePools;
}
- public async Task GetNodePool(
+ public async Task GetNodePoolAsync(
string subscription,
string resourceGroup,
string clusterName,
@@ -206,107 +183,103 @@ public async Task> ListNodePools(
return cachedNodePool;
}
- var subscriptionResource = await _subscriptionService.GetSubscription(subscription, tenant, retryPolicy);
-
try
{
- var resourceGroupResource = await subscriptionResource.GetResourceGroupAsync(resourceGroup);
- if (resourceGroupResource?.Value == null)
- {
- return null;
- }
-
- var clusterResource = await resourceGroupResource.Value
- .GetContainerServiceManagedClusters()
- .GetAsync(clusterName);
-
- if (clusterResource?.Value == null)
- {
- return null;
- }
-
- var agentPoolResource = await clusterResource.Value
- .GetContainerServiceAgentPools()
- .GetAsync(nodePoolName);
-
- if (agentPoolResource?.Value?.Data == null)
+ var nodePools = await ExecuteSingleResourceQueryAsync(
+ "Microsoft.ContainerService/managedClusters",
+ resourceGroup,
+ subscription,
+ retryPolicy,
+ ConvertToClusterNodePoolModel,
+ $"name =~ '{EscapeKqlString(clusterName)}'") ?? new List();
+
+ var nodePool = nodePools.FirstOrDefault(np => np.Name == nodePoolName);
+ if (nodePool != null)
{
- return null;
+ // Cache the result
+ await _cacheService.SetAsync(CacheGroup, cacheKey, nodePool, s_cacheDuration);
+ return nodePool;
}
- var nodePool = ConvertToNodePoolModel(agentPoolResource.Value);
-
- // Cache the result
- await _cacheService.SetAsync(CacheGroup, cacheKey, nodePool, s_cacheDuration);
-
- return nodePool;
+ throw new KeyNotFoundException($"AKS node pool '{nodePoolName}' not found in cluster '{clusterName}' in resource group '{resourceGroup}' for subscription '{subscription}'.");
}
catch (Exception ex)
{
- throw new Exception($"Error retrieving AKS node pool '{nodePoolName}' for cluster '{clusterName}': {ex.Message}", ex);
+ _logger.LogError(ex,
+ "Error retrieving AKS node pool '{NodePoolName}' for cluster '{ClusterName}' in resource group '{ResourceGroup}' for subscription '{Subscription}'",
+ nodePoolName, clusterName, resourceGroup, subscription);
+ throw;
}
}
- private static Cluster ConvertToClusterModel(ContainerServiceManagedClusterResource clusterResource)
+ private static Cluster ConvertToClusterModel(JsonElement item)
{
- var data = clusterResource.Data;
- var agentPool = data.AgentPoolProfiles?.FirstOrDefault();
+ var data = Models.AksClusterData.FromJson(item) ?? throw new InvalidOperationException("Failed to parse AKS cluster data");
+ // Resource identity
+ if (string.IsNullOrEmpty(data.ResourceId))
+ throw new InvalidOperationException("Resource ID is missing");
+ var id = new ResourceIdentifier(data.ResourceId);
+
+ var agentPool = data.Properties?.AgentPoolProfiles?.FirstOrDefault();
return new Cluster
{
- Name = data.Name,
- SubscriptionId = clusterResource.Id.SubscriptionId,
- ResourceGroupName = clusterResource.Id.ResourceGroupName,
- Location = data.Location.ToString(),
- KubernetesVersion = data.KubernetesVersion,
- ProvisioningState = data.ProvisioningState?.ToString(),
- PowerState = data.PowerStateCode?.ToString(),
- DnsPrefix = data.DnsPrefix,
- Fqdn = data.Fqdn,
+ Name = data.ResourceName ?? "Unknown",
+ SubscriptionId = id.SubscriptionId ?? "Unknown",
+ ResourceGroupName = id.ResourceGroupName ?? "Unknown",
+ Location = data.Location ?? "Unknown",
+ KubernetesVersion = data.Properties?.KubernetesVersion,
+ ProvisioningState = data.Properties?.ProvisioningState,
+ PowerState = data.Properties?.PowerState?.Code,
+ DnsPrefix = data.Properties?.DnsPrefix,
+ Fqdn = data.Properties?.Fqdn,
NodeCount = agentPool?.Count,
NodeVmSize = agentPool?.VmSize,
- IdentityType = data.Identity?.ManagedServiceIdentityType.ToString(),
- EnableRbac = data.EnableRbac,
- NetworkPlugin = data.NetworkProfile?.NetworkPlugin?.ToString(),
- NetworkPolicy = data.NetworkProfile?.NetworkPolicy?.ToString(),
- ServiceCidr = data.NetworkProfile?.ServiceCidr,
- DnsServiceIP = data.NetworkProfile?.DnsServiceIP?.ToString(),
- SkuTier = data.Sku?.Tier?.ToString(),
- Tags = data.Tags?.ToDictionary(kvp => kvp.Key, kvp => kvp.Value)
+ IdentityType = data.IdentityType,
+ EnableRbac = data.Properties?.EnableRbac,
+ NetworkPlugin = data.Properties?.NetworkProfile?.NetworkPlugin,
+ NetworkPolicy = data.Properties?.NetworkProfile?.NetworkPolicy,
+ ServiceCidr = data.Properties?.NetworkProfile?.ServiceCidr,
+ DnsServiceIP = data.Properties?.NetworkProfile?.DnsServiceIP,
+ SkuTier = data.Sku?.Tier,
+ Tags = data.Tags != null ? new Dictionary(data.Tags) : null
};
}
- private static NodePool ConvertToNodePoolModel(ContainerServiceAgentPoolResource agentPoolResource)
+ private static List ConvertToClusterNodePoolModel(JsonElement item)
{
- var data = agentPoolResource.Data;
+ var data = Models.AksClusterData.FromJson(item) ?? throw new InvalidOperationException("Failed to parse AKS cluster data");
- return new NodePool
- {
- Name = data.Name,
- Count = data.Count,
- VmSize = data.VmSize?.ToString(),
- OsDiskSizeGB = data.OSDiskSizeInGB,
- OsDiskType = data.OSDiskType?.ToString(),
- KubeletDiskType = data.KubeletDiskType?.ToString(),
- MaxPods = data.MaxPods,
- Type = data.TypePropertiesType?.ToString(),
- MaxCount = data.MaxCount,
- MinCount = data.MinCount,
- EnableAutoScaling = data.EnableAutoScaling,
- ScaleDownMode = data.ScaleDownMode?.ToString(),
- ProvisioningState = data.ProvisioningState?.ToString(),
- PowerState = data.PowerStateCode.HasValue ? new NodePoolPowerState { Code = data.PowerStateCode.Value.ToString() } : null,
- Mode = data.Mode?.ToString(),
- OrchestratorVersion = data.OrchestratorVersion,
- CurrentOrchestratorVersion = data.CurrentOrchestratorVersion,
- EnableNodePublicIP = data.EnableNodePublicIP,
- ScaleSetPriority = data.ScaleSetPriority?.ToString(),
- ScaleSetEvictionPolicy = data.ScaleSetEvictionPolicy?.ToString(),
- NodeLabels = data.NodeLabels?.ToDictionary(kvp => kvp.Key, kvp => kvp.Value),
- NodeTaints = data.NodeTaints?.ToList(),
- OsType = data.OSType?.ToString(),
- OsSKU = data.OSSku?.ToString(),
- NodeImageVersion = data.NodeImageVersion
- };
+ return data.Properties?.AgentPoolProfiles?
+ .Select(node => new NodePool
+ {
+ Name = node.Name ?? "Unknown",
+ Count = node.Count,
+ VmSize = node.VmSize,
+ OsDiskSizeGB = node.OSDiskSizeInGB,
+ OsDiskType = node.OSDiskType,
+ KubeletDiskType = node.KubeletDiskType,
+ MaxPods = node.MaxPods,
+ Type = node.AgentPoolType,
+ MinCount = node.MinCount,
+ MaxCount = node.MaxCount,
+ EnableAutoScaling = node.EnableAutoScaling,
+ ScaleDownMode = node.ScaleDownMode,
+ ProvisioningState = node.ProvisioningState,
+ PowerState = node.PowerState?.Code,
+ Mode = node.Mode,
+ OrchestratorVersion = node.OrchestratorVersion,
+ CurrentOrchestratorVersion = node.CurrentOrchestratorVersion,
+ EnableNodePublicIP = node.EnableNodePublicIP,
+ ScaleSetPriority = node.ScaleSetPriority,
+ ScaleSetEvictionPolicy = node.ScaleSetEvictionPolicy,
+ NodeLabels = node.NodeLabels?.ToDictionary(kvp => kvp.Key, kvp => kvp.Value),
+ NodeTaints = node.NodeTaints?.ToList(),
+ OsType = node.OSType,
+ OsSKU = node.OSSku,
+ NodeImageVersion = node.NodeImageVersion
+ })
+ .ToList()
+ ?? new List();
}
}
diff --git a/tools/Azure.Mcp.Tools.Aks/src/Services/IAksService.cs b/tools/Azure.Mcp.Tools.Aks/src/Services/IAksService.cs
index c1c5729a3..56bcb1bae 100644
--- a/tools/Azure.Mcp.Tools.Aks/src/Services/IAksService.cs
+++ b/tools/Azure.Mcp.Tools.Aks/src/Services/IAksService.cs
@@ -8,26 +8,26 @@ namespace Azure.Mcp.Tools.Aks.Services;
public interface IAksService
{
- Task> ListClusters(
+ Task> ListClustersAsync(
string subscription,
string? tenant = null,
RetryPolicyOptions? retryPolicy = null);
- Task GetCluster(
+ Task GetClusterAsync(
string subscription,
string clusterName,
string resourceGroup,
string? tenant = null,
RetryPolicyOptions? retryPolicy = null);
- Task> ListNodePools(
+ Task> ListNodePoolsAsync(
string subscription,
string resourceGroup,
string clusterName,
string? tenant = null,
RetryPolicyOptions? retryPolicy = null);
- Task GetNodePool(
+ Task GetNodePoolAsync(
string subscription,
string resourceGroup,
string clusterName,
diff --git a/tools/Azure.Mcp.Tools.Aks/src/Services/Models/AksAgentPoolProfile.cs b/tools/Azure.Mcp.Tools.Aks/src/Services/Models/AksAgentPoolProfile.cs
new file mode 100644
index 000000000..6447d8156
--- /dev/null
+++ b/tools/Azure.Mcp.Tools.Aks/src/Services/Models/AksAgentPoolProfile.cs
@@ -0,0 +1,65 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Text.Json.Serialization;
+
+namespace Azure.Mcp.Tools.Aks.Services.Models;
+
+internal sealed class AksAgentPoolProfile
+{
+ /// Windows agent pool names must be 6 characters or less.
+ public string? Name { get; set; }
+ /// Number of agents (VMs) to host docker containers. Allowed values must be in the range of 0 to 1000 (inclusive) for user pools and in the range of 1 to 1000 (inclusive) for system pools. The default value is 1.
+ public int? Count { get; set; }
+ /// VM size availability varies by region. If a node contains insufficient compute resources (memory, cpu, etc) pods might fail to run correctly. For more details on restricted VM sizes, see: https://docs.microsoft.com/azure/aks/quotas-skus-regions.
+ public string? VmSize { get; set; }
+ /// OS Disk Size in GB to be used to specify the disk size for every machine in the master/agent pool. If you specify 0, it will apply the default osDisk size according to the vmSize specified.
+ [JsonPropertyName("osDiskSizeGB")]
+ public int? OSDiskSizeInGB { get; set; }
+ /// The default is 'Ephemeral' if the VM supports it and has a cache disk larger than the requested OSDiskSizeGB. Otherwise, defaults to 'Managed'. May not be changed after creation. For more information see [Ephemeral OS](https://docs.microsoft.com/azure/aks/cluster-configuration#ephemeral-os).
+ [JsonPropertyName("osDiskType")]
+ public string? OSDiskType { get; set; }
+ /// Determines the placement of emptyDir volumes, container runtime data root, and Kubelet ephemeral storage.
+ public string? KubeletDiskType { get; set; }
+ /// The maximum number of pods that can run on a node.
+ public int? MaxPods { get; set; }
+ /// The type of Agent Pool.
+ [JsonPropertyName("type")]
+ public string? AgentPoolType { get; set; }
+ /// The maximum number of nodes for auto-scaling.
+ public int? MaxCount { get; set; }
+ /// The minimum number of nodes for auto-scaling.
+ public int? MinCount { get; set; }
+ /// Whether to enable auto-scaler.
+ public bool? EnableAutoScaling { get; set; }
+ /// This also effects the cluster autoscaler behavior. If not specified, it defaults to Delete.
+ public string? ScaleDownMode { get; set; }
+ /// The current deployment or provisioning state.
+ public string? ProvisioningState { get; set; }
+ /// Tells whether the cluster is Running or Stopped.
+ public AksPowerState? PowerState { get; set; }
+ /// Both patch version <major.minor.patch> (e.g. 1.20.13) and <major.minor> (e.g. 1.20) are supported. When <major.minor> is specified, the latest supported GA patch version is chosen automatically. Updating the cluster with the same <major.minor> once it has been created (e.g. 1.14.x -> 1.14) will not trigger an upgrade, even if a newer patch version is available. As a best practice, you should upgrade all node pools in an AKS cluster to the same Kubernetes version. The node pool version must have the same major version as the control plane. The node pool minor version must be within two minor versions of the control plane version. The node pool version cannot be greater than the control plane version. For more information see [upgrading a node pool](https://docs.microsoft.com/azure/aks/use-multiple-node-pools#upgrade-a-node-pool).
+ public string? OrchestratorVersion { get; set; }
+ /// If orchestratorVersion is a fully specified version <major.minor.patch>, this field will be exactly equal to it. If orchestratorVersion is <major.minor>, this field will contain the full <major.minor.patch> version being used.
+ public string? CurrentOrchestratorVersion { get; set; }
+ /// Some scenarios may require nodes in a node pool to receive their own dedicated public IP addresses. A common scenario is for gaming workloads, where a console needs to make a direct connection to a cloud virtual machine to minimize hops. For more information see [assigning a public IP per node](https://docs.microsoft.com/azure/aks/use-multiple-node-pools#assign-a-public-ip-per-node-for-your-node-pools). The default is false.
+ public bool? EnableNodePublicIP { get; set; }
+ /// The Virtual Machine Scale Set priority. If not specified, the default is 'Regular'.
+ public string? ScaleSetPriority { get; set; }
+ /// This cannot be specified unless the scaleSetPriority is 'Spot'. If not specified, the default is 'Delete'.
+ public string? ScaleSetEvictionPolicy { get; set; }
+ /// The node labels to be persisted across all nodes in agent pool.
+ public IDictionary? NodeLabels { get; }
+ /// The taints added to new nodes during node pool create and scale. For example, key=value:NoSchedule.
+ public IList? NodeTaints { get; }
+ /// A cluster must have at least one 'System' Agent Pool at all times. For additional information on agent pool restrictions and best practices, see: https://docs.microsoft.com/azure/aks/use-system-pools.
+ public string? Mode { get; set; }
+ /// The operating system type. The default is Linux.
+ [JsonPropertyName("osType")]
+ public string? OSType { get; set; }
+ /// Specifies the OS SKU used by the agent pool. The default is Ubuntu if OSType is Linux. The default is Windows2019 when Kubernetes <= 1.24 or Windows2022 when Kubernetes >= 1.25 if OSType is Windows.
+ [JsonPropertyName("osSKU")]
+ public string? OSSku { get; set; }
+ /// The version of node image.
+ public string? NodeImageVersion { get; set; }
+}
diff --git a/tools/Azure.Mcp.Tools.Aks/src/Services/Models/AksClusterData.cs b/tools/Azure.Mcp.Tools.Aks/src/Services/Models/AksClusterData.cs
new file mode 100644
index 000000000..8a384656e
--- /dev/null
+++ b/tools/Azure.Mcp.Tools.Aks/src/Services/Models/AksClusterData.cs
@@ -0,0 +1,40 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Azure.Mcp.Tools.Aks.Commands;
+
+namespace Azure.Mcp.Tools.Aks.Services.Models;
+
+///
+/// A class representing the AKS Cluster data model for Resource Graph results.
+///
+internal sealed class AksClusterData
+{
+ /// The resource ID for the resource.
+ [JsonPropertyName("id")]
+ public string? ResourceId { get; set; }
+ /// The type of the resource.
+ [JsonPropertyName("type")]
+ public string? ResourceType { get; set; }
+ /// The name of the resource.
+ [JsonPropertyName("name")]
+ public string? ResourceName { get; set; }
+ /// The location of the resource.
+ public string? Location { get; set; }
+ /// The SKU of the resource.
+ public AksManagedClusterSku? Sku { get; set; }
+ /// The identity type of the resource.
+ public string? IdentityType { get; set; }
+ /// The tags of the resource.
+ public IDictionary? Tags { get; set; }
+ /// The properties of the cluster.
+ public AksClusterProperties? Properties { get; set; }
+
+ // Read the JSON response content and create a model instance from it.
+ public static AksClusterData? FromJson(JsonElement source)
+ {
+ return JsonSerializer.Deserialize(source, AksJsonContext.Default.AksClusterData);
+ }
+}
diff --git a/tools/Azure.Mcp.Tools.Aks/src/Services/Models/AksClusterProperties.cs b/tools/Azure.Mcp.Tools.Aks/src/Services/Models/AksClusterProperties.cs
new file mode 100644
index 000000000..d52705c1d
--- /dev/null
+++ b/tools/Azure.Mcp.Tools.Aks/src/Services/Models/AksClusterProperties.cs
@@ -0,0 +1,30 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Text.Json.Serialization;
+
+namespace Azure.Mcp.Tools.Aks.Services.Models;
+
+///
+/// A class representing the AksClusterProperties data model.
+///
+internal sealed class AksClusterProperties
+{
+ /// The current provisioning state.
+ public string? ProvisioningState { get; set; }
+ /// The Power State of the cluster.
+ public AksPowerState? PowerState { get; set; }
+ /// Both patch version <major.minor.patch> (e.g. 1.20.13) and <major.minor> (e.g. 1.20) are supported. When <major.minor> is specified, the latest supported GA patch version is chosen automatically. Updating the cluster with the same <major.minor> once it has been created (e.g. 1.14.x -> 1.14) will not trigger an upgrade, even if a newer patch version is available. When you upgrade a supported AKS cluster, Kubernetes minor versions cannot be skipped. All upgrades must be performed sequentially by major version number. For example, upgrades between 1.14.x -> 1.15.x or 1.15.x -> 1.16.x are allowed, however 1.14.x -> 1.16.x is not allowed. See [upgrading an AKS cluster](https://docs.microsoft.com/azure/aks/upgrade-cluster) for more details.
+ public string? KubernetesVersion { get; set; }
+ /// This cannot be updated once the Managed Cluster has been created.
+ public string? DnsPrefix { get; set; }
+ /// The FQDN of the master pool.
+ public string? Fqdn { get; set; }
+ /// The agent pool properties.
+ public IList? AgentPoolProfiles { get; set; }
+ /// Whether to enable Kubernetes Role-Based Access Control.
+ [JsonPropertyName("enableRBAC")]
+ public bool? EnableRbac { get; set; }
+ /// The network configuration profile.
+ public AksNetworkProfile? NetworkProfile { get; set; }
+}
diff --git a/tools/Azure.Mcp.Tools.Aks/src/Services/Models/AksManagedClusterSku.cs b/tools/Azure.Mcp.Tools.Aks/src/Services/Models/AksManagedClusterSku.cs
new file mode 100644
index 000000000..b15d2de2c
--- /dev/null
+++ b/tools/Azure.Mcp.Tools.Aks/src/Services/Models/AksManagedClusterSku.cs
@@ -0,0 +1,13 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+namespace Azure.Mcp.Tools.Aks.Services.Models;
+
+/// The SKU of a Managed Cluster.
+internal sealed class AksManagedClusterSku
+{
+ /// The name of a managed cluster SKU.
+ public string? Name { get; set; }
+ /// If not specified, the default is 'Free'. See [AKS Pricing Tier](https://learn.microsoft.com/azure/aks/free-standard-pricing-tiers) for more details.
+ public string? Tier { get; set; }
+}
diff --git a/tools/Azure.Mcp.Tools.Aks/src/Services/Models/AksNetworkProfile.cs b/tools/Azure.Mcp.Tools.Aks/src/Services/Models/AksNetworkProfile.cs
new file mode 100644
index 000000000..fb86d9cf6
--- /dev/null
+++ b/tools/Azure.Mcp.Tools.Aks/src/Services/Models/AksNetworkProfile.cs
@@ -0,0 +1,16 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+namespace Azure.Mcp.Tools.Aks.Services.Models;
+
+internal sealed class AksNetworkProfile
+{
+ /// Network plugin used for building the Kubernetes network.
+ public string? NetworkPlugin { get; set; }
+ /// Network policy used for building the Kubernetes network.
+ public string? NetworkPolicy { get; set; }
+ /// A CIDR notation IP range from which to assign service cluster IPs. It must not overlap with any Subnet IP ranges.
+ public string? ServiceCidr { get; set; }
+ /// An IP address assigned to the Kubernetes DNS service. It must be within the Kubernetes service address range specified in serviceCidr.
+ public string? DnsServiceIP { get; set; }
+}
diff --git a/tools/Azure.Mcp.Tools.Aks/src/Services/Models/AksPowerState.cs b/tools/Azure.Mcp.Tools.Aks/src/Services/Models/AksPowerState.cs
new file mode 100644
index 000000000..49c85a8cc
--- /dev/null
+++ b/tools/Azure.Mcp.Tools.Aks/src/Services/Models/AksPowerState.cs
@@ -0,0 +1,10 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+namespace Azure.Mcp.Tools.Aks.Services.Models;
+
+internal sealed class AksPowerState
+{
+ /// Tells whether the cluster is Running or Stopped.
+ public string? Code { get; set; }
+}
diff --git a/tools/Azure.Mcp.Tools.Aks/tests/Azure.Mcp.Tools.Aks.LiveTests/AksCommandTests.cs b/tools/Azure.Mcp.Tools.Aks/tests/Azure.Mcp.Tools.Aks.LiveTests/AksCommandTests.cs
index 29a71fc3a..1f14ca6c7 100644
--- a/tools/Azure.Mcp.Tools.Aks/tests/Azure.Mcp.Tools.Aks.LiveTests/AksCommandTests.cs
+++ b/tools/Azure.Mcp.Tools.Aks/tests/Azure.Mcp.Tools.Aks.LiveTests/AksCommandTests.cs
@@ -159,7 +159,7 @@ public async Task Should_handle_nonexistent_cluster_gracefully()
var errorDetails = result.Value;
Assert.True(errorDetails.TryGetProperty("message", out _));
Assert.True(errorDetails.TryGetProperty("type", out var typeProperty));
- Assert.Equal("Exception", typeProperty.GetString());
+ Assert.Equal("KeyNotFoundException", typeProperty.GetString());
}
[Fact]
diff --git a/tools/Azure.Mcp.Tools.Aks/tests/Azure.Mcp.Tools.Aks.LiveTests/NodepoolGetCommandTests.cs b/tools/Azure.Mcp.Tools.Aks/tests/Azure.Mcp.Tools.Aks.LiveTests/NodepoolGetCommandTests.cs
index d45fd74c1..83a416cf4 100644
--- a/tools/Azure.Mcp.Tools.Aks/tests/Azure.Mcp.Tools.Aks.LiveTests/NodepoolGetCommandTests.cs
+++ b/tools/Azure.Mcp.Tools.Aks/tests/Azure.Mcp.Tools.Aks.LiveTests/NodepoolGetCommandTests.cs
@@ -96,7 +96,7 @@ public async Task Should_handle_nonexistent_nodepool_gracefully()
var errorDetails = result.Value;
Assert.True(errorDetails.TryGetProperty("message", out _));
Assert.True(errorDetails.TryGetProperty("type", out var typeProperty));
- Assert.Equal("Exception", typeProperty.GetString());
+ Assert.Equal("KeyNotFoundException", typeProperty.GetString());
}
[Fact]
diff --git a/tools/Azure.Mcp.Tools.Aks/tests/Azure.Mcp.Tools.Aks.UnitTests/Cluster/ClusterGetCommandTests.cs b/tools/Azure.Mcp.Tools.Aks/tests/Azure.Mcp.Tools.Aks.UnitTests/Cluster/ClusterGetCommandTests.cs
index b4809aa03..117088b55 100644
--- a/tools/Azure.Mcp.Tools.Aks/tests/Azure.Mcp.Tools.Aks.UnitTests/Cluster/ClusterGetCommandTests.cs
+++ b/tools/Azure.Mcp.Tools.Aks/tests/Azure.Mcp.Tools.Aks.UnitTests/Cluster/ClusterGetCommandTests.cs
@@ -61,7 +61,7 @@ public async Task ExecuteAsync_ValidatesInputCorrectly(string args, bool shouldS
Location = "East US"
};
- _aksService.GetCluster(
+ _aksService.GetClusterAsync(
Arg.Any(),
Arg.Any(),
Arg.Any(),
@@ -102,7 +102,7 @@ public async Task ExecuteAsync_ReturnsClusterWhenFound()
ProvisioningState = "Succeeded"
};
- _aksService.GetCluster("test-subscription", "test-cluster", "test-rg", null, Arg.Any())
+ _aksService.GetClusterAsync("test-subscription", "test-cluster", "test-rg", null, Arg.Any())
.Returns(expectedCluster);
var parseResult = _commandDefinition.Parse(["--subscription", "test-subscription", "--resource-group", "test-rg", "--cluster", "test-cluster"]);
@@ -120,7 +120,7 @@ public async Task ExecuteAsync_ReturnsClusterWhenFound()
public async Task ExecuteAsync_ReturnsNullWhenClusterNotFound()
{
// Arrange
- _aksService.GetCluster("test-subscription", "nonexistent-cluster", "test-rg", null, Arg.Any())
+ _aksService.GetClusterAsync("test-subscription", "nonexistent-cluster", "test-rg", null, Arg.Any())
.Returns((Models.Cluster?)null);
var parseResult = _commandDefinition.Parse(["--subscription", "test-subscription", "--resource-group", "test-rg", "--cluster", "nonexistent-cluster"]);
@@ -138,7 +138,7 @@ public async Task ExecuteAsync_ReturnsNullWhenClusterNotFound()
public async Task ExecuteAsync_HandlesServiceErrors()
{
// Arrange
- _aksService.GetCluster(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())
+ _aksService.GetClusterAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())
.Returns(Task.FromException(new Exception("Test error")));
var parseResult = _commandDefinition.Parse(["--subscription", "test-subscription", "--resource-group", "test-rg", "--cluster", "test-cluster"]);
@@ -157,7 +157,7 @@ public async Task ExecuteAsync_Handles404NotFound()
{
// Arrange
var notFoundException = new RequestFailedException(404, "Not Found");
- _aksService.GetCluster(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())
+ _aksService.GetClusterAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())
.Returns(Task.FromException(notFoundException));
var parseResult = _commandDefinition.Parse(["--subscription", "test-subscription", "--resource-group", "test-rg", "--cluster", "test-cluster"]);
@@ -175,7 +175,7 @@ public async Task ExecuteAsync_Handles403Forbidden()
{
// Arrange
var forbiddenException = new RequestFailedException(403, "Forbidden");
- _aksService.GetCluster(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())
+ _aksService.GetClusterAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())
.Returns(Task.FromException(forbiddenException));
var parseResult = _commandDefinition.Parse(["--subscription", "test-subscription", "--resource-group", "test-rg", "--cluster", "test-cluster"]);
diff --git a/tools/Azure.Mcp.Tools.Aks/tests/Azure.Mcp.Tools.Aks.UnitTests/Cluster/ClusterListCommandTests.cs b/tools/Azure.Mcp.Tools.Aks/tests/Azure.Mcp.Tools.Aks.UnitTests/Cluster/ClusterListCommandTests.cs
index f28a106e6..ffaa2a452 100644
--- a/tools/Azure.Mcp.Tools.Aks/tests/Azure.Mcp.Tools.Aks.UnitTests/Cluster/ClusterListCommandTests.cs
+++ b/tools/Azure.Mcp.Tools.Aks/tests/Azure.Mcp.Tools.Aks.UnitTests/Cluster/ClusterListCommandTests.cs
@@ -56,7 +56,7 @@ public async Task ExecuteAsync_ValidatesInputCorrectly(string args, bool shouldS
new() { Name = "cluster1", Location = "eastus" },
new() { Name = "cluster2", Location = "westus" }
};
- _aksService.ListClusters(Arg.Any(), Arg.Any(), Arg.Any())
+ _aksService.ListClustersAsync(Arg.Any(), Arg.Any(), Arg.Any())
.Returns(testClusters);
}
@@ -89,7 +89,7 @@ public async Task ExecuteAsync_ReturnsClustersList()
new() { Name = "cluster2", Location = "westus", KubernetesVersion = "1.29.0" },
new() { Name = "cluster3", Location = "centralus", KubernetesVersion = "1.28.5" }
};
- _aksService.ListClusters(Arg.Any(), Arg.Any(), Arg.Any())
+ _aksService.ListClustersAsync(Arg.Any(), Arg.Any(), Arg.Any())
.Returns(expectedClusters);
var context = new CommandContext(_serviceProvider);
@@ -101,7 +101,7 @@ public async Task ExecuteAsync_ReturnsClustersList()
Assert.NotNull(response.Results);
// Verify the mock was called
- await _aksService.Received(1).ListClusters(Arg.Any(), Arg.Any(), Arg.Any());
+ await _aksService.Received(1).ListClustersAsync(Arg.Any(), Arg.Any(), Arg.Any());
var json = JsonSerializer.Serialize(response.Results);
// Debug: Output the actual JSON to understand the structure
@@ -120,7 +120,7 @@ public async Task ExecuteAsync_ReturnsClustersList()
public async Task ExecuteAsync_ReturnsEmptyWhenNoClusters()
{
// Arrange
- _aksService.ListClusters(Arg.Any(), Arg.Any(), Arg.Any())
+ _aksService.ListClustersAsync(Arg.Any(), Arg.Any(), Arg.Any())
.Returns([]);
var context = new CommandContext(_serviceProvider);
@@ -144,7 +144,7 @@ public async Task ExecuteAsync_ReturnsEmptyWhenNoClusters()
public async Task ExecuteAsync_HandlesServiceErrors()
{
// Arrange
- _aksService.ListClusters(Arg.Any(), Arg.Any(), Arg.Any())
+ _aksService.ListClustersAsync(Arg.Any(), Arg.Any(), Arg.Any())
.Returns(Task.FromException>(new Exception("Test error")));
var context = new CommandContext(_serviceProvider);
diff --git a/tools/Azure.Mcp.Tools.Aks/tests/Azure.Mcp.Tools.Aks.UnitTests/Nodepool/NodepoolGetCommandTests.cs b/tools/Azure.Mcp.Tools.Aks/tests/Azure.Mcp.Tools.Aks.UnitTests/Nodepool/NodepoolGetCommandTests.cs
index 4164bd383..117531d21 100644
--- a/tools/Azure.Mcp.Tools.Aks/tests/Azure.Mcp.Tools.Aks.UnitTests/Nodepool/NodepoolGetCommandTests.cs
+++ b/tools/Azure.Mcp.Tools.Aks/tests/Azure.Mcp.Tools.Aks.UnitTests/Nodepool/NodepoolGetCommandTests.cs
@@ -62,7 +62,7 @@ public async Task ExecuteAsync_ValidatesInputCorrectly(string args, bool shouldS
VmSize = "Standard_DS2_v2",
Mode = "System"
};
- _aksService.GetNodePool(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())
+ _aksService.GetNodePoolAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())
.Returns(testNodePool);
}
@@ -103,7 +103,7 @@ public async Task ExecuteAsync_ReturnsNodePool()
EnableAutoScaling = true,
ScaleDownMode = "Delete",
ProvisioningState = "Succeeded",
- PowerState = new Models.NodePoolPowerState { Code = "Running" },
+ PowerState = "Running",
OrchestratorVersion = "1.33.2",
CurrentOrchestratorVersion = "1.33.2",
EnableNodePublicIP = false,
@@ -116,7 +116,7 @@ public async Task ExecuteAsync_ReturnsNodePool()
OsSKU = "Ubuntu",
NodeImageVersion = "AKSUbuntu-2204gen2containerd-202508.20.1"
};
- _aksService.GetNodePool(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())
+ _aksService.GetNodePoolAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())
.Returns(expectedNodePool);
var context = new CommandContext(_serviceProvider);
@@ -129,7 +129,7 @@ public async Task ExecuteAsync_ReturnsNodePool()
Assert.Equal(200, response.Status);
Assert.NotNull(response.Results);
- await _aksService.Received(1).GetNodePool(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any());
+ await _aksService.Received(1).GetNodePoolAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any());
var json = JsonSerializer.Serialize(response.Results);
var result = JsonSerializer.Deserialize(json, AksJsonContext.Default.NodepoolGetCommandResult);
@@ -148,7 +148,7 @@ public async Task ExecuteAsync_ReturnsNodePool()
Assert.Equal(expectedNodePool.EnableAutoScaling, result.NodePool.EnableAutoScaling);
Assert.Equal(expectedNodePool.ScaleDownMode, result.NodePool.ScaleDownMode);
Assert.Equal(expectedNodePool.ProvisioningState, result.NodePool.ProvisioningState);
- Assert.Equal(expectedNodePool.PowerState?.Code, result.NodePool.PowerState?.Code);
+ Assert.Equal(expectedNodePool.PowerState, result.NodePool.PowerState);
Assert.Equal(expectedNodePool.OrchestratorVersion, result.NodePool.OrchestratorVersion);
Assert.Equal(expectedNodePool.CurrentOrchestratorVersion, result.NodePool.CurrentOrchestratorVersion);
Assert.Equal(expectedNodePool.EnableNodePublicIP, result.NodePool.EnableNodePublicIP);
@@ -166,7 +166,7 @@ public async Task ExecuteAsync_ReturnsNodePool()
public async Task ExecuteAsync_ReturnsNullWhenNodePoolNotFound()
{
// Arrange
- _aksService.GetNodePool(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())
+ _aksService.GetNodePoolAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())
.Returns((Models.NodePool?)null);
var context = new CommandContext(_serviceProvider);
@@ -184,7 +184,7 @@ public async Task ExecuteAsync_ReturnsNullWhenNodePoolNotFound()
public async Task ExecuteAsync_HandlesServiceErrors()
{
// Arrange
- _aksService.GetNodePool(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())
+ _aksService.GetNodePoolAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())
.Returns(Task.FromException(new Exception("Test error")));
var context = new CommandContext(_serviceProvider);
diff --git a/tools/Azure.Mcp.Tools.Aks/tests/Azure.Mcp.Tools.Aks.UnitTests/Nodepool/NodepoolListCommandTests.cs b/tools/Azure.Mcp.Tools.Aks/tests/Azure.Mcp.Tools.Aks.UnitTests/Nodepool/NodepoolListCommandTests.cs
index 79e6a6bef..7f23c7db4 100644
--- a/tools/Azure.Mcp.Tools.Aks/tests/Azure.Mcp.Tools.Aks.UnitTests/Nodepool/NodepoolListCommandTests.cs
+++ b/tools/Azure.Mcp.Tools.Aks/tests/Azure.Mcp.Tools.Aks.UnitTests/Nodepool/NodepoolListCommandTests.cs
@@ -59,7 +59,7 @@ public async Task ExecuteAsync_ValidatesInputCorrectly(string args, bool shouldS
new() { Name = "np1", Count = 3, VmSize = "Standard_DS2_v2" },
new() { Name = "np2", Count = 5, VmSize = "Standard_D4s_v5" }
};
- _aksService.ListNodePools(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())
+ _aksService.ListNodePoolsAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())
.Returns(testNodePools);
}
@@ -103,7 +103,7 @@ public async Task ExecuteAsync_ReturnsNodePoolsList()
EnableAutoScaling = true,
ScaleDownMode = "Delete",
ProvisioningState = "Succeeded",
- PowerState = new Models.NodePoolPowerState { Code = "Running" },
+ PowerState = "Running",
OrchestratorVersion = "1.33.1",
CurrentOrchestratorVersion = "1.33.1",
EnableNodePublicIP = false,
@@ -131,7 +131,7 @@ public async Task ExecuteAsync_ReturnsNodePoolsList()
EnableAutoScaling = true,
ScaleDownMode = "Delete",
ProvisioningState = "Succeeded",
- PowerState = new Models.NodePoolPowerState { Code = "Running" },
+ PowerState = "Running",
OrchestratorVersion = "1.33.2",
CurrentOrchestratorVersion = "1.33.2",
EnableNodePublicIP = false,
@@ -145,7 +145,7 @@ public async Task ExecuteAsync_ReturnsNodePoolsList()
NodeImageVersion = "AKSUbuntu-2204gen2containerd-202508.20.1"
}
};
- _aksService.ListNodePools(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())
+ _aksService.ListNodePoolsAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())
.Returns(expectedNodePools);
var context = new CommandContext(_serviceProvider);
@@ -159,7 +159,7 @@ public async Task ExecuteAsync_ReturnsNodePoolsList()
Assert.NotNull(response.Results);
// Verify the mock was called
- await _aksService.Received(1).ListNodePools(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any());
+ await _aksService.Received(1).ListNodePoolsAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any());
var json = JsonSerializer.Serialize(response.Results);
var result = JsonSerializer.Deserialize(json, AksJsonContext.Default.NodepoolListCommandResult);
@@ -181,7 +181,7 @@ public async Task ExecuteAsync_ReturnsNodePoolsList()
Assert.Equal(expectedNodePools[0].EnableAutoScaling, result.NodePools[0].EnableAutoScaling);
Assert.Equal(expectedNodePools[0].ScaleDownMode, result.NodePools[0].ScaleDownMode);
Assert.Equal(expectedNodePools[0].ProvisioningState, result.NodePools[0].ProvisioningState);
- Assert.Equal(expectedNodePools[0].PowerState?.Code, result.NodePools[0].PowerState?.Code);
+ Assert.Equal(expectedNodePools[0].PowerState, result.NodePools[0].PowerState);
Assert.Equal(expectedNodePools[0].OrchestratorVersion, result.NodePools[0].OrchestratorVersion);
Assert.Equal(expectedNodePools[0].CurrentOrchestratorVersion, result.NodePools[0].CurrentOrchestratorVersion);
Assert.Equal(expectedNodePools[0].EnableNodePublicIP, result.NodePools[0].EnableNodePublicIP);
@@ -199,7 +199,7 @@ public async Task ExecuteAsync_ReturnsNodePoolsList()
public async Task ExecuteAsync_ReturnsEmptyWhenNoNodePools()
{
// Arrange
- _aksService.ListNodePools(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())
+ _aksService.ListNodePoolsAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())
.Returns([]);
var context = new CommandContext(_serviceProvider);
@@ -223,7 +223,7 @@ public async Task ExecuteAsync_ReturnsEmptyWhenNoNodePools()
public async Task ExecuteAsync_HandlesServiceErrors()
{
// Arrange
- _aksService.ListNodePools(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())
+ _aksService.ListNodePoolsAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())
.Returns(Task.FromException>(new Exception("Test error")));
var context = new CommandContext(_serviceProvider);
diff --git a/tools/Azure.Mcp.Tools.Sql/src/Services/Models/SqlDatabaseProperties.cs b/tools/Azure.Mcp.Tools.Sql/src/Services/Models/SqlDatabaseProperties.cs
index e8b4f3e14..8c77c8d0f 100644
--- a/tools/Azure.Mcp.Tools.Sql/src/Services/Models/SqlDatabaseProperties.cs
+++ b/tools/Azure.Mcp.Tools.Sql/src/Services/Models/SqlDatabaseProperties.cs
@@ -23,8 +23,6 @@ internal sealed class SqlDatabaseProperties
public DateTimeOffset? CreatedOn { get; set; }
/// The current service level objective name of the database.
public string? CurrentServiceObjectiveName { get; set; }
- /// The license type to apply for this database. `LicenseIncluded` if you need a license, or `BasePrice` if you have a license and are eligible for the Azure Hybrid Benefit.
- public string? LicenseType { get; set; }
/// This records the earliest start date and time that restore is available for this database (ISO8601 format).
[JsonPropertyName("earliestRestoreDate")]
public DateTimeOffset? EarliestRestoreOn { get; set; }