Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -207,11 +207,11 @@ protected async Task<ResourceQueryResults<T>> ExecuteResourceQueryAsync<T>(
/// <param name="tenant">Optional tenant to use when creating the client.</param>
/// <param name="retryPolicy">Optional retry policy used by token acquisition.</param>
/// <returns>An initialized <see cref="ArmClient"/> configured with the requested API version.</returns>
protected async Task<ArmClient> CreateArmClientWithApiVersionAsync(string resourceTypeForApiVersion, string apiVersion, string? tenant = null, RetryPolicyOptions? retryPolicy = null)
protected async Task<ArmClient> 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);
}

/// <summary>
Expand Down Expand Up @@ -251,7 +251,7 @@ protected async Task<GenericResource> GetGenericResourceAsync(ArmClient armClien
/// <returns>The <see cref="GenericResource"/> instance for the requested resource.</returns>
/// <exception cref="ArgumentNullException">Thrown when a required parameter is null.</exception>
/// <exception cref="InvalidOperationException">Thrown when the content is invalid.</exception>
protected async Task<GenericResource> CreateOrUpdateGenericResourceAsync<T>(ArmClient armClient, ResourceIdentifier resourceIdentifier, AzureLocation azureLocation, T content, JsonTypeInfo<T> jsonTypeInfo)
protected async Task<GenericResource> CreateOrUpdateGenericResourceAsync<T>(ArmClient armClient, ResourceIdentifier resourceIdentifier, AzureLocation azureLocation, T content, JsonTypeInfo<T> jsonTypeInfo, CancellationToken cancellationToken)
{
if (armClient == null)
throw new ArgumentNullException(nameof(armClient));
Expand All @@ -263,7 +263,7 @@ protected async Task<GenericResource> CreateOrUpdateGenericResourceAsync<T>(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;
}
}
Expand Down
29 changes: 17 additions & 12 deletions servers/Azure.Mcp.Server/docs/new-command.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -904,7 +904,7 @@ public interface IMyService
string subscription,
string? resourceGroup = null,
RetryPolicyOptions? retryPolicy = null,
CancellationToken cancellationToken);
CancellationToken cancellationToken = default);
}
```

Expand Down Expand Up @@ -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);
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -2412,15 +2412,17 @@ public sealed class StorageAccountGetCommand : SubscriptionCommand<StorageAccoun

public override async Task<CommandResponse> ExecuteAsync(
CommandContext context,
ParseResult parseResult)
ParseResult parseResult,
CancellationToken cancellationToken)
{
var options = BindOptions(parseResult);

// Authentication provider handles both stdio and HTTP scenarios
var accounts = await _storageService.GetStorageAccountsAsync(
options.Subscription!,
options.ResourceGroup,
options.RetryPolicy);
options.RetryPolicy
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing comma after 'options.RetryPolicy' parameter. This will cause a syntax error.

Suggested change
options.RetryPolicy
options.RetryPolicy,

Copilot uses AI. Check for mistakes.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Marking this as unresolved. @msalaman, do you have a local change to fix this syntax error not yet pushed?

cancellationToken);

// Standard response format works for all transports
context.Response.Results = ResponseResult.Create(
Expand Down Expand Up @@ -2473,7 +2475,7 @@ public class StorageService : BaseAzureService, IStorageService
CancellationToken cancellationToken = default)
{
// ✅ Use base class methods that handle authentication and ARM client creation
var armClient = await CreateArmClientAsync(tenant: null, retryPolicy);
var armClient = await CreateArmClientAsync(tenant: null, retryPolicy, cancellationToken: cancellationToken);

// ✅ CreateArmClientAsync automatically uses appropriate auth strategy:
// - OBO flow in remote HTTP mode with --outgoing-auth-strategy UseOnBehalfOf
Expand Down Expand Up @@ -2511,7 +2513,8 @@ public sealed class SqlDatabaseListCommand : SubscriptionCommand<SqlDatabaseList

public override async Task<CommandResponse> ExecuteAsync(
CommandContext context,
ParseResult parseResult)
ParseResult parseResult,
CancellationToken cancellationToken)
{
// ✅ Options created per-request, no shared state
var options = BindOptions(parseResult);
Expand All @@ -2520,7 +2523,8 @@ public sealed class SqlDatabaseListCommand : SubscriptionCommand<SqlDatabaseList
var databases = await _sqlService.ListDatabasesAsync(
options.Subscription!,
options.ResourceGroup,
options.Server);
options.Server,
cancellationToken: cancellationToken);

return context.Response;
}
Expand Down Expand Up @@ -2739,6 +2743,7 @@ Before submitting:
### Azure SDK Integration
- [ ] All Azure SDK property names verified and correct
- [ ] Resource access patterns use collections (e.g., `.GetSqlServers().GetAsync()`)
- [ ] Use cancellation token when using async methods (e.g., `GetAsync(serverName, cancellationToken: cancellationToken)`)
- [ ] Subscription resolution uses `ISubscriptionService.GetSubscription()`
- [ ] Service constructor includes `ISubscriptionService` injection for Azure resources

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ public async Task<ModelDeploymentResult> 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(
Expand Down Expand Up @@ -262,7 +262,8 @@ public async Task<ModelDeploymentResult> DeployModel(
deploymentId,
cognitiveServicesAccount.Data.Location,
deploymentData,
FoundryJsonContext.Default.CognitiveServicesAccountDeploymentData);
FoundryJsonContext.Default.CognitiveServicesAccountDeploymentData,
cancellationToken);
if (!result.HasData)
{
return new ModelDeploymentResult
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ public override async Task<List<string>> GetAvailableRegionsAsync(string resourc
{
try
{
var quotas = subscription.GetModelsAsync(region);
var quotas = subscription.GetModelsAsync(region, cancellationToken);

await foreach (CognitiveServicesModel modelElement in quotas.WithCancellation(cancellationToken))
{
Expand Down Expand Up @@ -152,7 +152,7 @@ public override async Task<List<string>> GetAvailableRegionsAsync(string resourc
{
try
{
AsyncPageable<PostgreSqlFlexibleServerCapabilityProperties> result = subscription.ExecuteLocationBasedCapabilitiesAsync(region);
AsyncPageable<PostgreSqlFlexibleServerCapabilityProperties> result = subscription.ExecuteLocationBasedCapabilitiesAsync(region, cancellationToken);
await foreach (var capability in result.WithCancellation(cancellationToken))
{
if (capability.SupportedServerEditions?.Any() == true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ public async Task<StorageAccountResult> 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}");
Expand Down Expand Up @@ -134,7 +134,8 @@ public async Task<StorageAccountResult> CreateStorageAccount(
accountId,
location,
createContent,
StorageJsonContext.Default.StorageAccountCreateOrUpdateContent);
StorageJsonContext.Default.StorageAccountCreateOrUpdateContent,
cancellationToken);
if (!result.HasData)
{
return new StorageAccountResult(
Expand Down