diff --git a/servers/Azure.Mcp.Server/changelog-entries/1781298285544.yaml b/servers/Azure.Mcp.Server/changelog-entries/1781298285544.yaml new file mode 100644 index 0000000000..86ff80c750 --- /dev/null +++ b/servers/Azure.Mcp.Server/changelog-entries/1781298285544.yaml @@ -0,0 +1,3 @@ +changes: + - section: "Breaking Changes" + description: "Removed unused parameters from App Service tools." diff --git a/tools/Azure.Mcp.Tools.AppService/src/Azure.Mcp.Tools.AppService.csproj b/tools/Azure.Mcp.Tools.AppService/src/Azure.Mcp.Tools.AppService.csproj index d142f82723..ce87031e90 100644 --- a/tools/Azure.Mcp.Tools.AppService/src/Azure.Mcp.Tools.AppService.csproj +++ b/tools/Azure.Mcp.Tools.AppService/src/Azure.Mcp.Tools.AppService.csproj @@ -14,6 +14,5 @@ - diff --git a/tools/Azure.Mcp.Tools.AppService/src/Commands/BaseAppServiceCommand.cs b/tools/Azure.Mcp.Tools.AppService/src/Commands/BaseAppServiceCommand.cs deleted file mode 100644 index ea7105e340..0000000000 --- a/tools/Azure.Mcp.Tools.AppService/src/Commands/BaseAppServiceCommand.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Diagnostics.CodeAnalysis; -using Azure.Mcp.Core.Commands.Subscription; -using Azure.Mcp.Tools.AppService.Options; -using Microsoft.Mcp.Core.Commands; -using Microsoft.Mcp.Core.Extensions; -using Microsoft.Mcp.Core.Models.Option; - -namespace Azure.Mcp.Tools.AppService.Commands; - -public abstract class BaseAppServiceCommand< - [DynamicallyAccessedMembers(TrimAnnotations.CommandAnnotations)] TOptions>(bool resourceGroupRequired = false, bool appRequired = false) - : SubscriptionCommand - where TOptions : BaseAppServiceOptions, new() -{ - protected override void RegisterOptions(Command command) - { - base.RegisterOptions(command); - command.Options.Add(resourceGroupRequired - ? OptionDefinitions.Common.ResourceGroup.AsRequired() - : OptionDefinitions.Common.ResourceGroup.AsOptional()); - command.Options.Add(appRequired - ? AppServiceOptionDefinitions.AppServiceName.AsRequired() - : AppServiceOptionDefinitions.AppServiceName.AsOptional()); - } - - protected override TOptions BindOptions(ParseResult parseResult) - { - var options = base.BindOptions(parseResult); - options.ResourceGroup ??= parseResult.GetValueOrDefault(OptionDefinitions.Common.ResourceGroup.Name); - options.AppName = parseResult.GetValueOrDefault(AppServiceOptionDefinitions.AppServiceName.Name); - return options; - } -} diff --git a/tools/Azure.Mcp.Tools.AppService/src/Commands/Database/DatabaseAddCommand.cs b/tools/Azure.Mcp.Tools.AppService/src/Commands/Database/DatabaseAddCommand.cs index 68fb394201..471a1713ae 100644 --- a/tools/Azure.Mcp.Tools.AppService/src/Commands/Database/DatabaseAddCommand.cs +++ b/tools/Azure.Mcp.Tools.AppService/src/Commands/Database/DatabaseAddCommand.cs @@ -1,13 +1,13 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using Azure.Mcp.Core.Commands.Subscription; +using Azure.Mcp.Core.Services.Azure.Subscription; using Azure.Mcp.Tools.AppService.Models; -using Azure.Mcp.Tools.AppService.Options; using Azure.Mcp.Tools.AppService.Options.Database; using Azure.Mcp.Tools.AppService.Services; using Microsoft.Extensions.Logging; using Microsoft.Mcp.Core.Commands; -using Microsoft.Mcp.Core.Extensions; using Microsoft.Mcp.Core.Models.Command; namespace Azure.Mcp.Tools.AppService.Commands.Database; @@ -27,51 +27,24 @@ namespace Azure.Mcp.Tools.AppService.Commands.Database; ReadOnly = false, Secret = false, LocalRequired = false)] -public sealed class DatabaseAddCommand(ILogger logger, IAppServiceService appServiceService) - : BaseAppServiceCommand(resourceGroupRequired: true, appRequired: true) +public sealed class DatabaseAddCommand(ILogger logger, IAppServiceService appServiceService, ISubscriptionResolver subscriptionResolver) + : SubscriptionCommand(subscriptionResolver) { private readonly ILogger _logger = logger; private readonly IAppServiceService _appServiceService = appServiceService; - protected override void RegisterOptions(Command command) + public override async Task ExecuteAsync(CommandContext context, DatabaseAddOptions options, CancellationToken cancellationToken) { - base.RegisterOptions(command); - command.Options.Add(AppServiceOptionDefinitions.DatabaseTypeOption); - command.Options.Add(AppServiceOptionDefinitions.DatabaseServerOption); - command.Options.Add(AppServiceOptionDefinitions.DatabaseNameOption); - command.Options.Add(AppServiceOptionDefinitions.ConnectionStringOption); - } - - protected override DatabaseAddOptions BindOptions(ParseResult parseResult) - { - var options = base.BindOptions(parseResult); - options.DatabaseType = parseResult.GetValueOrDefault(AppServiceOptionDefinitions.DatabaseTypeOption); - options.DatabaseServer = parseResult.GetValueOrDefault(AppServiceOptionDefinitions.DatabaseServerOption); - options.DatabaseName = parseResult.GetValueOrDefault(AppServiceOptionDefinitions.DatabaseNameOption); - options.ConnectionString = parseResult.GetValueOrDefault(AppServiceOptionDefinitions.ConnectionStringOption); - return options; - } - - public override async Task ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken) - { - // Validate first, then bind - if (!Validate(parseResult.CommandResult, context.Response).IsValid) - { - return context.Response; - } - - var options = BindOptions(parseResult); - try { context.Activity?.AddTag("subscription", options.Subscription); var connectionInfo = await _appServiceService.AddDatabaseAsync( - options.AppName!, - options.ResourceGroup!, - options.DatabaseType!, - options.DatabaseServer!, - options.DatabaseName!, + options.App, + options.ResourceGroup, + options.DatabaseType, + options.DatabaseServer, + options.Database, options.ConnectionString ?? string.Empty, // connectionString - will be generated if not provided options.Subscription!, options.Tenant, @@ -82,12 +55,12 @@ public override async Task ExecuteAsync(CommandContext context, } catch (Exception ex) { - _logger.LogError(ex, "Failed to add database connection to App Service '{AppName}'", options.AppName); + _logger.LogError(ex, "Failed to add database connection to App Service '{App}'", options.App); HandleException(context, ex); } return context.Response; } - public record DatabaseAddResult(DatabaseConnectionInfo ConnectionInfo); + public sealed record DatabaseAddResult(DatabaseConnectionInfo ConnectionInfo); } diff --git a/tools/Azure.Mcp.Tools.AppService/src/Commands/Webapp/Deployment/DeploymentGetCommand.cs b/tools/Azure.Mcp.Tools.AppService/src/Commands/Webapp/Deployment/DeploymentGetCommand.cs index d42a33982e..cdecf5d5fd 100644 --- a/tools/Azure.Mcp.Tools.AppService/src/Commands/Webapp/Deployment/DeploymentGetCommand.cs +++ b/tools/Azure.Mcp.Tools.AppService/src/Commands/Webapp/Deployment/DeploymentGetCommand.cs @@ -1,13 +1,13 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using Azure.Mcp.Core.Commands.Subscription; +using Azure.Mcp.Core.Services.Azure.Subscription; using Azure.Mcp.Tools.AppService.Models; -using Azure.Mcp.Tools.AppService.Options; using Azure.Mcp.Tools.AppService.Options.Webapp.Deployment; using Azure.Mcp.Tools.AppService.Services; using Microsoft.Extensions.Logging; using Microsoft.Mcp.Core.Commands; -using Microsoft.Mcp.Core.Extensions; using Microsoft.Mcp.Core.Models.Command; namespace Azure.Mcp.Tools.AppService.Commands.Webapp.Deployment; @@ -29,43 +29,22 @@ namespace Azure.Mcp.Tools.AppService.Commands.Webapp.Deployment; ReadOnly = true, Secret = false, LocalRequired = false)] -public sealed class DeploymentGetCommand(ILogger logger, IAppServiceService appServiceService) - : BaseAppServiceCommand(resourceGroupRequired: true, appRequired: true) +public sealed class DeploymentGetCommand(ILogger logger, IAppServiceService appServiceService, ISubscriptionResolver subscriptionResolver) + : SubscriptionCommand(subscriptionResolver) { private readonly ILogger _logger = logger; private readonly IAppServiceService _appServiceService = appServiceService; - protected override void RegisterOptions(Command command) + public override async Task ExecuteAsync(CommandContext context, DeploymentGetOptions options, CancellationToken cancellationToken) { - base.RegisterOptions(command); - command.Options.Add(AppServiceOptionDefinitions.DeploymentIdOption); - } - - protected override DeploymentGetOptions BindOptions(ParseResult parseResult) - { - var options = base.BindOptions(parseResult); - options.DeploymentId = parseResult.GetValueOrDefault(AppServiceOptionDefinitions.DeploymentIdOption.Name); - return options; - } - - public override async Task ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken) - { - // Validate first, then bind - if (!Validate(parseResult.CommandResult, context.Response).IsValid) - { - return context.Response; - } - - var options = BindOptions(parseResult); - try { context.Activity?.AddTag("subscription", options.Subscription); var deployments = await _appServiceService.GetDeploymentsAsync( options.Subscription!, - options.ResourceGroup!, - options.AppName!, + options.ResourceGroup, + options.App, options.DeploymentId, options.Tenant, options.RetryPolicy, @@ -77,13 +56,13 @@ public override async Task ExecuteAsync(CommandContext context, { if (options.DeploymentId == null) { - _logger.LogError(ex, "Failed to list deployments for Web App '{AppName}' in resource group {ResourceGroup} and subscription {Subscription}", - options.AppName, options.ResourceGroup, options.Subscription); + _logger.LogError(ex, "Failed to list deployments for Web App '{App}' in resource group {ResourceGroup} and subscription {Subscription}", + options.App, options.ResourceGroup, options.Subscription); } else { - _logger.LogError(ex, "Failed to get deployment '{DeploymentId}' for Web App '{AppName}' in subscription {Subscription} and resource group {ResourceGroup}", - options.DeploymentId, options.AppName, options.Subscription, options.ResourceGroup); + _logger.LogError(ex, "Failed to get deployment '{DeploymentId}' for Web App '{App}' in subscription {Subscription} and resource group {ResourceGroup}", + options.DeploymentId, options.App, options.Subscription, options.ResourceGroup); } HandleException(context, ex); } @@ -91,5 +70,5 @@ public override async Task ExecuteAsync(CommandContext context, return context.Response; } - public record DeploymentGetResult(List Deployments); + public sealed record DeploymentGetResult(List Deployments); } diff --git a/tools/Azure.Mcp.Tools.AppService/src/Commands/Webapp/Diagnostic/DetectorDiagnoseCommand.cs b/tools/Azure.Mcp.Tools.AppService/src/Commands/Webapp/Diagnostic/DetectorDiagnoseCommand.cs index 74d05ae0a6..0746636f90 100644 --- a/tools/Azure.Mcp.Tools.AppService/src/Commands/Webapp/Diagnostic/DetectorDiagnoseCommand.cs +++ b/tools/Azure.Mcp.Tools.AppService/src/Commands/Webapp/Diagnostic/DetectorDiagnoseCommand.cs @@ -1,15 +1,14 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using Azure.Mcp.Core.Commands.Subscription; +using Azure.Mcp.Core.Services.Azure.Subscription; using Azure.Mcp.Tools.AppService.Models; -using Azure.Mcp.Tools.AppService.Options; using Azure.Mcp.Tools.AppService.Options.Webapp.Diagnostic; using Azure.Mcp.Tools.AppService.Services; using Microsoft.Extensions.Logging; using Microsoft.Mcp.Core.Commands; -using Microsoft.Mcp.Core.Extensions; using Microsoft.Mcp.Core.Models.Command; -using Microsoft.Mcp.Core.Models.Option; namespace Azure.Mcp.Tools.AppService.Commands.Webapp.Diagnostic; @@ -29,82 +28,36 @@ namespace Azure.Mcp.Tools.AppService.Commands.Webapp.Diagnostic; ReadOnly = true, Secret = false, LocalRequired = false)] -public sealed class DetectorDiagnoseCommand(ILogger logger, IAppServiceService appServiceService) - : BaseAppServiceCommand(resourceGroupRequired: true, appRequired: true) +public sealed class DetectorDiagnoseCommand(ILogger logger, IAppServiceService appServiceService, ISubscriptionResolver subscriptionResolver) + : SubscriptionCommand(subscriptionResolver) { private readonly ILogger _logger = logger; private readonly IAppServiceService _appServiceService = appServiceService; - protected override void RegisterOptions(Command command) + public override void ValidateOptions(DetectorDiagnoseOptions options, ValidationResult validationResult) { - base.RegisterOptions(command); - command.Options.Add(AppServiceOptionDefinitions.DetectorId.AsRequired()); - command.Options.Add(AppServiceOptionDefinitions.StartTime); - command.Options.Add(AppServiceOptionDefinitions.EndTime); - command.Options.Add(AppServiceOptionDefinitions.Interval); - command.Validators.Add(result => - { - var startTime = result.GetValueOrDefault(AppServiceOptionDefinitions.StartTime.Name); - var endTime = result.GetValueOrDefault(AppServiceOptionDefinitions.EndTime.Name); - - bool hasStartTime = !string.IsNullOrEmpty(startTime); - bool hasEndTime = !string.IsNullOrEmpty(endTime); - - if (hasStartTime && !DateTimeOffset.TryParse(startTime, out _)) - { - result.AddError($"Invalid start time format: {startTime}. Please provide a valid ISO format date time string."); - } + base.ValidateOptions(options, validationResult); - if (hasEndTime && !DateTimeOffset.TryParse(endTime, out _)) - { - result.AddError($"Invalid end time format: {endTime}. Please provide a valid ISO format date time string."); - } + options.StartTime = options.StartTime?.ToUniversalTime(); + options.EndTime = options.EndTime?.ToUniversalTime(); - if (hasStartTime && hasEndTime - && DateTimeOffset.TryParse(startTime, out var start) - && DateTimeOffset.TryParse(endTime, out var end) - && start > end) - { - result.AddError($"Start time '{startTime}' must be earlier than end time '{endTime}'."); - } - }); - } - - protected override DetectorDiagnoseOptions BindOptions(ParseResult parseResult) - { - var options = base.BindOptions(parseResult); - options.DetectorId = parseResult.GetValueOrDefault(AppServiceOptionDefinitions.DetectorId.Name); - if (DateTimeOffset.TryParse(parseResult.GetValueOrDefault(AppServiceOptionDefinitions.StartTime.Name), out var startTime)) + if (options.StartTime != null && options.EndTime != null && options.StartTime > options.EndTime) { - options.StartTime = startTime.ToUniversalTime(); + validationResult.Errors.Add($"Start time '{options.StartTime}' must be earlier than end time '{options.EndTime}'."); } - if (DateTimeOffset.TryParse(parseResult.GetValueOrDefault(AppServiceOptionDefinitions.EndTime.Name), out var endTime)) - { - options.EndTime = endTime.ToUniversalTime(); - } - options.Interval = parseResult.GetValueOrDefault(AppServiceOptionDefinitions.Interval.Name); - return options; } - public override async Task ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken) + public override async Task ExecuteAsync(CommandContext context, DetectorDiagnoseOptions options, CancellationToken cancellationToken) { - // Validate first, then bind - if (!Validate(parseResult.CommandResult, context.Response).IsValid) - { - return context.Response; - } - - var options = BindOptions(parseResult); - try { context.Activity?.AddTag("subscription", options.Subscription); var diagnoses = await _appServiceService.DiagnoseDetectorAsync( options.Subscription!, - options.ResourceGroup!, - options.AppName!, - options.DetectorId!, + options.ResourceGroup, + options.App, + options.DetectorId, options.StartTime, options.EndTime, options.Interval, @@ -116,13 +69,13 @@ public override async Task ExecuteAsync(CommandContext context, } catch (Exception ex) { - _logger.LogError(ex, "Failed to get diagnostic detectors for Web App '{AppName}' in subscription {Subscription} and resource group {ResourceGroup}", - options.AppName, options.Subscription, options.ResourceGroup); + _logger.LogError(ex, "Failed to get diagnostic detectors for Web App '{App}' in subscription {Subscription} and resource group {ResourceGroup}", + options.App, options.Subscription, options.ResourceGroup); HandleException(context, ex); } return context.Response; } - public record DetectorDiagnoseResult(DiagnosisResults Diagnoses); + public sealed record DetectorDiagnoseResult(DiagnosisResults Diagnoses); } diff --git a/tools/Azure.Mcp.Tools.AppService/src/Commands/Webapp/Diagnostic/DetectorListCommand.cs b/tools/Azure.Mcp.Tools.AppService/src/Commands/Webapp/Diagnostic/DetectorListCommand.cs index 4845c702ab..957b3bbc78 100644 --- a/tools/Azure.Mcp.Tools.AppService/src/Commands/Webapp/Diagnostic/DetectorListCommand.cs +++ b/tools/Azure.Mcp.Tools.AppService/src/Commands/Webapp/Diagnostic/DetectorListCommand.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using Azure.Mcp.Core.Commands.Subscription; +using Azure.Mcp.Core.Services.Azure.Subscription; using Azure.Mcp.Tools.AppService.Models; using Azure.Mcp.Tools.AppService.Options; using Azure.Mcp.Tools.AppService.Services; @@ -26,34 +28,22 @@ namespace Azure.Mcp.Tools.AppService.Commands.Webapp.Diagnostic; ReadOnly = true, Secret = false, LocalRequired = false)] -public sealed class DetectorListCommand(ILogger logger, IAppServiceService appServiceService) - : BaseAppServiceCommand(resourceGroupRequired: true, appRequired: true) +public sealed class DetectorListCommand(ILogger logger, IAppServiceService appServiceService, ISubscriptionResolver subscriptionResolver) + : SubscriptionCommand(subscriptionResolver) { private readonly ILogger _logger = logger; private readonly IAppServiceService _appServiceService = appServiceService; - protected override void RegisterOptions(Command command) => base.RegisterOptions(command); - - protected override BaseAppServiceOptions BindOptions(ParseResult parseResult) => base.BindOptions(parseResult); - - public override async Task ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken) + public override async Task ExecuteAsync(CommandContext context, BaseAppServiceOptions options, CancellationToken cancellationToken) { - // Validate first, then bind - if (!Validate(parseResult.CommandResult, context.Response).IsValid) - { - return context.Response; - } - - var options = BindOptions(parseResult); - try { context.Activity?.AddTag("subscription", options.Subscription); var detectors = await _appServiceService.ListDetectorsAsync( options.Subscription!, - options.ResourceGroup!, - options.AppName!, + options.ResourceGroup, + options.App, options.Tenant, options.RetryPolicy, cancellationToken); @@ -62,13 +52,13 @@ public override async Task ExecuteAsync(CommandContext context, } catch (Exception ex) { - _logger.LogError(ex, "Failed to get diagnostic detectors for Web App '{AppName}' in subscription {Subscription} and resource group {ResourceGroup}", - options.AppName, options.Subscription, options.ResourceGroup); + _logger.LogError(ex, "Failed to get diagnostic detectors for Web App '{App}' in subscription {Subscription} and resource group {ResourceGroup}", + options.App, options.Subscription, options.ResourceGroup); HandleException(context, ex); } return context.Response; } - public record DetectorListResult(List Detectors); + public sealed record DetectorListResult(List Detectors); } diff --git a/tools/Azure.Mcp.Tools.AppService/src/Commands/Webapp/Settings/AppSettingsGetCommand.cs b/tools/Azure.Mcp.Tools.AppService/src/Commands/Webapp/Settings/AppSettingsGetCommand.cs index df12162761..96a51fce17 100644 --- a/tools/Azure.Mcp.Tools.AppService/src/Commands/Webapp/Settings/AppSettingsGetCommand.cs +++ b/tools/Azure.Mcp.Tools.AppService/src/Commands/Webapp/Settings/AppSettingsGetCommand.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using Azure.Mcp.Core.Commands.Subscription; +using Azure.Mcp.Core.Services.Azure.Subscription; using Azure.Mcp.Tools.AppService.Options; using Azure.Mcp.Tools.AppService.Services; using Microsoft.Extensions.Logging; @@ -23,34 +25,22 @@ setting. Application settings may contain sensitive information. ReadOnly = true, Secret = true, LocalRequired = false)] -public sealed class AppSettingsGetCommand(ILogger logger, IAppServiceService appServiceService) - : BaseAppServiceCommand(resourceGroupRequired: true, appRequired: true) +public sealed class AppSettingsGetCommand(ILogger logger, IAppServiceService appServiceService, ISubscriptionResolver subscriptionResolver) + : SubscriptionCommand(subscriptionResolver) { private readonly ILogger _logger = logger; private readonly IAppServiceService _appServiceService = appServiceService; - protected override void RegisterOptions(Command command) => base.RegisterOptions(command); - - protected override BaseAppServiceOptions BindOptions(ParseResult parseResult) => base.BindOptions(parseResult); - - public override async Task ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken) + public override async Task ExecuteAsync(CommandContext context, BaseAppServiceOptions options, CancellationToken cancellationToken) { - // Validate first, then bind - if (!Validate(parseResult.CommandResult, context.Response).IsValid) - { - return context.Response; - } - - var options = BindOptions(parseResult); - try { context.Activity?.AddTag("subscription", options.Subscription); var appSettings = await _appServiceService.GetAppSettingsAsync( options.Subscription!, - options.ResourceGroup!, - options.AppName!, + options.ResourceGroup, + options.App, options.Tenant, options.RetryPolicy, cancellationToken); @@ -59,13 +49,13 @@ public override async Task ExecuteAsync(CommandContext context, } catch (Exception ex) { - _logger.LogError(ex, "Failed to get application settings for Web App details for '{AppName}' in subscription {Subscription} and resource group {ResourceGroup}", - options.AppName, options.Subscription, options.ResourceGroup); + _logger.LogError(ex, "Failed to get application settings for Web App details for '{App}' in subscription {Subscription} and resource group {ResourceGroup}", + options.App, options.Subscription, options.ResourceGroup); HandleException(context, ex); } return context.Response; } - public record AppSettingsGetResult(IDictionary AppSettings); + public sealed record AppSettingsGetResult(IDictionary AppSettings); } diff --git a/tools/Azure.Mcp.Tools.AppService/src/Commands/Webapp/Settings/AppSettingsUpdateCommand.cs b/tools/Azure.Mcp.Tools.AppService/src/Commands/Webapp/Settings/AppSettingsUpdateCommand.cs index eda6e82d40..c4f5293c7c 100644 --- a/tools/Azure.Mcp.Tools.AppService/src/Commands/Webapp/Settings/AppSettingsUpdateCommand.cs +++ b/tools/Azure.Mcp.Tools.AppService/src/Commands/Webapp/Settings/AppSettingsUpdateCommand.cs @@ -1,12 +1,13 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using Azure.Mcp.Core.Commands.Subscription; +using Azure.Mcp.Core.Services.Azure.Subscription; using Azure.Mcp.Tools.AppService.Options; using Azure.Mcp.Tools.AppService.Options.Webapp.Settings; using Azure.Mcp.Tools.AppService.Services; using Microsoft.Extensions.Logging; using Microsoft.Mcp.Core.Commands; -using Microsoft.Mcp.Core.Extensions; using Microsoft.Mcp.Core.Models.Command; namespace Azure.Mcp.Tools.AppService.Commands.Webapp.Settings; @@ -30,43 +31,35 @@ namespace Azure.Mcp.Tools.AppService.Commands.Webapp.Settings; ReadOnly = false, Secret = false, LocalRequired = false)] -public sealed class AppSettingsUpdateCommand(ILogger logger, IAppServiceService appServiceService) - : BaseAppServiceCommand(resourceGroupRequired: true, appRequired: true) +public sealed class AppSettingsUpdateCommand(ILogger logger, IAppServiceService appServiceService, ISubscriptionResolver subscriptionResolver) + : SubscriptionCommand(subscriptionResolver) { private readonly ILogger _logger = logger; private readonly IAppServiceService _appServiceService = appServiceService; - private static readonly HashSet ValidUpdateTypes = ["add", "set", "delete"]; + private static readonly HashSet s_validUpdateTypes = ["add", "set", "delete"]; - protected override void RegisterOptions(Command command) + public override void ValidateOptions(AppSettingsUpdateOptions options, ValidationResult validationResult) { - base.RegisterOptions(command); - command.Options.Add(AppServiceOptionDefinitions.AppSettingName); - command.Options.Add(AppServiceOptionDefinitions.AppSettingValue); - command.Options.Add(AppServiceOptionDefinitions.AppSettingUpdateType); - command.Validators.Add(commandResult => + base.ValidateOptions(options, validationResult); + + if (!ValidateUpdateType(options.SettingUpdateType, out var errorMessage)) + { + validationResult.Errors.Add(errorMessage); + } + + if (!ValidateSettingValue(options.SettingUpdateType, options.SettingValue, out errorMessage)) { - var updateType = commandResult.GetValueOrDefault(AppServiceOptionDefinitions.AppSettingUpdateType.Name); - var settingValue = commandResult.GetValueOrDefault(AppServiceOptionDefinitions.AppSettingValue.Name); - - if (!ValidateUpdateType(updateType, out var errorMessage)) - { - commandResult.AddError(errorMessage); - } - - if (!ValidateSettingValue(updateType, settingValue, out errorMessage)) - { - commandResult.AddError(errorMessage); - } - }); + validationResult.Errors.Add(errorMessage); + } } internal static bool ValidateUpdateType(string? settingUpdateType, out string errorMessage) { errorMessage = string.Empty; - if (!ValidUpdateTypes.Contains(settingUpdateType, StringComparer.OrdinalIgnoreCase)) + if (!s_validUpdateTypes.Contains(settingUpdateType, StringComparer.OrdinalIgnoreCase)) { - errorMessage = $"'{AppServiceOptionDefinitions.AppSettingUpdateTypeName}' must be one of the following values: {string.Join(", ", ValidUpdateTypes)}."; + errorMessage = $"'{AppServiceOptionDefinitions.AppSettingUpdateTypeName}' must be one of the following values: {string.Join(", ", s_validUpdateTypes)}."; return false; } return true; @@ -84,35 +77,18 @@ internal static bool ValidateSettingValue(string? settingUpdateType, string? set return true; } - protected override AppSettingsUpdateOptions BindOptions(ParseResult parseResult) - { - var options = base.BindOptions(parseResult); - options.SettingName = parseResult.GetValueOrDefault(AppServiceOptionDefinitions.AppSettingName.Name); - options.SettingValue = parseResult.GetValueOrDefault(AppServiceOptionDefinitions.AppSettingValue.Name); - options.SettingUpdateType = parseResult.GetValueOrDefault(AppServiceOptionDefinitions.AppSettingUpdateType.Name); - return options; - } - - public override async Task ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken) + public override async Task ExecuteAsync(CommandContext context, AppSettingsUpdateOptions options, CancellationToken cancellationToken) { - // Validate first, then bind - if (!Validate(parseResult.CommandResult, context.Response).IsValid) - { - return context.Response; - } - - var options = BindOptions(parseResult); - try { context.Activity?.AddTag("subscription", options.Subscription); var updateResult = await _appServiceService.UpdateAppSettingsAsync( options.Subscription!, - options.ResourceGroup!, - options.AppName!, - options.SettingName!, - options.SettingUpdateType!, + options.ResourceGroup, + options.App, + options.SettingName, + options.SettingUpdateType, options.SettingValue, options.Tenant, options.RetryPolicy, @@ -122,8 +98,8 @@ public override async Task ExecuteAsync(CommandContext context, } catch (Exception ex) { - _logger.LogError(ex, "Failed to '{SettingUpdateType}' application setting '{SettingName}' for Web App details for '{AppName}' in subscription {Subscription} and resource group {ResourceGroup}", - options.SettingUpdateType, options.SettingName, options.AppName, options.Subscription, options.ResourceGroup); + _logger.LogError(ex, "Failed to '{SettingUpdateType}' application setting '{SettingName}' for Web App details for '{App}' in subscription {Subscription} and resource group {ResourceGroup}", + options.SettingUpdateType, options.SettingName, options.App, options.Subscription, options.ResourceGroup); HandleException(context, ex); } diff --git a/tools/Azure.Mcp.Tools.AppService/src/Commands/Webapp/WebappChangeStateCommand.cs b/tools/Azure.Mcp.Tools.AppService/src/Commands/Webapp/WebappChangeStateCommand.cs index ff50167270..11c03d216e 100644 --- a/tools/Azure.Mcp.Tools.AppService/src/Commands/Webapp/WebappChangeStateCommand.cs +++ b/tools/Azure.Mcp.Tools.AppService/src/Commands/Webapp/WebappChangeStateCommand.cs @@ -1,12 +1,12 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using Azure.Mcp.Tools.AppService.Options; +using Azure.Mcp.Core.Commands.Subscription; +using Azure.Mcp.Core.Services.Azure.Subscription; using Azure.Mcp.Tools.AppService.Options.Webapp; using Azure.Mcp.Tools.AppService.Services; using Microsoft.Extensions.Logging; using Microsoft.Mcp.Core.Commands; -using Microsoft.Mcp.Core.Extensions; using Microsoft.Mcp.Core.Models.Command; namespace Azure.Mcp.Tools.AppService.Commands.Webapp; @@ -34,51 +34,45 @@ Returns a message indicating the result of the operation. Secret = false, LocalRequired = false )] -public sealed class WebappChangeStateCommand(ILogger logger, IAppServiceService appServiceService) - : BaseAppServiceCommand(resourceGroupRequired: true, appRequired: true) +public sealed class WebappChangeStateCommand(ILogger logger, IAppServiceService appServiceService, ISubscriptionResolver subscriptionResolver) + : SubscriptionCommand(subscriptionResolver) { private readonly ILogger _logger = logger; - private static readonly HashSet ValidStateChanges = new(StringComparer.OrdinalIgnoreCase) + private static readonly HashSet s_validStateChanges = new(StringComparer.OrdinalIgnoreCase) { "start", "stop", "restart" }; - protected override void RegisterOptions(Command command) + public override void ValidateOptions(WebappChangeStateOptions options, ValidationResult validationResult) { - base.RegisterOptions(command); - command.Options.Add(AppServiceOptionDefinitions.StateChange); - command.Options.Add(AppServiceOptionDefinitions.SoftRestart); - command.Options.Add(AppServiceOptionDefinitions.WaitForCompletion); - command.Validators.Add(result => + base.ValidateOptions(options, validationResult); + + if (!ValidateStateChange(options.StateChange, out var errorMessage)) { - var stateChange = result.GetValueOrDefault(AppServiceOptionDefinitions.StateChange.Name); - if (!ValidateStateChange(stateChange, out var errorMessage)) - { - result.AddError(errorMessage); - } - else + validationResult.Errors.Add(errorMessage); + } + else + { + if (!"restart".Equals(options.StateChange, StringComparison.OrdinalIgnoreCase)) { - if (!"restart".Equals(stateChange, StringComparison.OrdinalIgnoreCase)) + if (options.SoftRestart) + { + validationResult.Errors.Add("soft-restart only applies for change-state 'restart'."); + } + if (options.WaitForCompletion) { - if (result.HasOptionResult(AppServiceOptionDefinitions.SoftRestart)) - { - result.AddError("soft-restart only applies for change-state 'restart'."); - } - if (result.HasOptionResult(AppServiceOptionDefinitions.WaitForCompletion)) - { - result.AddError("wait-for-completion only applies for change-state 'restart'."); - } + validationResult.Errors.Add("wait-for-completion only applies for change-state 'restart'."); } } - }); + } } internal static bool ValidateStateChange(string? stateChange, out string errorMessage) { - if (string.IsNullOrEmpty(stateChange) || !ValidStateChanges.Contains(stateChange)) + if (string.IsNullOrEmpty(stateChange) || !s_validStateChanges.Contains(stateChange)) { errorMessage = $"Invalid value '{stateChange}' for state change. Valid values are: start, stop, restart."; return false; @@ -88,34 +82,17 @@ internal static bool ValidateStateChange(string? stateChange, out string errorMe return true; } - protected override WebappChangeStateOptions BindOptions(ParseResult parseResult) - { - var options = base.BindOptions(parseResult); - options.StateChange = parseResult.GetValueOrDefault(AppServiceOptionDefinitions.StateChange.Name); - options.SoftRestart = parseResult.GetValueOrDefault(AppServiceOptionDefinitions.SoftRestart.Name); - options.WaitForCompletion = parseResult.GetValueOrDefault(AppServiceOptionDefinitions.WaitForCompletion.Name); - return options; - } - - public override async Task ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken) + public override async Task ExecuteAsync(CommandContext context, WebappChangeStateOptions options, CancellationToken cancellationToken) { - // Validate first, then bind - if (!Validate(parseResult.CommandResult, context.Response).IsValid) - { - return context.Response; - } - - var options = BindOptions(parseResult); - try { context.Activity?.AddTag("subscription", options.Subscription); var stateChange = await appServiceService.ChangeWebAppStateAsync( options.Subscription!, - options.ResourceGroup!, - options.AppName!, - options.StateChange!, + options.ResourceGroup, + options.App, + options.StateChange, options.SoftRestart, options.WaitForCompletion, options.Tenant, @@ -128,13 +105,13 @@ public override async Task ExecuteAsync(CommandContext context, { if ("restart".Equals(options.StateChange, StringComparison.OrdinalIgnoreCase)) { - _logger.LogError(ex, "Failed to restart the Web App '{AppName}' in subscription {Subscription} and resource group {ResourceGroup} (Soft Restart: {SoftRestart}, Wait For Completion: {WaitForCompletion})", - options.AppName, options.Subscription, options.ResourceGroup, options.SoftRestart, options.WaitForCompletion); + _logger.LogError(ex, "Failed to restart the Web App '{App}' in subscription {Subscription} and resource group {ResourceGroup} (Soft Restart: {SoftRestart}, Wait For Completion: {WaitForCompletion})", + options.App, options.Subscription, options.ResourceGroup, options.SoftRestart, options.WaitForCompletion); } else { - _logger.LogError(ex, "Failed to {StateChange} the Web App '{AppName}' in subscription {Subscription} and resource group {ResourceGroup}", - options.StateChange, options.AppName, options.Subscription, options.ResourceGroup); + _logger.LogError(ex, "Failed to {StateChange} the Web App '{App}' in subscription {Subscription} and resource group {ResourceGroup}", + options.StateChange, options.App, options.Subscription, options.ResourceGroup); } HandleException(context, ex); } @@ -142,5 +119,5 @@ public override async Task ExecuteAsync(CommandContext context, return context.Response; } - public record WebappChangeStateResult(string StateChangeStatus); + public sealed record WebappChangeStateResult(string StateChangeStatus); } diff --git a/tools/Azure.Mcp.Tools.AppService/src/Commands/Webapp/WebappGetCommand.cs b/tools/Azure.Mcp.Tools.AppService/src/Commands/Webapp/WebappGetCommand.cs index c2a5e90dee..c8198f2646 100644 --- a/tools/Azure.Mcp.Tools.AppService/src/Commands/Webapp/WebappGetCommand.cs +++ b/tools/Azure.Mcp.Tools.AppService/src/Commands/Webapp/WebappGetCommand.cs @@ -1,12 +1,14 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using Azure.Mcp.Core.Commands.Subscription; +using Azure.Mcp.Core.Services.Azure.Subscription; using Azure.Mcp.Tools.AppService.Models; using Azure.Mcp.Tools.AppService.Options; +using Azure.Mcp.Tools.AppService.Options.Webapp; using Azure.Mcp.Tools.AppService.Services; using Microsoft.Extensions.Logging; using Microsoft.Mcp.Core.Commands; -using Microsoft.Mcp.Core.Extensions; using Microsoft.Mcp.Core.Models.Command; using Microsoft.Mcp.Core.Models.Option; @@ -28,38 +30,24 @@ namespace Azure.Mcp.Tools.AppService.Commands.Webapp; ReadOnly = true, Secret = false, LocalRequired = false)] -public sealed class WebappGetCommand(ILogger logger, IAppServiceService appServiceService) - : BaseAppServiceCommand(resourceGroupRequired: false) +public sealed class WebappGetCommand(ILogger logger, IAppServiceService appServiceService, ISubscriptionResolver subscriptionResolver) + : SubscriptionCommand(subscriptionResolver) { private readonly ILogger _logger = logger; private readonly IAppServiceService _appServiceService = appServiceService; - protected override void RegisterOptions(Command command) + public override void ValidateOptions(WebappGetOptions options, ValidationResult validationResult) { - base.RegisterOptions(command); - command.Validators.Add(commandResult => - { - var appName = commandResult.GetValueOrDefault(AppServiceOptionDefinitions.AppServiceName.Name); - var resourceGroup = commandResult.GetValueOrDefault(OptionDefinitions.Common.ResourceGroup.Name); - if (!string.IsNullOrWhiteSpace(appName) && string.IsNullOrWhiteSpace(resourceGroup)) - { - commandResult.AddError($"When specifying '{AppServiceOptionDefinitions.AppServiceName.Name}', you must also specify '{OptionDefinitions.Common.ResourceGroup.Name}'."); - } - }); - } - - protected override BaseAppServiceOptions BindOptions(ParseResult parseResult) => base.BindOptions(parseResult); + base.ValidateOptions(options, validationResult); - public override async Task ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken) - { - // Validate first, then bind - if (!Validate(parseResult.CommandResult, context.Response).IsValid) + if (!string.IsNullOrWhiteSpace(options.App) && string.IsNullOrWhiteSpace(options.ResourceGroup)) { - return context.Response; + validationResult.Errors.Add($"When specifying '{AppServiceOptionDefinitions.AppName}', you must also specify '{OptionDefinitions.Common.ResourceGroupName}'."); } + } - var options = BindOptions(parseResult); - + public override async Task ExecuteAsync(CommandContext context, WebappGetOptions options, CancellationToken cancellationToken) + { try { context.Activity?.AddTag("subscription", options.Subscription); @@ -67,7 +55,7 @@ public override async Task ExecuteAsync(CommandContext context, var webapps = await _appServiceService.GetWebAppsAsync( options.Subscription!, options.ResourceGroup, - options.AppName, + options.App, options.Tenant, options.RetryPolicy, cancellationToken); @@ -76,7 +64,7 @@ public override async Task ExecuteAsync(CommandContext context, } catch (Exception ex) { - if (options.AppName == null) + if (options.App == null) { if (options.ResourceGroup == null) { @@ -90,8 +78,8 @@ public override async Task ExecuteAsync(CommandContext context, } else { - _logger.LogError(ex, "Failed to get Web App details for '{AppName}' in subscription {Subscription} and resource group {ResourceGroup}", - options.AppName, options.Subscription, options.ResourceGroup); + _logger.LogError(ex, "Failed to get Web App details for '{App}' in subscription {Subscription} and resource group {ResourceGroup}", + options.App, options.Subscription, options.ResourceGroup); } HandleException(context, ex); } @@ -99,5 +87,5 @@ public override async Task ExecuteAsync(CommandContext context, return context.Response; } - public record WebappGetResult(List Webapps); + public sealed record WebappGetResult(List Webapps); } diff --git a/tools/Azure.Mcp.Tools.AppService/src/GlobalUsings.cs b/tools/Azure.Mcp.Tools.AppService/src/GlobalUsings.cs deleted file mode 100644 index b41cc886b4..0000000000 --- a/tools/Azure.Mcp.Tools.AppService/src/GlobalUsings.cs +++ /dev/null @@ -1,4 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -global using System.CommandLine; diff --git a/tools/Azure.Mcp.Tools.AppService/src/Options/AppServiceOptionDefinitions.cs b/tools/Azure.Mcp.Tools.AppService/src/Options/AppServiceOptionDefinitions.cs index bc1d1da009..b80bed658a 100644 --- a/tools/Azure.Mcp.Tools.AppService/src/Options/AppServiceOptionDefinitions.cs +++ b/tools/Azure.Mcp.Tools.AppService/src/Options/AppServiceOptionDefinitions.cs @@ -5,118 +5,8 @@ namespace Azure.Mcp.Tools.AppService.Options; public static class AppServiceOptionDefinitions { + internal const string App = "The name of the Azure App Service (e.g., my-webapp)."; public const string AppName = "app"; - public const string DatabaseType = "database-type"; - public const string DatabaseServer = "database-server"; - public const string DatabaseName = "database"; - public const string ConnectionString = "connection-string"; - public const string AppSettingNameName = "setting-name"; public const string AppSettingValueName = "setting-value"; public const string AppSettingUpdateTypeName = "setting-update-type"; - public const string DeploymentIdName = "deployment-id"; - public const string DetectorIdName = "detector-id"; - public const string StartTimeName = "start-time"; - public const string EndTimeName = "end-time"; - public const string IntervalName = "interval"; - public const string StateChangeName = "state-change"; - public const string SoftRestartName = "soft-restart"; - public const string WaitForCompletionName = "wait-for-completion"; - - public static readonly Option AppServiceName = new($"--{AppName}") - { - Description = "The name of the Azure App Service (e.g., my-webapp).", - Required = true - }; - - public static readonly Option DatabaseTypeOption = new($"--{DatabaseType}") - { - Description = "The type of database (e.g., SqlServer, MySQL, PostgreSQL, CosmosDB).", - Required = true - }; - - public static readonly Option DatabaseServerOption = new($"--{DatabaseServer}") - { - Description = "The server name or endpoint for the database (e.g., myserver.database.windows.net).", - Required = true - }; - - public static readonly Option DatabaseNameOption = new($"--{DatabaseName}") - { - Description = "The name of the database to connect to (e.g., mydb).", - Required = true - }; - - public static readonly Option ConnectionStringOption = new($"--{ConnectionString}") - { - Description = "The connection string for the database. If not provided, a default will be generated.", - Required = false - }; - - public static readonly Option AppSettingName = new($"--{AppSettingNameName}") - { - Description = "The name of the application setting.", - Required = true - }; - - public static readonly Option AppSettingValue = new($"--{AppSettingValueName}") - { - Description = "The value of the application setting. Required for add and set update types.", - Required = false - }; - - public static readonly Option AppSettingUpdateType = new($"--{AppSettingUpdateTypeName}") - { - Description = "The type of update to perform on the application setting. Valid values are: add, set, delete.", - Required = true - }; - - public static readonly Option DeploymentIdOption = new($"--{DeploymentIdName}") - { - Description = "The ID of the deployment.", - Required = false - }; - - public static readonly Option DetectorId = new($"--{DetectorIdName}") - { - Description = "The ID of the diagnostic detector to run. Use the 'id' field from 'azmcp appservice webapp diagnostic list' output (e.g., LinuxContainerRecycle, LinuxMemoryDrillDown).", - Required = true - }; - - public static readonly Option StartTime = new($"--{StartTimeName}") - { - Description = "The start time in ISO format (e.g., 2023-01-01T00:00:00Z).", - Required = false - }; - - public static readonly Option EndTime = new($"--{EndTimeName}") - { - Description = "The end time in ISO format (e.g., 2023-01-01T00:00:00Z).", - Required = false - }; - - public static readonly Option Interval = new($"--{IntervalName}") - { - Description = "The time interval (e.g., PT1H for 1 hour, PT5M for 5 minutes).", - Required = false - }; - - public static readonly Option StateChange = new($"--{StateChangeName}") - { - Description = "The state change action to perform. Valid values are: start, stop, restart.", - Required = true - }; - - public static readonly Option SoftRestart = new($"--{SoftRestartName}") - { - Description = "When state-change is restart, indicates whether to perform a soft restart.", - DefaultValueFactory = _ => false, - Required = false - }; - - public static readonly Option WaitForCompletion = new($"--{WaitForCompletionName}") - { - Description = "When state-change is restart, indicates whether to synchronously wait for the state change operation to complete before returning.", - DefaultValueFactory = _ => false, - Required = false - }; } diff --git a/tools/Azure.Mcp.Tools.AppService/src/Options/BaseAppServiceOptions.cs b/tools/Azure.Mcp.Tools.AppService/src/Options/BaseAppServiceOptions.cs index 1d1e586e3c..a050f7c991 100644 --- a/tools/Azure.Mcp.Tools.AppService/src/Options/BaseAppServiceOptions.cs +++ b/tools/Azure.Mcp.Tools.AppService/src/Options/BaseAppServiceOptions.cs @@ -1,13 +1,25 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Text.Json.Serialization; +using Azure.Mcp.Core.Options; using Microsoft.Mcp.Core.Options; namespace Azure.Mcp.Tools.AppService.Options; -public class BaseAppServiceOptions : SubscriptionOptions +public sealed class BaseAppServiceOptions : ISubscriptionOption { - [JsonPropertyName(AppServiceOptionDefinitions.AppName)] - public string? AppName { get; set; } + [Option(Description = AppServiceOptionDefinitions.App)] + public required string App { get; set; } + + [Option(Description = OptionDescriptions.Tenant)] + public string? Tenant { get; set; } + + [Option(Description = OptionDescriptions.Subscription)] + public string? Subscription { get; set; } + + [Option(Description = OptionDescriptions.ResourceGroup)] + public required string ResourceGroup { get; set; } + + [OptionContainer(Prefix = "retry")] + public RetryPolicyOptions? RetryPolicy { get; set; } } diff --git a/tools/Azure.Mcp.Tools.AppService/src/Options/Database/DatabaseAddOptions.cs b/tools/Azure.Mcp.Tools.AppService/src/Options/Database/DatabaseAddOptions.cs index 62e9e5300c..5c221b185a 100644 --- a/tools/Azure.Mcp.Tools.AppService/src/Options/Database/DatabaseAddOptions.cs +++ b/tools/Azure.Mcp.Tools.AppService/src/Options/Database/DatabaseAddOptions.cs @@ -1,21 +1,37 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Text.Json.Serialization; +using Azure.Mcp.Core.Options; +using Microsoft.Mcp.Core.Options; namespace Azure.Mcp.Tools.AppService.Options.Database; -public class DatabaseAddOptions : BaseAppServiceOptions +public sealed class DatabaseAddOptions : ISubscriptionOption { - [JsonPropertyName(AppServiceOptionDefinitions.DatabaseType)] - public string? DatabaseType { get; set; } + [Option(Description = AppServiceOptionDefinitions.App)] + public required string App { get; set; } - [JsonPropertyName(AppServiceOptionDefinitions.DatabaseServer)] - public string? DatabaseServer { get; set; } + [Option(Description = "The type of database (e.g., SqlServer, MySQL, PostgreSQL, CosmosDB).")] + public required string DatabaseType { get; set; } - [JsonPropertyName(AppServiceOptionDefinitions.DatabaseName)] - public string? DatabaseName { get; set; } + [Option(Description = "The server name or endpoint for the database (e.g., myserver.database.windows.net).")] + public required string DatabaseServer { get; set; } - [JsonPropertyName(AppServiceOptionDefinitions.ConnectionString)] + [Option(Description = "The name of the database to connect to (e.g., mydb).")] + public required string Database { get; set; } + + [Option(Description = "The connection string for the database. If not provided, a default will be generated.")] public string? ConnectionString { get; set; } + + [Option(Description = OptionDescriptions.Tenant)] + public string? Tenant { get; set; } + + [Option(Description = OptionDescriptions.Subscription)] + public string? Subscription { get; set; } + + [Option(Description = OptionDescriptions.ResourceGroup)] + public required string ResourceGroup { get; set; } + + [OptionContainer(Prefix = "retry")] + public RetryPolicyOptions? RetryPolicy { get; set; } } diff --git a/tools/Azure.Mcp.Tools.AppService/src/Options/Webapp/Deployment/DeploymentGetOptions.cs b/tools/Azure.Mcp.Tools.AppService/src/Options/Webapp/Deployment/DeploymentGetOptions.cs index 40c9bbf1b4..9867f06297 100644 --- a/tools/Azure.Mcp.Tools.AppService/src/Options/Webapp/Deployment/DeploymentGetOptions.cs +++ b/tools/Azure.Mcp.Tools.AppService/src/Options/Webapp/Deployment/DeploymentGetOptions.cs @@ -1,12 +1,28 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Text.Json.Serialization; +using Azure.Mcp.Core.Options; +using Microsoft.Mcp.Core.Options; namespace Azure.Mcp.Tools.AppService.Options.Webapp.Deployment; -public class DeploymentGetOptions : BaseAppServiceOptions +public sealed class DeploymentGetOptions : ISubscriptionOption { - [JsonPropertyName(AppServiceOptionDefinitions.DeploymentIdName)] + [Option(Description = AppServiceOptionDefinitions.App)] + public required string App { get; set; } + + [Option(Description = "The ID of the deployment.")] public string? DeploymentId { get; set; } + + [Option(Description = OptionDescriptions.Tenant)] + public string? Tenant { get; set; } + + [Option(Description = OptionDescriptions.Subscription)] + public string? Subscription { get; set; } + + [Option(Description = OptionDescriptions.ResourceGroup)] + public required string ResourceGroup { get; set; } + + [OptionContainer(Prefix = "retry")] + public RetryPolicyOptions? RetryPolicy { get; set; } } diff --git a/tools/Azure.Mcp.Tools.AppService/src/Options/Webapp/Diagnostic/DetectorDiagnoseOptions.cs b/tools/Azure.Mcp.Tools.AppService/src/Options/Webapp/Diagnostic/DetectorDiagnoseOptions.cs index 096633e21c..f963cebfed 100644 --- a/tools/Azure.Mcp.Tools.AppService/src/Options/Webapp/Diagnostic/DetectorDiagnoseOptions.cs +++ b/tools/Azure.Mcp.Tools.AppService/src/Options/Webapp/Diagnostic/DetectorDiagnoseOptions.cs @@ -1,21 +1,37 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Text.Json.Serialization; +using Azure.Mcp.Core.Options; +using Microsoft.Mcp.Core.Options; namespace Azure.Mcp.Tools.AppService.Options.Webapp.Diagnostic; -public sealed class DetectorDiagnoseOptions : BaseAppServiceOptions +public sealed class DetectorDiagnoseOptions : ISubscriptionOption { - [JsonPropertyName(AppServiceOptionDefinitions.DetectorIdName)] - public string? DetectorId { get; set; } + [Option(Description = AppServiceOptionDefinitions.App)] + public required string App { get; set; } - [JsonPropertyName(AppServiceOptionDefinitions.StartTimeName)] + [Option(Description = "The ID of the diagnostic detector to run. Use the 'id' field from 'azmcp appservice webapp diagnostic list' output (e.g., LinuxContainerRecycle, LinuxMemoryDrillDown).")] + public required string DetectorId { get; set; } + + [Option(Description = "The start time in ISO format (e.g., 2023-01-01T00:00:00Z).")] public DateTimeOffset? StartTime { get; set; } - [JsonPropertyName(AppServiceOptionDefinitions.EndTimeName)] + [Option(Description = "The end time in ISO format (e.g., 2023-01-01T00:00:00Z).")] public DateTimeOffset? EndTime { get; set; } - [JsonPropertyName(AppServiceOptionDefinitions.IntervalName)] + [Option(Description = "The time interval (e.g., PT1H for 1 hour, PT5M for 5 minutes).")] public string? Interval { get; set; } + + [Option(Description = OptionDescriptions.Tenant)] + public string? Tenant { get; set; } + + [Option(Description = OptionDescriptions.Subscription)] + public string? Subscription { get; set; } + + [Option(Description = OptionDescriptions.ResourceGroup)] + public required string ResourceGroup { get; set; } + + [OptionContainer(Prefix = "retry")] + public RetryPolicyOptions? RetryPolicy { get; set; } } diff --git a/tools/Azure.Mcp.Tools.AppService/src/Options/Webapp/Settings/AppSettingsUpdateOptions.cs b/tools/Azure.Mcp.Tools.AppService/src/Options/Webapp/Settings/AppSettingsUpdateOptions.cs index 6b9ad03637..49fc7a0a62 100644 --- a/tools/Azure.Mcp.Tools.AppService/src/Options/Webapp/Settings/AppSettingsUpdateOptions.cs +++ b/tools/Azure.Mcp.Tools.AppService/src/Options/Webapp/Settings/AppSettingsUpdateOptions.cs @@ -1,18 +1,34 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Text.Json.Serialization; +using Azure.Mcp.Core.Options; +using Microsoft.Mcp.Core.Options; namespace Azure.Mcp.Tools.AppService.Options.Webapp.Settings; -public sealed class AppSettingsUpdateOptions : BaseAppServiceOptions +public sealed class AppSettingsUpdateOptions : ISubscriptionOption { - [JsonPropertyName(AppServiceOptionDefinitions.AppSettingNameName)] - public string? SettingName { get; set; } + [Option(Description = AppServiceOptionDefinitions.App)] + public required string App { get; set; } - [JsonPropertyName(AppServiceOptionDefinitions.AppSettingValueName)] + [Option(Description = "The name of the application setting.")] + public required string SettingName { get; set; } + + [Option(Description = "The value of the application setting. Required for add and set update types.")] public string? SettingValue { get; set; } - [JsonPropertyName(AppServiceOptionDefinitions.AppSettingUpdateTypeName)] - public string? SettingUpdateType { get; set; } + [Option(Description = "The type of update to perform on the application setting. Valid values are: add, set, delete.")] + public required string SettingUpdateType { get; set; } + + [Option(Description = OptionDescriptions.Tenant)] + public string? Tenant { get; set; } + + [Option(Description = OptionDescriptions.Subscription)] + public string? Subscription { get; set; } + + [Option(Description = OptionDescriptions.ResourceGroup)] + public required string ResourceGroup { get; set; } + + [OptionContainer(Prefix = "retry")] + public RetryPolicyOptions? RetryPolicy { get; set; } } diff --git a/tools/Azure.Mcp.Tools.AppService/src/Options/Webapp/WebappChangeStateOptions.cs b/tools/Azure.Mcp.Tools.AppService/src/Options/Webapp/WebappChangeStateOptions.cs index d5da318f8f..2a6e98aacd 100644 --- a/tools/Azure.Mcp.Tools.AppService/src/Options/Webapp/WebappChangeStateOptions.cs +++ b/tools/Azure.Mcp.Tools.AppService/src/Options/Webapp/WebappChangeStateOptions.cs @@ -1,18 +1,34 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Text.Json.Serialization; +using Azure.Mcp.Core.Options; +using Microsoft.Mcp.Core.Options; namespace Azure.Mcp.Tools.AppService.Options.Webapp; -public class WebappChangeStateOptions : BaseAppServiceOptions +public sealed class WebappChangeStateOptions : ISubscriptionOption { - [JsonPropertyName(AppServiceOptionDefinitions.StateChangeName)] - public string? StateChange { get; set; } + [Option(Description = AppServiceOptionDefinitions.App)] + public required string App { get; set; } - [JsonPropertyName(AppServiceOptionDefinitions.SoftRestartName)] + [Option(Description = "The state change action to perform. Valid values are: start, stop, restart.")] + public required string StateChange { get; set; } + + [Option(Description = "When state-change is restart, indicates whether to perform a soft restart.")] public bool SoftRestart { get; set; } = false; - [JsonPropertyName(AppServiceOptionDefinitions.WaitForCompletionName)] + [Option(Description = "When state-change is restart, indicates whether to synchronously wait for the state change operation to complete before returning.")] public bool WaitForCompletion { get; set; } = false; + + [Option(Description = OptionDescriptions.Tenant)] + public string? Tenant { get; set; } + + [Option(Description = OptionDescriptions.Subscription)] + public string? Subscription { get; set; } + + [Option(Description = OptionDescriptions.ResourceGroup)] + public required string ResourceGroup { get; set; } + + [OptionContainer(Prefix = "retry")] + public RetryPolicyOptions? RetryPolicy { get; set; } } diff --git a/tools/Azure.Mcp.Tools.AppService/src/Options/Webapp/WebappGetOptions.cs b/tools/Azure.Mcp.Tools.AppService/src/Options/Webapp/WebappGetOptions.cs new file mode 100644 index 0000000000..8737bba3eb --- /dev/null +++ b/tools/Azure.Mcp.Tools.AppService/src/Options/Webapp/WebappGetOptions.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Azure.Mcp.Core.Options; +using Microsoft.Mcp.Core.Options; + +namespace Azure.Mcp.Tools.AppService.Options.Webapp; + +public sealed class WebappGetOptions : ISubscriptionOption +{ + [Option(Description = AppServiceOptionDefinitions.App)] + public string? App { get; set; } + + [Option(Description = OptionDescriptions.Tenant)] + public string? Tenant { get; set; } + + [Option(Description = OptionDescriptions.Subscription)] + public string? Subscription { get; set; } + + [Option(Description = OptionDescriptions.ResourceGroup)] + public string? ResourceGroup { get; set; } + + [OptionContainer(Prefix = "retry")] + public RetryPolicyOptions? RetryPolicy { get; set; } +} diff --git a/tools/Azure.Mcp.Tools.AppService/src/Services/AppServiceService.cs b/tools/Azure.Mcp.Tools.AppService/src/Services/AppServiceService.cs index 037e5477e1..86cec21954 100644 --- a/tools/Azure.Mcp.Tools.AppService/src/Services/AppServiceService.cs +++ b/tools/Azure.Mcp.Tools.AppService/src/Services/AppServiceService.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Net.Http.Headers; using System.Text.Json; using Azure.Core; using Azure.Mcp.Core.Services.Azure; diff --git a/tools/Azure.Mcp.Tools.AppService/tests/Azure.Mcp.Tools.AppService.Tests/AppServiceCommandTests.cs b/tools/Azure.Mcp.Tools.AppService/tests/Azure.Mcp.Tools.AppService.Tests/AppServiceCommandTests.cs index e5f62b94b0..0e0871e895 100644 --- a/tools/Azure.Mcp.Tools.AppService/tests/Azure.Mcp.Tools.AppService.Tests/AppServiceCommandTests.cs +++ b/tools/Azure.Mcp.Tools.AppService/tests/Azure.Mcp.Tools.AppService.Tests/AppServiceCommandTests.cs @@ -125,7 +125,6 @@ public async Task ExecuteAsync_WithDifferentDatabaseTypes_AcceptsValidTypes(stri [Theory] [InlineData("InvalidType")] - [InlineData("")] [InlineData("random")] public async Task ExecuteAsync_WithInvalidDatabaseTypes_ReturnsValidationError(string invalidDatabaseType) { diff --git a/tools/Azure.Mcp.Tools.AppService/tests/Azure.Mcp.Tools.AppService.Tests/Commands/Database/DatabaseAddCommandTests.cs b/tools/Azure.Mcp.Tools.AppService/tests/Azure.Mcp.Tools.AppService.Tests/Commands/Database/DatabaseAddCommandTests.cs index d22f279b87..1eff13c07d 100644 --- a/tools/Azure.Mcp.Tools.AppService/tests/Azure.Mcp.Tools.AppService.Tests/Commands/Database/DatabaseAddCommandTests.cs +++ b/tools/Azure.Mcp.Tools.AppService/tests/Azure.Mcp.Tools.AppService.Tests/Commands/Database/DatabaseAddCommandTests.cs @@ -2,11 +2,11 @@ // Licensed under the MIT License. using System.Net; +using Azure.Mcp.Tests.Commands; using Azure.Mcp.Tools.AppService.Commands.Database; using Azure.Mcp.Tools.AppService.Models; using Azure.Mcp.Tools.AppService.Services; using Microsoft.Mcp.Core.Options; -using Microsoft.Mcp.Tests.Client; using NSubstitute; using NSubstitute.ExceptionExtensions; using Xunit; @@ -14,7 +14,7 @@ namespace Azure.Mcp.Tools.AppService.Tests.Commands.Database; [Trait("Command", "DatabaseAdd")] -public class DatabaseAddCommandTests : CommandUnitTestsBase +public class DatabaseAddCommandTests : SubscriptionCommandUnitTestsBase { [Theory] [InlineData("SqlServer", "test-server.database.windows.net", "test-db", null, null)] diff --git a/tools/Azure.Mcp.Tools.AppService/tests/Azure.Mcp.Tools.AppService.Tests/Commands/Webapp/Deployment/DeploymentGetCommandTests.cs b/tools/Azure.Mcp.Tools.AppService/tests/Azure.Mcp.Tools.AppService.Tests/Commands/Webapp/Deployment/DeploymentGetCommandTests.cs index 2d67e95d23..db696270db 100644 --- a/tools/Azure.Mcp.Tools.AppService/tests/Azure.Mcp.Tools.AppService.Tests/Commands/Webapp/Deployment/DeploymentGetCommandTests.cs +++ b/tools/Azure.Mcp.Tools.AppService/tests/Azure.Mcp.Tools.AppService.Tests/Commands/Webapp/Deployment/DeploymentGetCommandTests.cs @@ -3,12 +3,12 @@ using System.Net; using System.Text.Json; +using Azure.Mcp.Tests.Commands; using Azure.Mcp.Tools.AppService.Commands; using Azure.Mcp.Tools.AppService.Commands.Webapp.Deployment; using Azure.Mcp.Tools.AppService.Models; using Azure.Mcp.Tools.AppService.Services; using Microsoft.Mcp.Core.Options; -using Microsoft.Mcp.Tests.Client; using NSubstitute; using NSubstitute.ExceptionExtensions; using Xunit; @@ -16,7 +16,7 @@ namespace Azure.Mcp.Tools.AppService.Tests.Commands.Webapp.Deployment; [Trait("Command", "DeploymentGet")] -public class DeploymentGetCommandTests : CommandUnitTestsBase +public class DeploymentGetCommandTests : SubscriptionCommandUnitTestsBase { [Theory] [InlineData(null)] diff --git a/tools/Azure.Mcp.Tools.AppService/tests/Azure.Mcp.Tools.AppService.Tests/Commands/Webapp/Diagnostic/DetectorDiagnoseCommandTests.cs b/tools/Azure.Mcp.Tools.AppService/tests/Azure.Mcp.Tools.AppService.Tests/Commands/Webapp/Diagnostic/DetectorDiagnoseCommandTests.cs index 73d1e3b19b..f07835cd7a 100644 --- a/tools/Azure.Mcp.Tools.AppService/tests/Azure.Mcp.Tools.AppService.Tests/Commands/Webapp/Diagnostic/DetectorDiagnoseCommandTests.cs +++ b/tools/Azure.Mcp.Tools.AppService/tests/Azure.Mcp.Tools.AppService.Tests/Commands/Webapp/Diagnostic/DetectorDiagnoseCommandTests.cs @@ -2,13 +2,13 @@ // Licensed under the MIT License. using System.Net; +using Azure.Mcp.Tests.Commands; using Azure.Mcp.Tools.AppService.Commands; using Azure.Mcp.Tools.AppService.Commands.Webapp.Diagnostic; using Azure.Mcp.Tools.AppService.Models; using Azure.Mcp.Tools.AppService.Services; using Azure.ResourceManager.AppService.Models; using Microsoft.Mcp.Core.Options; -using Microsoft.Mcp.Tests.Client; using NSubstitute; using NSubstitute.ExceptionExtensions; using Xunit; @@ -16,7 +16,7 @@ namespace Azure.Mcp.Tools.AppService.Tests.Commands.Webapp.Diagnostic; [Trait("Command", "DetectorDiagnose")] -public class DetectorDiagnoseCommandTests : CommandUnitTestsBase +public class DetectorDiagnoseCommandTests : SubscriptionCommandUnitTestsBase { [Theory] [InlineData(null, null, null)] diff --git a/tools/Azure.Mcp.Tools.AppService/tests/Azure.Mcp.Tools.AppService.Tests/Commands/Webapp/Diagnostic/DetectorListCommandTests.cs b/tools/Azure.Mcp.Tools.AppService/tests/Azure.Mcp.Tools.AppService.Tests/Commands/Webapp/Diagnostic/DetectorListCommandTests.cs index 16e096bb09..bad57a0350 100644 --- a/tools/Azure.Mcp.Tools.AppService/tests/Azure.Mcp.Tools.AppService.Tests/Commands/Webapp/Diagnostic/DetectorListCommandTests.cs +++ b/tools/Azure.Mcp.Tools.AppService/tests/Azure.Mcp.Tools.AppService.Tests/Commands/Webapp/Diagnostic/DetectorListCommandTests.cs @@ -2,12 +2,12 @@ // Licensed under the MIT License. using System.Net; +using Azure.Mcp.Tests.Commands; using Azure.Mcp.Tools.AppService.Commands; using Azure.Mcp.Tools.AppService.Commands.Webapp.Diagnostic; using Azure.Mcp.Tools.AppService.Models; using Azure.Mcp.Tools.AppService.Services; using Microsoft.Mcp.Core.Options; -using Microsoft.Mcp.Tests.Client; using NSubstitute; using NSubstitute.ExceptionExtensions; using Xunit; @@ -15,7 +15,7 @@ namespace Azure.Mcp.Tools.AppService.Tests.Commands.Webapp.Diagnostic; [Trait("Command", "DetectorList")] -public class DetectorListCommandTests : CommandUnitTestsBase +public class DetectorListCommandTests : SubscriptionCommandUnitTestsBase { [Fact] public async Task ExecuteAsync_WithValidParameters_CallsServiceWithCorrectArguments() diff --git a/tools/Azure.Mcp.Tools.AppService/tests/Azure.Mcp.Tools.AppService.Tests/Commands/Webapp/Settings/AppSettingsGetCommandTests.cs b/tools/Azure.Mcp.Tools.AppService/tests/Azure.Mcp.Tools.AppService.Tests/Commands/Webapp/Settings/AppSettingsGetCommandTests.cs index e7bc0b206b..38d0448621 100644 --- a/tools/Azure.Mcp.Tools.AppService/tests/Azure.Mcp.Tools.AppService.Tests/Commands/Webapp/Settings/AppSettingsGetCommandTests.cs +++ b/tools/Azure.Mcp.Tools.AppService/tests/Azure.Mcp.Tools.AppService.Tests/Commands/Webapp/Settings/AppSettingsGetCommandTests.cs @@ -3,11 +3,11 @@ using System.Net; using System.Text.Json; +using Azure.Mcp.Tests.Commands; using Azure.Mcp.Tools.AppService.Commands; using Azure.Mcp.Tools.AppService.Commands.Webapp.Settings; using Azure.Mcp.Tools.AppService.Services; using Microsoft.Mcp.Core.Options; -using Microsoft.Mcp.Tests.Client; using NSubstitute; using NSubstitute.ExceptionExtensions; using Xunit; @@ -15,7 +15,7 @@ namespace Azure.Mcp.Tools.AppService.Tests.Commands.Webapp.Settings; [Trait("Command", "AppSettingsGet")] -public class AppSettingsGetCommandTests : CommandUnitTestsBase +public class AppSettingsGetCommandTests : SubscriptionCommandUnitTestsBase { [Fact] public async Task ExecuteAsync_WithValidParameters_CallsServiceWithCorrectArguments() diff --git a/tools/Azure.Mcp.Tools.AppService/tests/Azure.Mcp.Tools.AppService.Tests/Commands/Webapp/Settings/AppSettingsUpdateCommandTests.cs b/tools/Azure.Mcp.Tools.AppService/tests/Azure.Mcp.Tools.AppService.Tests/Commands/Webapp/Settings/AppSettingsUpdateCommandTests.cs index bd240d2b1e..e777f5915a 100644 --- a/tools/Azure.Mcp.Tools.AppService/tests/Azure.Mcp.Tools.AppService.Tests/Commands/Webapp/Settings/AppSettingsUpdateCommandTests.cs +++ b/tools/Azure.Mcp.Tools.AppService/tests/Azure.Mcp.Tools.AppService.Tests/Commands/Webapp/Settings/AppSettingsUpdateCommandTests.cs @@ -2,11 +2,11 @@ // Licensed under the MIT License. using System.Net; +using Azure.Mcp.Tests.Commands; using Azure.Mcp.Tools.AppService.Commands; using Azure.Mcp.Tools.AppService.Commands.Webapp.Settings; using Azure.Mcp.Tools.AppService.Services; using Microsoft.Mcp.Core.Options; -using Microsoft.Mcp.Tests.Client; using NSubstitute; using NSubstitute.ExceptionExtensions; using Xunit; @@ -14,7 +14,7 @@ namespace Azure.Mcp.Tools.AppService.Tests.Commands.Webapp.Settings; [Trait("Command", "AppSettingsUpdate")] -public class AppSettingsUpdateCommandTests : CommandUnitTestsBase +public class AppSettingsUpdateCommandTests : SubscriptionCommandUnitTestsBase { [Theory] [InlineData("add", "Setting1", "Value1", "Application setting 'Setting1' added successfully.")] diff --git a/tools/Azure.Mcp.Tools.AppService/tests/Azure.Mcp.Tools.AppService.Tests/Commands/Webapp/WebappChangeStateCommandTests.cs b/tools/Azure.Mcp.Tools.AppService/tests/Azure.Mcp.Tools.AppService.Tests/Commands/Webapp/WebappChangeStateCommandTests.cs index 5eb16c4722..5f328001bc 100644 --- a/tools/Azure.Mcp.Tools.AppService/tests/Azure.Mcp.Tools.AppService.Tests/Commands/Webapp/WebappChangeStateCommandTests.cs +++ b/tools/Azure.Mcp.Tools.AppService/tests/Azure.Mcp.Tools.AppService.Tests/Commands/Webapp/WebappChangeStateCommandTests.cs @@ -2,11 +2,11 @@ // Licensed under the MIT License. using System.Net; +using Azure.Mcp.Tests.Commands; using Azure.Mcp.Tools.AppService.Commands; using Azure.Mcp.Tools.AppService.Commands.Webapp; using Azure.Mcp.Tools.AppService.Services; using Microsoft.Mcp.Core.Options; -using Microsoft.Mcp.Tests.Client; using NSubstitute; using NSubstitute.ExceptionExtensions; using Xunit; @@ -14,7 +14,7 @@ namespace Azure.Mcp.Tools.AppService.Tests.Commands.Webapp; [Trait("Command", "WebappChangeState")] -public class WebappChangeStateCommandTests : CommandUnitTestsBase +public class WebappChangeStateCommandTests : SubscriptionCommandUnitTestsBase { [Theory] [InlineData("start", false, false)] @@ -147,11 +147,11 @@ public async Task ExecuteAsync_ServiceThrowsException_ReturnsErrorResponse(strin var response = await ExecuteCommandAsync(unparsedArgs.ToArray()); // Assert - Assert.NotNull(response); - Assert.Equal(HttpStatusCode.UnprocessableEntity, response.Status); - await Service.Received(1).ChangeWebAppStateAsync("sub123", "rg1", "test-app", stateChange, softRestart, waitForCompletion, Arg.Any(), Arg.Any(), Arg.Any()); + + Assert.NotNull(response); + Assert.Equal(HttpStatusCode.UnprocessableEntity, response.Status); } } diff --git a/tools/Azure.Mcp.Tools.AppService/tests/Azure.Mcp.Tools.AppService.Tests/Commands/Webapp/WebappGetCommandTests.cs b/tools/Azure.Mcp.Tools.AppService/tests/Azure.Mcp.Tools.AppService.Tests/Commands/Webapp/WebappGetCommandTests.cs index 514ef63af3..37ebad84cf 100644 --- a/tools/Azure.Mcp.Tools.AppService/tests/Azure.Mcp.Tools.AppService.Tests/Commands/Webapp/WebappGetCommandTests.cs +++ b/tools/Azure.Mcp.Tools.AppService/tests/Azure.Mcp.Tools.AppService.Tests/Commands/Webapp/WebappGetCommandTests.cs @@ -3,12 +3,12 @@ using System.Net; using System.Text.Json; +using Azure.Mcp.Tests.Commands; using Azure.Mcp.Tools.AppService.Commands; using Azure.Mcp.Tools.AppService.Commands.Webapp; using Azure.Mcp.Tools.AppService.Models; using Azure.Mcp.Tools.AppService.Services; using Microsoft.Mcp.Core.Options; -using Microsoft.Mcp.Tests.Client; using NSubstitute; using NSubstitute.ExceptionExtensions; using Xunit; @@ -16,7 +16,7 @@ namespace Azure.Mcp.Tools.AppService.Tests.Commands.Webapp; [Trait("Command", "WebappGet")] -public class WebappGetCommandTests : CommandUnitTestsBase +public class WebappGetCommandTests : SubscriptionCommandUnitTestsBase { [Theory] [InlineData("sub123", null, null)]