diff --git a/core/Microsoft.Mcp.Core/src/Services/Azure/BaseAzureResourceService.cs b/core/Microsoft.Mcp.Core/src/Services/Azure/BaseAzureResourceService.cs index f0520a3968..f4dd5c8396 100644 --- a/core/Microsoft.Mcp.Core/src/Services/Azure/BaseAzureResourceService.cs +++ b/core/Microsoft.Mcp.Core/src/Services/Azure/BaseAzureResourceService.cs @@ -207,11 +207,11 @@ protected async Task> ExecuteResourceQueryAsync( /// Optional tenant to use when creating the client. /// Optional retry policy used by token acquisition. /// An initialized configured with the requested API version. - protected async Task CreateArmClientWithApiVersionAsync(string resourceTypeForApiVersion, string apiVersion, string? tenant = null, RetryPolicyOptions? retryPolicy = null) + protected async Task CreateArmClientWithApiVersionAsync(string resourceTypeForApiVersion, string apiVersion, string? tenant = null, RetryPolicyOptions? retryPolicy = null, CancellationToken cancellationToken = default) { var options = new ArmClientOptions(); options.SetApiVersion(resourceTypeForApiVersion, apiVersion); - return await CreateArmClientAsync(tenant, retryPolicy, options).ConfigureAwait(false); + return await CreateArmClientAsync(tenant, retryPolicy, options, cancellationToken).ConfigureAwait(false); } /// @@ -251,7 +251,7 @@ protected async Task GetGenericResourceAsync(ArmClient armClien /// The instance for the requested resource. /// Thrown when a required parameter is null. /// Thrown when the content is invalid. - protected async Task CreateOrUpdateGenericResourceAsync(ArmClient armClient, ResourceIdentifier resourceIdentifier, AzureLocation azureLocation, T content, JsonTypeInfo jsonTypeInfo) + protected async Task CreateOrUpdateGenericResourceAsync(ArmClient armClient, ResourceIdentifier resourceIdentifier, AzureLocation azureLocation, T content, JsonTypeInfo jsonTypeInfo, CancellationToken cancellationToken) { if (armClient == null) throw new ArgumentNullException(nameof(armClient)); @@ -263,7 +263,7 @@ protected async Task CreateOrUpdateGenericResourceAsync(ArmC GenericResourceData data = dataModel.Create(ref reader, new ModelReaderWriterOptions("W")) ?? throw new InvalidOperationException("Failed to create deployment data"); // Create the resource - var result = await armClient.GetGenericResources().CreateOrUpdateAsync(WaitUntil.Completed, resourceIdentifier, data); + var result = await armClient.GetGenericResources().CreateOrUpdateAsync(WaitUntil.Completed, resourceIdentifier, data, cancellationToken); return result.Value; } } diff --git a/servers/Azure.Mcp.Server/docs/new-command.md b/servers/Azure.Mcp.Server/docs/new-command.md index c36db25fb5..a9cc33af78 100644 --- a/servers/Azure.Mcp.Server/docs/new-command.md +++ b/servers/Azure.Mcp.Server/docs/new-command.md @@ -270,7 +270,7 @@ Choose the appropriate base class for your service based on the operations neede **API Pattern Discovery:** - Study existing services (e.g., Sql, Postgres, Redis) to understand resource access patterns - Use resource collections correctly - - ✅ Good: `.GetSqlServers().GetAsync(serverName)` + - ✅ Good: `.GetSqlServers().GetAsync(serverName, cancellationToken: cancellationToken)` - ❌ Bad: `.GetSqlServerAsync(serverName, cancellationToken)` - Check Azure SDK documentation for correct method signatures and property names @@ -364,9 +364,9 @@ var upgradeStatus = await vmssResource.Value .GetLatestVirtualMachineScaleSetRollingUpgradeAsync(cancellationToken); // ✅ Correct: VMSS instances -var vms = vmssResource.Value.GetVirtualMachineScaleSetVms().GetAllAsync(); +var vms = await vmssResource.Value.GetVirtualMachineScaleSetVms().GetAllAsync(cancellationToken: cancellationToken); -// Pattern: Get{ResourceType}() returns collection, then .GetAsync() or .GetAllAsync() +// Pattern: Get{ResourceType}() returns collection, then .GetAsync(ResourceName, CancellationToken) or .GetAllAsync(CancellationToken) ``` ### 2. Options Class @@ -904,7 +904,7 @@ public interface IMyService string subscription, string? resourceGroup = null, RetryPolicyOptions? retryPolicy = null, - CancellationToken cancellationToken); + CancellationToken cancellationToken = default); } ``` @@ -2175,7 +2175,7 @@ catch (Exception ex) - **Pattern**: ```csharp // Correct - use service -var subscriptionResource = await _subscriptionService.GetSubscription(subscription, null, retryPolicy); +var subscriptionResource = await _subscriptionService.GetSubscription(subscription, null, retryPolicy, cancellationToken); // Wrong - manual creation var armClient = await CreateArmClientAsync(null, retryPolicy); @@ -2185,7 +2185,7 @@ var subscriptionResource = armClient.GetSubscriptionResource(new ResourceIdentif **Issue: `cannot convert from 'System.Threading.CancellationToken' to 'string'`** - **Cause**: Wrong parameter order in resource manager method calls - **Solution**: Check method signatures; many Azure SDK methods don't take CancellationToken as second parameter -- **Fix**: Use `.GetAsync(resourceName)` instead of `.GetAsync(resourceName, cancellationToken)` +- **Fix**: Use `.GetAsync(resourceName, cancellationToken: cancellationToken)` instead of `.GetAsync(resourceName, cancellationToken)` **Issue: `'SqlDatabaseData' does not contain a definition for 'CreationDate'`** - **Cause**: Property names in Azure SDK differ from expected/documented names @@ -2201,7 +2201,7 @@ var subscriptionResource = armClient.GetSubscriptionResource(new ResourceIdentif **Issue: Wrong resource access pattern** - **Problem**: Using `.GetSqlServerAsync(name, cancellationToken)` -- **Solution**: Use resource collections: `.GetSqlServers().GetAsync(name)` +- **Solution**: Use resource collections: `GetSqlServers().GetAsync(name, cancellationToken: cancellationToken)` - **Pattern**: Always access through collections, not direct async methods ### Live Test Infrastructure Issues @@ -2412,7 +2412,8 @@ public sealed class StorageAccountGetCommand : SubscriptionCommand ExecuteAsync( CommandContext context, - ParseResult parseResult) + ParseResult parseResult, + CancellationToken cancellationToken) { var options = BindOptions(parseResult); @@ -2420,7 +2421,8 @@ public sealed class StorageAccountGetCommand : SubscriptionCommand ExecuteAsync( CommandContext context, - ParseResult parseResult) + ParseResult parseResult, + CancellationToken cancellationToken) { // ✅ Options created per-request, no shared state var options = BindOptions(parseResult); @@ -2520,7 +2523,8 @@ public sealed class SqlDatabaseListCommand : SubscriptionCommand DeployModel( try { // Create ArmClient for deployments - ArmClient armClient = await CreateArmClientWithApiVersionAsync("Microsoft.CognitiveServices/accounts/deployments", "2025-06-01", null, retryPolicy); + ArmClient armClient = await CreateArmClientWithApiVersionAsync("Microsoft.CognitiveServices/accounts/deployments", "2025-06-01", null, retryPolicy, cancellationToken); // Retrieve the Cognitive Services account var cognitiveServicesAccount = await GetGenericResourceAsync( @@ -262,7 +262,8 @@ public async Task DeployModel( deploymentId, cognitiveServicesAccount.Data.Location, deploymentData, - FoundryJsonContext.Default.CognitiveServicesAccountDeploymentData); + FoundryJsonContext.Default.CognitiveServicesAccountDeploymentData, + cancellationToken); if (!result.HasData) { return new ModelDeploymentResult diff --git a/tools/Azure.Mcp.Tools.Quota/src/Services/Util/AzureRegionChecker.cs b/tools/Azure.Mcp.Tools.Quota/src/Services/Util/AzureRegionChecker.cs index 4639778b8d..fe6f67c81f 100644 --- a/tools/Azure.Mcp.Tools.Quota/src/Services/Util/AzureRegionChecker.cs +++ b/tools/Azure.Mcp.Tools.Quota/src/Services/Util/AzureRegionChecker.cs @@ -101,7 +101,7 @@ public override async Task> GetAvailableRegionsAsync(string resourc { try { - var quotas = subscription.GetModelsAsync(region); + var quotas = subscription.GetModelsAsync(region, cancellationToken); await foreach (CognitiveServicesModel modelElement in quotas.WithCancellation(cancellationToken)) { @@ -152,7 +152,7 @@ public override async Task> GetAvailableRegionsAsync(string resourc { try { - AsyncPageable result = subscription.ExecuteLocationBasedCapabilitiesAsync(region); + AsyncPageable result = subscription.ExecuteLocationBasedCapabilitiesAsync(region, cancellationToken); await foreach (var capability in result.WithCancellation(cancellationToken)) { if (capability.SupportedServerEditions?.Any() == true) diff --git a/tools/Azure.Mcp.Tools.Storage/src/Services/StorageService.cs b/tools/Azure.Mcp.Tools.Storage/src/Services/StorageService.cs index 55e00b4045..bd3ca3624d 100644 --- a/tools/Azure.Mcp.Tools.Storage/src/Services/StorageService.cs +++ b/tools/Azure.Mcp.Tools.Storage/src/Services/StorageService.cs @@ -106,7 +106,7 @@ public async Task CreateStorageAccount( try { // Create ArmClient for deployments - ArmClient armClient = await CreateArmClientWithApiVersionAsync("Microsoft.Storage/storageAccounts", "2024-01-01", null, retryPolicy); + ArmClient armClient = await CreateArmClientWithApiVersionAsync("Microsoft.Storage/storageAccounts", "2024-01-01", null, retryPolicy, cancellationToken); // Prepare data ResourceIdentifier accountId = new ResourceIdentifier($"/subscriptions/{subscription}/resourceGroups/{resourceGroup}/providers/Microsoft.Storage/storageAccounts/{account}"); @@ -134,7 +134,8 @@ public async Task CreateStorageAccount( accountId, location, createContent, - StorageJsonContext.Default.StorageAccountCreateOrUpdateContent); + StorageJsonContext.Default.StorageAccountCreateOrUpdateContent, + cancellationToken); if (!result.HasData) { return new StorageAccountResult(