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
@@ -0,0 +1,3 @@
changes:
- section: "Breaking Changes"
description: "Removed unused parameters from Resource Health tools."
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,11 @@
<EmbeddedResource Include="**\Resources\*.json" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\core\Azure.Mcp.Core\src\Azure.Mcp.Core.csproj" />
<ProjectReference Include="$(RepoRoot)core\Azure.Mcp.Core\src\Azure.Mcp.Core.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Azure.ResourceManager" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
<PackageReference Include="ModelContextProtocol" />
<PackageReference Include="System.CommandLine" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@
// Licensed under the MIT License.

using System.Net;
using Azure.Mcp.Core.Services.Azure.Subscription;
using Azure.Mcp.Tools.ResourceHealth.Options.AvailabilityStatus;
using Azure.Mcp.Tools.ResourceHealth.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.ResourceHealth.Commands.AvailabilityStatus;

Expand All @@ -26,36 +25,14 @@ namespace Azure.Mcp.Tools.ResourceHealth.Commands.AvailabilityStatus;
ReadOnly = true,
Secret = false,
LocalRequired = false)]
public sealed class AvailabilityStatusGetCommand(ILogger<AvailabilityStatusGetCommand> logger, IResourceHealthService resourceHealthService)
: BaseResourceHealthCommand<AvailabilityStatusGetOptions>()
public sealed class AvailabilityStatusGetCommand(ILogger<AvailabilityStatusGetCommand> logger, IResourceHealthService resourceHealthService, ISubscriptionResolver subscriptionResolver)
: BaseResourceHealthCommand<AvailabilityStatusGetOptions, AvailabilityStatusGetCommand.AvailabilityStatusGetCommandResult>(subscriptionResolver)
{
private readonly ILogger<AvailabilityStatusGetCommand> _logger = logger;
private readonly IResourceHealthService _resourceHealthService = resourceHealthService;

protected override void RegisterOptions(Command command)
public override async Task<CommandResponse> ExecuteAsync(CommandContext context, AvailabilityStatusGetOptions options, CancellationToken cancellationToken)
{
base.RegisterOptions(command);
command.Options.Add(ResourceHealthOptionDefinitions.ResourceId.AsOptional());
command.Options.Add(OptionDefinitions.Common.ResourceGroup.AsOptional());
}

protected override AvailabilityStatusGetOptions BindOptions(ParseResult parseResult)
{
var options = base.BindOptions(parseResult);
options.ResourceId = parseResult.GetValueOrDefault<string>(ResourceHealthOptionDefinitions.ResourceId.Name);
options.ResourceGroup ??= parseResult.GetValueOrDefault<string>(OptionDefinitions.Common.ResourceGroup.Name);
return options;
}

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

var options = BindOptions(parseResult);

try
{
List<Models.AvailabilityStatus> statuses;
Expand Down Expand Up @@ -119,5 +96,5 @@ public override async Task<CommandResponse> ExecuteAsync(CommandContext context,
_ => base.GetStatusCode(ex)
};

internal record AvailabilityStatusGetCommandResult(List<Models.AvailabilityStatus> Statuses);
public sealed record AvailabilityStatusGetCommandResult(List<Models.AvailabilityStatus> Statuses);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,15 @@
using System.Diagnostics.CodeAnalysis;
using System.Net;
using Azure.Mcp.Core.Commands.Subscription;
using Azure.Mcp.Tools.ResourceHealth.Options;
using Azure.Mcp.Core.Options;
using Azure.Mcp.Core.Services.Azure.Subscription;
using Azure.Mcp.Tools.ResourceHealth.Services;
using Microsoft.Mcp.Core.Commands;

namespace Azure.Mcp.Tools.ResourceHealth.Commands;

public abstract class BaseResourceHealthCommand<
[DynamicallyAccessedMembers(TrimAnnotations.CommandAnnotations)] T>
: SubscriptionCommand<T>
where T : BaseResourceHealthOptions, new()
public abstract class BaseResourceHealthCommand<[DynamicallyAccessedMembers(TrimAnnotations.CommandAnnotations)] TOptions, TResult>(ISubscriptionResolver subscriptionResolver)
: SubscriptionCommand<TOptions, TResult>(subscriptionResolver) where TOptions : class, ISubscriptionOption
{
protected override string GetErrorMessage(Exception ex) => ex switch
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using Azure.Mcp.Core.Services.Azure.Subscription;
using Azure.Mcp.Tools.ResourceHealth.Options.ServiceHealthEvents;
using Azure.Mcp.Tools.ResourceHealth.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.ResourceHealth.Commands.ServiceHealthEvents;
Expand All @@ -24,69 +24,34 @@ namespace Azure.Mcp.Tools.ResourceHealth.Commands.ServiceHealthEvents;
ReadOnly = true,
Secret = false,
LocalRequired = false)]
public sealed class ServiceHealthEventsListCommand(ILogger<ServiceHealthEventsListCommand> logger, IResourceHealthService resourceHealthService)
: BaseResourceHealthCommand<ServiceHealthEventsListOptions>()
public sealed class ServiceHealthEventsListCommand(ILogger<ServiceHealthEventsListCommand> logger, IResourceHealthService resourceHealthService, ISubscriptionResolver subscriptionResolver)
: BaseResourceHealthCommand<ServiceHealthEventsListOptions, ServiceHealthEventsListCommand.ServiceHealthEventsListCommandResult>(subscriptionResolver)
{
private readonly ILogger<ServiceHealthEventsListCommand> _logger = logger;
private readonly IResourceHealthService _resourceHealthService = resourceHealthService;

private static readonly HashSet<string> validEventTypes = new(StringComparer.OrdinalIgnoreCase) { "ServiceIssue", "PlannedMaintenance", "HealthAdvisory", "Security" };
private static readonly HashSet<string> validStatuses = new(StringComparer.OrdinalIgnoreCase) { "Active", "Resolved" };
private static readonly HashSet<string> s_validEventTypes = new(StringComparer.OrdinalIgnoreCase) { "ServiceIssue", "PlannedMaintenance", "HealthAdvisory", "Security" };
private static readonly HashSet<string> s_validStatuses = new(StringComparer.OrdinalIgnoreCase) { "Active", "Resolved" };

protected override void RegisterOptions(Command command)
public override void ValidateOptions(ServiceHealthEventsListOptions options, ValidationResult validationResult)
{
base.RegisterOptions(command);
command.Options.Add(ResourceHealthOptionDefinitions.EventType);
command.Options.Add(ResourceHealthOptionDefinitions.Status);
command.Options.Add(ResourceHealthOptionDefinitions.TrackingId);
command.Options.Add(ResourceHealthOptionDefinitions.Filter);
command.Options.Add(ResourceHealthOptionDefinitions.QueryStartTime);
command.Options.Add(ResourceHealthOptionDefinitions.QueryEndTime);
base.ValidateOptions(options, validationResult);

// Add validators for enum values
command.Validators.Add(commandResult =>
// Validate event-type enum values
if (!string.IsNullOrEmpty(options.EventType) && !s_validEventTypes.Contains(options.EventType))
{
// Validate event-type enum values
if (commandResult.TryGetValue(ResourceHealthOptionDefinitions.EventType, out var eventType) && !string.IsNullOrEmpty(eventType))
{
if (!validEventTypes.Contains(eventType))
{
commandResult.AddError($"Invalid event-type '{eventType}'. Valid values are: {string.Join(", ", validEventTypes)}");
}
}

// Validate status enum values
if (commandResult.TryGetValue(ResourceHealthOptionDefinitions.Status, out var status) && !string.IsNullOrEmpty(status))
{
if (!validStatuses.Contains(status))
{
commandResult.AddError($"Invalid status '{status}'. Valid values are: {string.Join(", ", validStatuses)}");
}
}
});
}

protected override ServiceHealthEventsListOptions BindOptions(ParseResult parseResult)
{
var options = base.BindOptions(parseResult);
options.EventType = parseResult.GetValueOrDefault(ResourceHealthOptionDefinitions.EventType);
options.Status = parseResult.GetValueOrDefault(ResourceHealthOptionDefinitions.Status);
options.TrackingId = parseResult.GetValueOrDefault(ResourceHealthOptionDefinitions.TrackingId);
options.Filter = parseResult.GetValueOrDefault(ResourceHealthOptionDefinitions.Filter);
options.QueryStartTime = parseResult.GetValueOrDefault(ResourceHealthOptionDefinitions.QueryStartTime);
options.QueryEndTime = parseResult.GetValueOrDefault(ResourceHealthOptionDefinitions.QueryEndTime);
return options;
}
validationResult.Errors.Add($"Invalid event-type '{options.EventType}'. Valid values are: {string.Join(", ", s_validEventTypes)}");
}

public override async Task<CommandResponse> ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken)
{
if (!Validate(parseResult.CommandResult, context.Response).IsValid)
// Validate status enum values
if (!string.IsNullOrEmpty(options.Status) && !s_validStatuses.Contains(options.Status))
{
return context.Response;
validationResult.Errors.Add($"Invalid status '{options.Status}'. Valid values are: {string.Join(", ", s_validStatuses)}");
}
}

var options = BindOptions(parseResult);

public override async Task<CommandResponse> ExecuteAsync(CommandContext context, ServiceHealthEventsListOptions options, CancellationToken cancellationToken)
{
try
{
var events = await _resourceHealthService.ListServiceHealthEventsAsync(
Expand All @@ -112,5 +77,5 @@ public override async Task<CommandResponse> ExecuteAsync(CommandContext context,
return context.Response;
}

internal record ServiceHealthEventsListCommandResult(List<Models.ServiceHealthEvent> Events);
public sealed record ServiceHealthEventsListCommandResult(List<Models.ServiceHealthEvent> Events);
}
4 changes: 0 additions & 4 deletions tools/Azure.Mcp.Tools.ResourceHealth/src/GlobalUsings.cs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,12 +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.ResourceHealth.Options.AvailabilityStatus;

public class AvailabilityStatusGetOptions : BaseResourceHealthOptions
public sealed class AvailabilityStatusGetOptions : ISubscriptionOption
{
[JsonPropertyName(ResourceHealthOptionDefinitions.ResourceIdName)]
[Option(Description = "The Azure resource ID to get health status for (e.g., /subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Compute/virtualMachines/{vm}).", Name = "resourceId")]
public string? ResourceId { get; set; }

[Option(Description = OptionDescriptions.ResourceGroup)]
public string? ResourceGroup { get; set; }

[Option(Description = OptionDescriptions.Subscription)]
public string? Subscription { get; set; }

[Option(Description = OptionDescriptions.Tenant)]
public string? Tenant { get; set; }

[OptionContainer(Prefix = "retry")]
public RetryPolicyOptions? RetryPolicy { get; set; }
Comment thread
alzimmermsft marked this conversation as resolved.
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,25 +1,43 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using Azure.Mcp.Core.Options;
using Microsoft.Mcp.Core.Options;

namespace Azure.Mcp.Tools.ResourceHealth.Options.ServiceHealthEvents;

public class ServiceHealthEventsListOptions : BaseResourceHealthOptions
public sealed class ServiceHealthEventsListOptions : ISubscriptionOption
{
/// <summary> Filter by event type (ServiceIssue, PlannedMaintenance, HealthAdvisory, Security). </summary>
[Option(Description = "Filter by event type (ServiceIssue, PlannedMaintenance, HealthAdvisory, Security). If not specified, all event types are included.")]
public string? EventType { get; set; }

/// <summary> Filter by status (Active, Resolved). </summary>
[Option(Description = "Filter by status (Active, Resolved). If not specified, all statuses are included.")]
public string? Status { get; set; }

/// <summary> Filter by tracking ID to get a specific service health event. </summary>
[Option(Description = "Filter by tracking ID to get a specific service health event.")]
public string? TrackingId { get; set; }

/// <summary> Additional OData filter expression to apply to the service health events query. </summary>
[Option(Description = "Additional OData filter expression to apply to the service health events query.")]
public string? Filter { get; set; }

/// <summary> Start time for the query in ISO 8601 format (e.g., 2024-01-01T00:00:00Z). </summary>
[Option(Description = "Start time for the query in ISO 8601 format (e.g., 2024-01-01T00:00:00Z). Events from this time onwards will be included.")]
public string? QueryStartTime { get; set; }

/// <summary> End time for the query in ISO 8601 format (e.g., 2024-01-31T23:59:59Z). </summary>
[Option(Description = "End time for the query in ISO 8601 format (e.g., 2024-01-31T23:59:59Z). Events up to this time will be included.")]
public string? QueryEndTime { get; set; }

[Option(Description = OptionDescriptions.Subscription)]
public string? Subscription { get; set; }

[Option(Description = OptionDescriptions.Tenant)]
public string? Tenant { get; set; }

[OptionContainer(Prefix = "retry")]
public RetryPolicyOptions? RetryPolicy { get; set; }
Comment thread
alzimmermsft marked this conversation as resolved.
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,7 @@ public class ResourceHealthSetup : IAreaSetup
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IResourceHealthService, ResourceHealthService>();

services.AddSingleton<AvailabilityStatusGetCommand>();

services.AddSingleton<ServiceHealthEventsListCommand>();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,19 @@
using Azure.Mcp.Core.Services.Azure.Tenant;
using Azure.Mcp.Tools.ResourceHealth.Models;
using Azure.Mcp.Tools.ResourceHealth.Models.Internal;
using Microsoft.Extensions.Logging;
using Microsoft.Mcp.Core.Options;

namespace Azure.Mcp.Tools.ResourceHealth.Services;

public class ResourceHealthService(
ISubscriptionService subscriptionService,
ITenantService tenantService,
IHttpClientFactory httpClientFactory,
ILogger<ResourceHealthService> logger)
IHttpClientFactory httpClientFactory)
: BaseAzureService(tenantService), IResourceHealthService
{
private readonly ISubscriptionService _subscriptionService = subscriptionService ?? throw new ArgumentNullException(nameof(subscriptionService));
private readonly ITenantService _tenantService = tenantService ?? throw new ArgumentNullException(nameof(tenantService));
private readonly IHttpClientFactory _httpClientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory));
private readonly ILogger<ResourceHealthService> _logger = logger ?? throw new ArgumentNullException(nameof(logger));

private const string ResourceHealthApiVersion = "2025-05-01";

Expand Down
Loading