diff --git a/.vscode/cspell.json b/.vscode/cspell.json
index a801e7bfd7..3f1732943d 100644
--- a/.vscode/cspell.json
+++ b/.vscode/cspell.json
@@ -368,6 +368,7 @@
"chilecentral",
"cicd",
"cloudarchitect",
+ "cloudhealth",
"codegen",
"codeium",
"codeowners",
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 6b08f9f8ce..a265d88a6a 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -11,7 +11,7 @@
-
+
@@ -25,6 +25,7 @@
+
@@ -94,7 +95,7 @@
-
+
diff --git a/eng/tools/ToolDescriptionEvaluator/prompts/prompts.json b/eng/tools/ToolDescriptionEvaluator/prompts/prompts.json
index 8d68ca1c56..c415d871d2 100644
--- a/eng/tools/ToolDescriptionEvaluator/prompts/prompts.json
+++ b/eng/tools/ToolDescriptionEvaluator/prompts/prompts.json
@@ -548,8 +548,13 @@
"monitor_activitylog_list": [
"List the activity logs of the last month for "
],
- "monitor_healthmodels_entity_get": [
- "Show me the health status of entity using the health model "
+ "monitor_healthmodels_get": [
+ "Show me the health model in resource group ",
+ "Get the Azure Monitor health model named "
+ ],
+ "monitor_healthmodels_list": [
+ "List the Azure Monitor health models in my subscription",
+ "What health models are in resource group ?"
],
"monitor_metrics_definitions": [
"Get metric definitions for from the namespace",
diff --git a/servers/Azure.Mcp.Server/KNOWN-ISSUES.md b/servers/Azure.Mcp.Server/KNOWN-ISSUES.md
index 59d9002ba7..41a5e5f604 100644
--- a/servers/Azure.Mcp.Server/KNOWN-ISSUES.md
+++ b/servers/Azure.Mcp.Server/KNOWN-ISSUES.md
@@ -11,7 +11,6 @@ The following tools are not supported when the Azure MCP Server is running in HT
| Namespace | Impacted Tools |
|-----------|----------------|
-| monitor | `azmcp_monitor_healthmodels_entity_gethealth` |
| communication | `azmcp_communication_sms_send`, `azmcp_communication_email_send` |
| confidentialledger | `azmcp_confidentialledger_entries_append`, `azmcp_confidentialledger_entries_get` |
diff --git a/servers/Azure.Mcp.Server/README.md b/servers/Azure.Mcp.Server/README.md
index a045da48aa..f7d3ad1efd 100644
--- a/servers/Azure.Mcp.Server/README.md
+++ b/servers/Azure.Mcp.Server/README.md
@@ -1116,6 +1116,8 @@ Example prompts that generate Azure CLI commands:
### 📊 Azure Monitor
* "Query my Log Analytics workspace"
+* "List my Azure Monitor Health Models"
+* "Get details for my Azure Monitor Health Model 'my-health-model'"
### 🧭 Azure Monitor Instrumentation (under Azure Monitor)
@@ -1255,7 +1257,7 @@ The Azure MCP Server provides tools for interacting with **44+ Azure service are
- 🗃️ **Azure Managed Lustre** - High-performance Lustre filesystem operations
- 🏪 **Azure Marketplace** - Product discovery
- 🔄 **Azure Migrate** - Platform Landing Zone generation and modification guidance
-- 📈 **Azure Monitor** - Logging, metrics, health monitoring, and instrumentation onboarding/migration workflow for local applications
+- 📈 **Azure Monitor** - Logging, metrics, health models, health monitoring, and instrumentation onboarding/migration workflow for local applications
- ⚖️ **Azure Policy** - Policies set to enforce organizational standards
- ⚙️ **Azure Native ISV Services** - Third-party integrations
- 🛡️ **Azure Quick Review CLI** - Compliance scanning
diff --git a/servers/Azure.Mcp.Server/changelog-entries/1782800143580.yaml b/servers/Azure.Mcp.Server/changelog-entries/1782800143580.yaml
new file mode 100644
index 0000000000..1887cf5d72
--- /dev/null
+++ b/servers/Azure.Mcp.Server/changelog-entries/1782800143580.yaml
@@ -0,0 +1,3 @@
+changes:
+ - section: "Features Added"
+ description: "Replaced the broken 'monitor healthmodels entity get' command with 'monitor healthmodels list' and 'monitor healthmodels get', using the Azure Health Models 2026-05-01-preview API (Microsoft.CloudHealth) via the Azure.ResourceManager.CloudHealth SDK."
diff --git a/servers/Azure.Mcp.Server/docs/azmcp-commands.md b/servers/Azure.Mcp.Server/docs/azmcp-commands.md
index cee0a848b5..0bad39ca90 100644
--- a/servers/Azure.Mcp.Server/docs/azmcp-commands.md
+++ b/servers/Azure.Mcp.Server/docs/azmcp-commands.md
@@ -3065,12 +3065,16 @@ azmcp monitor workspace log query --subscription \
#### Health Models
```bash
-# Get the health of an entity
+# List health models in a subscription (optionally scoped to a resource group)
# ❌ Destructive | ✅ Idempotent | ❌ OpenWorld | ✅ ReadOnly | ❌ Secret | ❌ LocalRequired
-azmcp monitor healthmodels entity get --subscription \
- --resource-group \
- --health-model \
- --entity
+azmcp monitor healthmodels list --subscription \
+ [--resource-group ]
+
+# Get (show) a single health model by name
+# ❌ Destructive | ✅ Idempotent | ❌ OpenWorld | ✅ ReadOnly | ❌ Secret | ❌ LocalRequired
+azmcp monitor healthmodels get --subscription \
+ --resource-group \
+ --health-model
```
#### Metrics
diff --git a/servers/Azure.Mcp.Server/docs/e2eTestPrompts.md b/servers/Azure.Mcp.Server/docs/e2eTestPrompts.md
index 0c61109223..6b5527188d 100644
--- a/servers/Azure.Mcp.Server/docs/e2eTestPrompts.md
+++ b/servers/Azure.Mcp.Server/docs/e2eTestPrompts.md
@@ -768,7 +768,9 @@ This file contains prompts used for end-to-end testing to ensure each tool is in
| Tool Name | Test Prompt |
|:----------|:----------|
| monitor_activitylog_list | List the activity logs of the last month for |
-| monitor_healthmodels_entity_get | Show me the health status of entity using the health model |
+| monitor_healthmodels_get | Show me the health model in resource group |
+| monitor_healthmodels_list | List the Azure Monitor health models in my subscription |
+| monitor_healthmodels_list | What health models are in resource group ? |
| monitor_instrumentation_get-learning-resource | Get the onboarding learning resource at path |
| monitor_instrumentation_get-learning-resource | Show me the content of the Azure Monitor onboarding learning resource at path |
| monitor_instrumentation_get-learning-resource | Get the content of the Azure Monitor learning resource file at path |
diff --git a/servers/Azure.Mcp.Server/src/Resources/consolidated-tools.json b/servers/Azure.Mcp.Server/src/Resources/consolidated-tools.json
index ae8c622d34..d459804e74 100644
--- a/servers/Azure.Mcp.Server/src/Resources/consolidated-tools.json
+++ b/servers/Azure.Mcp.Server/src/Resources/consolidated-tools.json
@@ -400,7 +400,8 @@
"datadog_monitoredresources_list",
"monitor_workspace_list",
"monitor_activitylog_list",
- "monitor_healthmodels_entity_get",
+ "monitor_healthmodels_list",
+ "monitor_healthmodels_get",
"monitor_metrics_definitions",
"monitor_metrics_query",
"monitor_resource_log_query",
diff --git a/tools/Azure.Mcp.Tools.Monitor/src/Azure.Mcp.Tools.Monitor.csproj b/tools/Azure.Mcp.Tools.Monitor/src/Azure.Mcp.Tools.Monitor.csproj
index 6e11d39d8c..3a03b666a8 100644
--- a/tools/Azure.Mcp.Tools.Monitor/src/Azure.Mcp.Tools.Monitor.csproj
+++ b/tools/Azure.Mcp.Tools.Monitor/src/Azure.Mcp.Tools.Monitor.csproj
@@ -27,6 +27,7 @@
+
diff --git a/tools/Azure.Mcp.Tools.Monitor/src/Commands/HealthModels/Entity/EntityGetHealthCommand.cs b/tools/Azure.Mcp.Tools.Monitor/src/Commands/HealthModels/Entity/EntityGetHealthCommand.cs
deleted file mode 100644
index f297cba1c6..0000000000
--- a/tools/Azure.Mcp.Tools.Monitor/src/Commands/HealthModels/Entity/EntityGetHealthCommand.cs
+++ /dev/null
@@ -1,76 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT License.
-
-using System.Text.Json.Nodes;
-using Azure.Mcp.Core.Commands.Subscription;
-using Azure.Mcp.Core.Services.Azure.Subscription;
-using Azure.Mcp.Tools.Monitor.Options;
-using Azure.Mcp.Tools.Monitor.Services;
-using Microsoft.Extensions.Logging;
-using Microsoft.Mcp.Core.Commands;
-using Microsoft.Mcp.Core.Models.Command;
-
-namespace Azure.Mcp.Tools.Monitor.Commands.HealthModels.Entity;
-
-[CommandMetadata(
- Id = "80b23546-a6ac-4f0c-ad70-f51d6dff5543",
- Name = "get",
- Title = "Get the health of an entity in a health model",
- Description = """
- Retrieve the health status of an entity for a given Azure Monitor Health Model. Use this tool ONLY when the user mentions a specific health model name and asks for health status, health events. This provides application-level health monitoring with custom health models, not basic Azure resource availability.
- For basic Azure resource availability status, use Resource Health tool instead `azmcp_resourcehealth_availability-status_get`.
- For querying logs from a Log Analystics workspace, use `azmcp_monitor_workspace_log_query`.
- For querying logs of a specific Azure resource, use `azmcp_monitor_resource_log_query`.
- """,
- Destructive = false,
- Idempotent = true,
- OpenWorld = false,
- ReadOnly = true,
- Secret = false,
- LocalRequired = false)]
-public sealed class EntityGetHealthCommand(ILogger logger, IMonitorHealthModelService healthModelService, ISubscriptionResolver subscriptionResolver)
- : SubscriptionCommand(subscriptionResolver)
-{
- private readonly ILogger _logger = logger;
- private readonly IMonitorHealthModelService _healthModelService = healthModelService;
-
- public override async Task ExecuteAsync(CommandContext context, BaseMonitorHealthModelsOptions options, CancellationToken cancellationToken)
- {
- try
- {
- var result = await _healthModelService.GetEntityHealth(
- options.Entity,
- options.HealthModel,
- options.ResourceGroup,
- options.Subscription!,
- options.AuthMethod,
- options.Tenant,
- options.RetryPolicy,
- cancellationToken);
-
- context.Response.Results = ResponseResult.Create(result, MonitorJsonContext.Default.JsonNode);
- }
- catch (Exception ex)
- {
- _logger.LogError(ex,
- "An exception occurred getting health for entity: {Entity} in healthModel: {HealthModelName}, resourceGroup: {ResourceGroup}, subscription: {Subscription}, authMethod: {AuthMethod}"
- + ", tenant: {Tenant}.",
- options.Entity,
- options.HealthModel,
- options.ResourceGroup,
- options.Subscription,
- options.AuthMethod,
- options.Tenant);
- HandleException(context, ex);
- }
-
- return context.Response;
- }
-
- protected override string GetErrorMessage(Exception ex) => ex switch
- {
- KeyNotFoundException => $"Entity or health model not found. Please check the entity ID, health model name, and resource group.",
- ArgumentException argEx => $"Invalid argument: {argEx.Message}",
- _ => base.GetErrorMessage(ex)
- };
-}
diff --git a/tools/Azure.Mcp.Tools.Monitor/src/Commands/HealthModels/HealthModelGetCommand.cs b/tools/Azure.Mcp.Tools.Monitor/src/Commands/HealthModels/HealthModelGetCommand.cs
new file mode 100644
index 0000000000..1fdb3d0d26
--- /dev/null
+++ b/tools/Azure.Mcp.Tools.Monitor/src/Commands/HealthModels/HealthModelGetCommand.cs
@@ -0,0 +1,56 @@
+// 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.Monitor.Models.HealthModels;
+using Azure.Mcp.Tools.Monitor.Options.HealthModels;
+using Azure.Mcp.Tools.Monitor.Services;
+using Microsoft.Mcp.Core.Commands;
+using Microsoft.Mcp.Core.Models.Command;
+
+namespace Azure.Mcp.Tools.Monitor.Commands.HealthModels;
+
+[CommandMetadata(
+ Id = "1e5a6b2c-0d4e-4f8a-9b3c-7a2d8e1f6b0d",
+ Name = "get",
+ Title = "Get an Azure Monitor Health Model",
+ Description = """
+ Get (show/view) a single, specific Azure Monitor Health Model by name, within a resource group and subscription.
+ Returns the model's metadata and if available, the health state (e.g. Healthy, Degraded, Unhealthy, Unknown) or null if it cannot be provided.
+ """,
+ Destructive = false,
+ Idempotent = true,
+ OpenWorld = false,
+ ReadOnly = true,
+ Secret = false,
+ LocalRequired = false)]
+public sealed class HealthModelGetCommand(IMonitorHealthModelService healthModelService, ISubscriptionResolver subscriptionResolver)
+ : SubscriptionCommand(subscriptionResolver)
+{
+ private readonly IMonitorHealthModelService _healthModelService = healthModelService;
+
+ public override async Task ExecuteAsync(CommandContext context, HealthModelGetOptions options, CancellationToken cancellationToken)
+ {
+ try
+ {
+ var model = await _healthModelService.GetHealthModel(
+ options.Subscription!,
+ options.ResourceGroup,
+ options.HealthModel,
+ options.Tenant,
+ options.RetryPolicy,
+ cancellationToken);
+
+ context.Response.Results = ResponseResult.Create(new(model), MonitorJsonContext.Default.HealthModelGetCommandResult);
+ }
+ catch (Exception ex)
+ {
+ HandleException(context, ex);
+ }
+
+ return context.Response;
+ }
+
+ public sealed record HealthModelGetCommandResult(HealthModelDetail HealthModel);
+}
diff --git a/tools/Azure.Mcp.Tools.Monitor/src/Commands/HealthModels/HealthModelListCommand.cs b/tools/Azure.Mcp.Tools.Monitor/src/Commands/HealthModels/HealthModelListCommand.cs
new file mode 100644
index 0000000000..dde64be399
--- /dev/null
+++ b/tools/Azure.Mcp.Tools.Monitor/src/Commands/HealthModels/HealthModelListCommand.cs
@@ -0,0 +1,53 @@
+// 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.Monitor.Models.HealthModels;
+using Azure.Mcp.Tools.Monitor.Options.HealthModels;
+using Azure.Mcp.Tools.Monitor.Services;
+using Microsoft.Mcp.Core.Commands;
+using Microsoft.Mcp.Core.Models.Command;
+
+namespace Azure.Mcp.Tools.Monitor.Commands.HealthModels;
+
+[CommandMetadata(
+ Id = "9d9d2f3a-6f9d-4a3a-8d7f-2c0b6a1f5e4c",
+ Name = "list",
+ Title = "List Azure Monitor Health Models",
+ Description = """
+ List (find/show/enumerate) Azure Monitor Health Models (Microsoft.CloudHealth/healthmodels) in a subscription, or scoped to a specific resource group.
+ Returns a summary of each health model, including its name, resource group, and location.
+ """,
+ Destructive = false,
+ Idempotent = true,
+ OpenWorld = false,
+ ReadOnly = true,
+ Secret = false,
+ LocalRequired = false)]
+public sealed class HealthModelListCommand(IMonitorHealthModelService healthModelService, ISubscriptionResolver subscriptionResolver)
+ : SubscriptionCommand>(subscriptionResolver)
+{
+ private readonly IMonitorHealthModelService _healthModelService = healthModelService;
+
+ public override async Task ExecuteAsync(CommandContext context, HealthModelListOptions options, CancellationToken cancellationToken)
+ {
+ try
+ {
+ var models = await _healthModelService.ListHealthModels(
+ options.Subscription!,
+ options.ResourceGroup,
+ options.Tenant,
+ options.RetryPolicy,
+ cancellationToken);
+
+ context.Response.Results = ResponseResult.Create(models, MonitorJsonContext.Default.ListHealthModelSummary);
+ }
+ catch (Exception ex)
+ {
+ HandleException(context, ex);
+ }
+
+ return context.Response;
+ }
+}
diff --git a/tools/Azure.Mcp.Tools.Monitor/src/Commands/MonitorJsonContext.cs b/tools/Azure.Mcp.Tools.Monitor/src/Commands/MonitorJsonContext.cs
index 14d1a8fec4..3f0e59a315 100644
--- a/tools/Azure.Mcp.Tools.Monitor/src/Commands/MonitorJsonContext.cs
+++ b/tools/Azure.Mcp.Tools.Monitor/src/Commands/MonitorJsonContext.cs
@@ -5,6 +5,7 @@
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using Azure.Mcp.Tools.Monitor.Commands.ActivityLog;
+using Azure.Mcp.Tools.Monitor.Commands.HealthModels;
using Azure.Mcp.Tools.Monitor.Commands.Instrumentation;
using Azure.Mcp.Tools.Monitor.Commands.Metrics;
using Azure.Mcp.Tools.Monitor.Commands.Table;
@@ -12,6 +13,7 @@
using Azure.Mcp.Tools.Monitor.Commands.WebTests;
using Azure.Mcp.Tools.Monitor.Commands.Workspace;
using Azure.Mcp.Tools.Monitor.Models.ActivityLog;
+using Azure.Mcp.Tools.Monitor.Models.HealthModels;
namespace Azure.Mcp.Tools.Monitor.Commands;
@@ -21,6 +23,11 @@ namespace Azure.Mcp.Tools.Monitor.Commands;
[JsonSerializable(typeof(ActivityLogListResponse))]
[JsonSerializable(typeof(ActivityLogLocalizableString))]
[JsonSerializable(typeof(GetLearningResourceCommand.GetLearningResourceCommandResult))]
+[JsonSerializable(typeof(HealthModelDetail))]
+[JsonSerializable(typeof(HealthModelGetCommand.HealthModelGetCommandResult))]
+[JsonSerializable(typeof(HealthModelIdentity))]
+[JsonSerializable(typeof(HealthModelSummary))]
+[JsonSerializable(typeof(List))]
[JsonSerializable(typeof(List))]
[JsonSerializable(typeof(MetricsDefinitionsCommand.MetricsDefinitionsCommandResult))]
[JsonSerializable(typeof(MetricsDefinitionsCommand.MetricsDefinitionsCommandResult))]
diff --git a/tools/Azure.Mcp.Tools.Monitor/src/Models/HealthModels/HealthModelDetail.cs b/tools/Azure.Mcp.Tools.Monitor/src/Models/HealthModels/HealthModelDetail.cs
new file mode 100644
index 0000000000..87eaf52dfc
--- /dev/null
+++ b/tools/Azure.Mcp.Tools.Monitor/src/Models/HealthModels/HealthModelDetail.cs
@@ -0,0 +1,22 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Text.Json.Serialization;
+
+namespace Azure.Mcp.Tools.Monitor.Models.HealthModels;
+
+///
+/// Azure Monitor Health Model with its identity and tags and a health state.
+/// is null when the root entity's health cannot be retrieved.
+///
+public sealed class HealthModelDetail : HealthModelSummary
+{
+ [JsonPropertyName("healthState")]
+ public string? HealthState { get; set; }
+
+ [JsonPropertyName("identity")]
+ public HealthModelIdentity? Identity { get; set; }
+
+ [JsonPropertyName("tags")]
+ public IDictionary? Tags { get; set; }
+}
diff --git a/tools/Azure.Mcp.Tools.Monitor/src/Models/HealthModels/HealthModelIdentity.cs b/tools/Azure.Mcp.Tools.Monitor/src/Models/HealthModels/HealthModelIdentity.cs
new file mode 100644
index 0000000000..0fdeffc370
--- /dev/null
+++ b/tools/Azure.Mcp.Tools.Monitor/src/Models/HealthModels/HealthModelIdentity.cs
@@ -0,0 +1,24 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Text.Json.Serialization;
+
+namespace Azure.Mcp.Tools.Monitor.Models.HealthModels;
+
+///
+/// Managed identity configuration of an Azure Monitor Health Model.
+///
+public sealed class HealthModelIdentity
+{
+ [JsonPropertyName("type")]
+ public string? Type { get; set; }
+
+ [JsonPropertyName("principalId")]
+ public string? PrincipalId { get; set; }
+
+ [JsonPropertyName("tenantId")]
+ public string? TenantId { get; set; }
+
+ [JsonPropertyName("userAssignedIdentities")]
+ public IReadOnlyList? UserAssignedIdentities { get; set; }
+}
diff --git a/tools/Azure.Mcp.Tools.Monitor/src/Models/HealthModels/HealthModelSummary.cs b/tools/Azure.Mcp.Tools.Monitor/src/Models/HealthModels/HealthModelSummary.cs
new file mode 100644
index 0000000000..3a9e8e0946
--- /dev/null
+++ b/tools/Azure.Mcp.Tools.Monitor/src/Models/HealthModels/HealthModelSummary.cs
@@ -0,0 +1,27 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Text.Json.Serialization;
+
+namespace Azure.Mcp.Tools.Monitor.Models.HealthModels;
+
+///
+/// Reduced projection of an Azure Monitor Health Model.
+///
+public class HealthModelSummary
+{
+ [JsonPropertyName("id")]
+ public string? Id { get; set; }
+
+ [JsonPropertyName("name")]
+ public string? Name { get; set; }
+
+ [JsonPropertyName("resourceGroup")]
+ public string? ResourceGroup { get; set; }
+
+ [JsonPropertyName("location")]
+ public string? Location { get; set; }
+
+ [JsonPropertyName("provisioningState")]
+ public string? ProvisioningState { get; set; }
+}
diff --git a/tools/Azure.Mcp.Tools.Monitor/src/MonitorSetup.cs b/tools/Azure.Mcp.Tools.Monitor/src/MonitorSetup.cs
index 2e434e4d98..732c4d6c1f 100644
--- a/tools/Azure.Mcp.Tools.Monitor/src/MonitorSetup.cs
+++ b/tools/Azure.Mcp.Tools.Monitor/src/MonitorSetup.cs
@@ -2,7 +2,7 @@
// Licensed under the MIT License.
using Azure.Mcp.Tools.Monitor.Commands.ActivityLog;
-using Azure.Mcp.Tools.Monitor.Commands.HealthModels.Entity;
+using Azure.Mcp.Tools.Monitor.Commands.HealthModels;
using Azure.Mcp.Tools.Monitor.Commands.Instrumentation;
using Azure.Mcp.Tools.Monitor.Commands.Log;
using Azure.Mcp.Tools.Monitor.Commands.Metrics;
@@ -78,7 +78,8 @@ public void ConfigureServices(IServiceCollection services)
services.AddSingleton();
- services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
@@ -139,13 +140,11 @@ public CommandGroup RegisterCommands(IServiceProvider serviceProvider)
monitorTableType.AddCommand(serviceProvider);
- var health = new CommandGroup("healthmodels", "Azure Monitor Health Models operations - Commands for working with Azure Monitor Health Models.");
+ var health = new CommandGroup("healthmodels", "Azure Monitor Health Models operations - Commands for listing and retrieving Azure Monitor Health Models (Microsoft.CloudHealth/healthmodels).");
monitor.AddSubGroup(health);
- var entity = new CommandGroup("entity", "Entity operations - Commands for working with entities in Azure Monitor Health Models.");
- health.AddSubGroup(entity);
-
- entity.AddCommand(serviceProvider);
+ health.AddCommand(serviceProvider);
+ health.AddCommand(serviceProvider);
// Create Metrics command group and register commands
var metrics = new CommandGroup("metrics", "Azure Monitor metrics operations - Commands for querying and analyzing Azure Monitor metrics.");
diff --git a/tools/Azure.Mcp.Tools.Monitor/src/Options/BaseMonitorHealthModelsOptions.cs b/tools/Azure.Mcp.Tools.Monitor/src/Options/HealthModels/HealthModelGetOptions.cs
similarity index 57%
rename from tools/Azure.Mcp.Tools.Monitor/src/Options/BaseMonitorHealthModelsOptions.cs
rename to tools/Azure.Mcp.Tools.Monitor/src/Options/HealthModels/HealthModelGetOptions.cs
index d89ff5c86d..5603a4783e 100644
--- a/tools/Azure.Mcp.Tools.Monitor/src/Options/BaseMonitorHealthModelsOptions.cs
+++ b/tools/Azure.Mcp.Tools.Monitor/src/Options/HealthModels/HealthModelGetOptions.cs
@@ -2,17 +2,13 @@
// Licensed under the MIT License.
using Azure.Mcp.Core.Options;
-using Microsoft.Mcp.Core.Models;
using Microsoft.Mcp.Core.Options;
-namespace Azure.Mcp.Tools.Monitor.Options;
+namespace Azure.Mcp.Tools.Monitor.Options.HealthModels;
-public sealed class BaseMonitorHealthModelsOptions : ISubscriptionOption
+public sealed class HealthModelGetOptions : ISubscriptionOption
{
- [Option(Description = "The entity to get health for.")]
- public required string Entity { get; set; }
-
- [Option(Description = "The name of the health model for which to get the health.")]
+ [Option(Description = "The name of the health model to retrieve.")]
public required string HealthModel { get; set; }
[Option(Description = OptionDescriptions.Tenant)]
@@ -26,7 +22,4 @@ public sealed class BaseMonitorHealthModelsOptions : ISubscriptionOption
[OptionContainer(Prefix = "retry")]
public RetryPolicyOptions? RetryPolicy { get; set; }
-
- [Option(Description = OptionDescriptions.AuthMethod)]
- public AuthMethod? AuthMethod { get; set; }
}
diff --git a/tools/Azure.Mcp.Tools.Monitor/src/Options/HealthModels/HealthModelListOptions.cs b/tools/Azure.Mcp.Tools.Monitor/src/Options/HealthModels/HealthModelListOptions.cs
new file mode 100644
index 0000000000..8c51a2b1c1
--- /dev/null
+++ b/tools/Azure.Mcp.Tools.Monitor/src/Options/HealthModels/HealthModelListOptions.cs
@@ -0,0 +1,22 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Azure.Mcp.Core.Options;
+using Microsoft.Mcp.Core.Options;
+
+namespace Azure.Mcp.Tools.Monitor.Options.HealthModels;
+
+public sealed class HealthModelListOptions : ISubscriptionOption
+{
+ [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.Monitor/src/Services/IMonitorHealthModelService.cs b/tools/Azure.Mcp.Tools.Monitor/src/Services/IMonitorHealthModelService.cs
index 46ff9baa0b..379ef9e462 100644
--- a/tools/Azure.Mcp.Tools.Monitor/src/Services/IMonitorHealthModelService.cs
+++ b/tools/Azure.Mcp.Tools.Monitor/src/Services/IMonitorHealthModelService.cs
@@ -1,8 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
-using System.Text.Json.Nodes;
-using Microsoft.Mcp.Core.Models;
+using Azure.Mcp.Tools.Monitor.Models.HealthModels;
using Microsoft.Mcp.Core.Options;
namespace Azure.Mcp.Tools.Monitor.Services;
@@ -10,26 +9,36 @@ namespace Azure.Mcp.Tools.Monitor.Services;
public interface IMonitorHealthModelService
{
///
- /// Gets the health of an entity from a health model
+ /// Lists Azure Monitor Health Models in a subscription or resource group. Returns a summary projection.
///
- /// The entity to get health for
- /// The health model name
- /// The resource group name containing the health model
- /// Subscription ID or name
- /// Optional authentication method
- /// Optional tenant ID for cross-tenant operations
- /// Optional retry policy for the operation
- /// A cancellation token
- /// Entity health information
- /// When authentication fails
- /// When the service request fails
- Task GetEntityHealth(
- string entity,
- string healthModelName,
- string resourceGroup,
+ /// Subscription ID or name.
+ /// Optional resource group to scope the listing.
+ /// Optional tenant ID.
+ /// Optional retry policy.
+ /// A cancellation token.
+ /// List health models.
+ Task> ListHealthModels(
+ string subscription,
+ string? resourceGroup = null,
+ string? tenant = null,
+ RetryPolicyOptions? retryPolicy = null,
+ CancellationToken cancellationToken = default);
+
+ ///
+ /// Gets a single Azure Monitor Health Model by name.
+ ///
+ /// Subscription ID or name.
+ /// The resource group containing the health model.
+ /// The health model name.
+ /// Optional tenant ID.
+ /// Optional retry policy.
+ /// A cancellation token.
+ /// The health model resource.
+ Task GetHealthModel(
string subscription,
- AuthMethod? authMethod = null,
- string? tenantId = null,
+ string resourceGroup,
+ string healthModelName,
+ string? tenant = null,
RetryPolicyOptions? retryPolicy = null,
CancellationToken cancellationToken = default);
}
diff --git a/tools/Azure.Mcp.Tools.Monitor/src/Services/MonitorHealthModelService.cs b/tools/Azure.Mcp.Tools.Monitor/src/Services/MonitorHealthModelService.cs
index 28ca9db20f..57f2c1984a 100644
--- a/tools/Azure.Mcp.Tools.Monitor/src/Services/MonitorHealthModelService.cs
+++ b/tools/Azure.Mcp.Tools.Monitor/src/Services/MonitorHealthModelService.cs
@@ -1,139 +1,130 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
-using System.Text.Json.Nodes;
-using Azure.Core;
+using System.Linq;
+using Azure;
using Azure.Mcp.Core.Services.Azure;
+using Azure.Mcp.Core.Services.Azure.Subscription;
using Azure.Mcp.Core.Services.Azure.Tenant;
-using Microsoft.Mcp.Core.Models;
+using Azure.Mcp.Tools.Monitor.Models.HealthModels;
+using Azure.ResourceManager.CloudHealth;
+using Azure.ResourceManager.Models;
+using Microsoft.Extensions.Logging;
using Microsoft.Mcp.Core.Options;
-using Microsoft.Mcp.Core.Services.Azure.Authentication;
namespace Azure.Mcp.Tools.Monitor.Services;
-public class MonitorHealthModelService(ITenantService tenantService, IHttpClientFactory httpClientFactory)
+public class MonitorHealthModelService(ISubscriptionService subscriptionService, ITenantService tenantService, ILogger logger)
: BaseAzureService(tenantService), IMonitorHealthModelService
{
- private const string ApiVersion = "2023-10-01-preview";
- private readonly IHttpClientFactory _httpClientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory));
- private readonly ITenantService _tenantService = tenantService ?? throw new ArgumentNullException(nameof(tenantService));
+ private readonly ISubscriptionService _subscriptionService = subscriptionService ?? throw new ArgumentNullException(nameof(subscriptionService));
+ private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger));
- ///
- /// Retrieves the health information for a specific entity in a health model.
- ///
- /// The identifier of the entity whose health is being queried.
- /// The name of the health model to query.
- /// The name of the resource group containing the health model.
- /// The Azure subscription ID containing the resource group.
- /// Optional. The authentication method to use for the request.
- /// Optional. The Azure tenant ID for authentication.
- /// Optional. Policy parameters for retrying failed requests.
- /// A cancellation token.
- /// A JSON node containing the entity's health information.
- /// Thrown when required parameters are missing or invalid.
- /// Thrown when parsing the health response fails.
- public async Task GetEntityHealth(
- string entity,
- string healthModelName,
- string resourceGroupName,
+ internal static HealthModelSummary ToSummary(HealthModelData data) =>
+ new()
+ {
+ Id = data.Id?.ToString(),
+ Name = data.Name,
+ ResourceGroup = data.Id?.ResourceGroupName,
+ Location = data.Location.ToString(),
+ ProvisioningState = data.HealthModelProvisioningState?.ToString(),
+ };
+
+ public async Task> ListHealthModels(
string subscription,
- AuthMethod? authMethod = null,
- string? tenantId = null,
+ string? resourceGroup = null,
+ string? tenant = null,
RetryPolicyOptions? retryPolicy = null,
CancellationToken cancellationToken = default)
{
- ValidateRequiredParameters((nameof(entity), entity), (nameof(healthModelName), healthModelName), (nameof(resourceGroupName), resourceGroupName), (nameof(subscription), subscription));
-
- string dataplaneEndpoint = await GetDataplaneEndpointAsync(subscription, resourceGroupName, healthModelName, cancellationToken);
- string entityHealthUrl = $"{dataplaneEndpoint}api/entities/{entity}/history";
-
- string healthResponseString = await GetDataplaneResponseAsync(entityHealthUrl, cancellationToken);
- return JsonNode.Parse(healthResponseString) ?? throw new Exception("Failed to parse health response to JSON.");
- }
-
- private async Task GetDataplaneResponseAsync(string url, CancellationToken cancellationToken)
- {
- string dataplaneToken = await GetDataplaneTokenAsync(cancellationToken);
- using var request = new HttpRequestMessage(HttpMethod.Get, url);
- request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", dataplaneToken);
-
- var client = _httpClientFactory.CreateClient();
- HttpResponseMessage healthResponse = await client.SendAsync(request, cancellationToken);
- healthResponse.EnsureSuccessStatusCode();
-
- string healthResponseString = await healthResponse.Content.ReadAsStringAsync(cancellationToken);
- return healthResponseString;
- }
-
- private async Task GetDataplaneEndpointAsync(string subscriptionId, string resourceGroupName, string healthModelName, CancellationToken cancellationToken)
- {
- string token = await GetControlPlaneTokenAsync(cancellationToken);
- string healthModelUrl = $"{GetManagementEndpoint()}/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.CloudHealth/healthmodels/{healthModelName}?api-version={ApiVersion}";
-
- using var request = new HttpRequestMessage(HttpMethod.Get, healthModelUrl);
- request.Headers.Authorization = new("Bearer", token);
+ ValidateRequiredParameters((nameof(subscription), subscription));
- var client = _httpClientFactory.CreateClient();
- HttpResponseMessage response = await client.SendAsync(request, cancellationToken);
- response.EnsureSuccessStatusCode();
- string responseString = await response.Content.ReadAsStringAsync(cancellationToken);
+ var subscriptionResource = await _subscriptionService.GetSubscription(subscription, tenant, retryPolicy, cancellationToken);
+ var results = new List();
- string dataplaneEndpoint = GetDataplaneEndpoint(responseString);
- return dataplaneEndpoint;
- }
-
- private static string GetDataplaneEndpoint(string jsonResponse)
- {
- try
+ if (string.IsNullOrEmpty(resourceGroup))
{
- JsonNode? json = JsonNode.Parse(jsonResponse);
- string? dataplaneEndpoint = json?["properties"]?["dataplaneEndpoint"]?.GetValue();
- if (string.IsNullOrEmpty(dataplaneEndpoint))
+ await foreach (var model in subscriptionResource.GetHealthModelsAsync(cancellationToken))
{
- throw new Exception("Dataplane endpoint is null or empty in the response.");
+ results.Add(ToSummary(model.Data));
}
-
- return dataplaneEndpoint!;
}
- catch (Exception ex)
+ else
{
- string errorMessage = $"Error parsing dataplane endpoint: {ex.Message}";
- throw new Exception(errorMessage, ex);
+ var resourceGroupResource = await subscriptionResource.GetResourceGroupAsync(resourceGroup, cancellationToken);
+ await foreach (var model in resourceGroupResource.Value.GetHealthModels().GetAllAsync(cancellationToken: cancellationToken))
+ {
+ results.Add(ToSummary(model.Data));
+ }
}
- }
- private async Task GetControlPlaneTokenAsync(CancellationToken cancellationToken)
- {
- return (await GetArmAccessTokenAsync(null, cancellationToken)).Token;
+ return results;
}
- private async Task GetDataplaneTokenAsync(CancellationToken cancellationToken)
+ internal static HealthModelDetail ToDetail(HealthModelData data, string? healthState) =>
+ new()
+ {
+ Id = data.Id?.ToString(),
+ Name = data.Name,
+ ResourceGroup = data.Id?.ResourceGroupName,
+ Location = data.Location.ToString(),
+ ProvisioningState = data.HealthModelProvisioningState?.ToString(),
+ HealthState = healthState,
+ Identity = ToIdentity(data.Identity),
+ Tags = data.Tags,
+ };
+
+ internal static HealthModelIdentity? ToIdentity(ManagedServiceIdentity? identity)
{
- TokenCredential credential = await GetCredential(cancellationToken);
- AccessToken accessToken = await credential.GetTokenAsync(
- new([GetHealthModelsDataApiScope()]),
- cancellationToken);
+ if (identity is null)
+ {
+ return null;
+ }
- return accessToken.Token;
+ return new HealthModelIdentity
+ {
+ Type = identity.ManagedServiceIdentityType.ToString(),
+ PrincipalId = identity.PrincipalId?.ToString(),
+ TenantId = identity.TenantId?.ToString(),
+ UserAssignedIdentities = identity.UserAssignedIdentities?
+ .Keys.Select(id => id.ToString()).ToList(),
+ };
}
- private string GetManagementEndpoint()
+ public async Task GetHealthModel(
+ string subscription,
+ string resourceGroup,
+ string healthModelName,
+ string? tenant = null,
+ RetryPolicyOptions? retryPolicy = null,
+ CancellationToken cancellationToken = default)
{
- return _tenantService.CloudConfiguration.ArmEnvironment.Endpoint.ToString().TrimEnd('/');
+ ValidateRequiredParameters(
+ (nameof(subscription), subscription),
+ (nameof(resourceGroup), resourceGroup),
+ (nameof(healthModelName), healthModelName));
+
+ var subscriptionResource = await _subscriptionService.GetSubscription(subscription, tenant, retryPolicy, cancellationToken);
+ var resourceGroupResource = await subscriptionResource.GetResourceGroupAsync(resourceGroup, cancellationToken);
+ var model = await resourceGroupResource.Value.GetHealthModels().GetAsync(healthModelName, cancellationToken);
+ var healthState = await TryGetRootHealthStateAsync(model.Value, healthModelName, cancellationToken);
+ return ToDetail(model.Value.Data, healthState);
}
- private string GetHealthModelsDataApiScope()
+ private async Task TryGetRootHealthStateAsync(HealthModelResource model, string rootEntityName, CancellationToken cancellationToken)
{
- return _tenantService.CloudConfiguration.CloudType switch
+ try
{
- AzureCloudConfiguration.AzureCloud.AzurePublicCloud =>
- "https://data.healthmodels.azure.com/.default",
- AzureCloudConfiguration.AzureCloud.AzureChinaCloud =>
- "https://data.healthmodels.azure.cn/.default",
- AzureCloudConfiguration.AzureCloud.AzureUSGovernmentCloud =>
- "https://data.healthmodels.azure.us/.default",
- _ =>
- "https://data.healthmodels.azure.com/.default"
- };
+ var entity = await model.GetHealthModelEntityAsync(rootEntityName, cancellationToken);
+ return entity.Value.Data.Properties?.HealthState?.ToString();
+ }
+ catch (RequestFailedException ex)
+ {
+ _logger.LogWarning(ex,
+ "Could not resolve root-entity health for health model '{HealthModel}'; returning null healthState. Error: {Message}",
+ rootEntityName,
+ ex.Message);
+ return null;
+ }
}
}
diff --git a/tools/Azure.Mcp.Tools.Monitor/tests/Azure.Mcp.Tools.Monitor.Tests/HealthModels/Entity/EntityGetHealthCommandTests.cs b/tools/Azure.Mcp.Tools.Monitor/tests/Azure.Mcp.Tools.Monitor.Tests/HealthModels/Entity/EntityGetHealthCommandTests.cs
deleted file mode 100644
index b96e299a24..0000000000
--- a/tools/Azure.Mcp.Tools.Monitor/tests/Azure.Mcp.Tools.Monitor.Tests/HealthModels/Entity/EntityGetHealthCommandTests.cs
+++ /dev/null
@@ -1,291 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT License.
-
-using System.Net;
-using System.Text.Json.Nodes;
-using Azure.Mcp.Tests.Commands;
-using Azure.Mcp.Tools.Monitor.Commands.HealthModels.Entity;
-using Azure.Mcp.Tools.Monitor.Services;
-using Microsoft.Mcp.Core.Models;
-using Microsoft.Mcp.Core.Options;
-using NSubstitute;
-using NSubstitute.ExceptionExtensions;
-using Xunit;
-
-namespace Azure.Mcp.Tools.Monitor.Tests.HealthModels.Entity;
-
-public class EntityGetHealthCommandTests : SubscriptionCommandUnitTestsBase
-{
- // Sample test data
- private const string TestEntity = "entity123";
- private const string TestHealthModel = "healthModel1";
- private const string TestResourceGroup = "resourceGroup1";
- private const string TestSubscription = "sub123";
- private const string TestTenant = "tenant123";
-
- [Fact]
- public async Task ExecuteAsync_WithValidParameters_ReturnsEntityHealth()
- {
- // Arrange
- JsonNode mockResponse = new JsonObject([new("entityId", "entity123"), new("health", "Healthy"), new("timestamp", "2023-05-01T12:00:00Z")]);
-
- Service.GetEntityHealth(
- TestEntity,
- TestHealthModel,
- TestResourceGroup,
- TestSubscription,
- Arg.Any(),
- TestTenant,
- Arg.Any(),
- Arg.Any())
- .Returns(mockResponse);
-
- // Act
- var result = await ExecuteCommandAsync(
- "--entity", TestEntity,
- "--health-model", TestHealthModel,
- "--resource-group", TestResourceGroup,
- "--subscription", TestSubscription,
- "--tenant", TestTenant);
-
- // Assert
- Assert.NotNull(result);
- Assert.Equal(HttpStatusCode.OK, result.Status);
- Assert.NotNull(result.Results);
-
- await Service.Received(1).GetEntityHealth(
- TestEntity,
- TestHealthModel,
- TestResourceGroup,
- TestSubscription,
- Arg.Any(),
- TestTenant,
- Arg.Any(),
- Arg.Any());
- }
-
- [Fact]
- public async Task ExecuteAsync_WithMissingRequiredParameters_ReturnsBadRequest()
- {
- // Arrange & Act - missing entity parameter
- var result = await ExecuteCommandAsync(
- "--health-model", TestHealthModel,
- "--resource-group", TestResourceGroup,
- "--subscription", TestSubscription);
-
- // Assert
- Assert.NotNull(result);
- Assert.Equal(HttpStatusCode.BadRequest, result.Status);
- Assert.Contains("required", result.Message, StringComparison.OrdinalIgnoreCase);
-
- // Verify service was not called
- await Service.DidNotReceive().GetEntityHealth(
- Arg.Any(),
- Arg.Any(),
- Arg.Any(),
- Arg.Any(),
- Arg.Any(),
- Arg.Any(),
- Arg.Any(),
- Arg.Any());
- }
-
- [Fact]
- public async Task ExecuteAsync_EntityNotFound_ReturnsNotFound()
- {
- // Arrange
- Service.GetEntityHealth(
- TestEntity,
- TestHealthModel,
- TestResourceGroup,
- TestSubscription,
- Arg.Any(),
- Arg.Any(),
- Arg.Any(),
- Arg.Any())
- .ThrowsAsync(new KeyNotFoundException("Entity not found"));
-
- // Act
- var result = await ExecuteCommandAsync(
- "--entity", TestEntity,
- "--health-model", TestHealthModel,
- "--resource-group", TestResourceGroup,
- "--subscription", TestSubscription);
-
- // Assert
- Assert.NotNull(result);
- Assert.Equal(HttpStatusCode.NotFound, result.Status);
- Assert.Contains("not found", result.Message, StringComparison.OrdinalIgnoreCase);
-
- await Service.Received(1).GetEntityHealth(
- TestEntity,
- TestHealthModel,
- TestResourceGroup,
- TestSubscription,
- Arg.Any(),
- Arg.Any(),
- Arg.Any(),
- Arg.Any());
- }
-
- [Fact]
- public async Task ExecuteAsync_InvalidArgument_ReturnsBadRequest()
- {
- // Arrange
- Service.GetEntityHealth(
- TestEntity,
- TestHealthModel,
- TestResourceGroup,
- TestSubscription,
- Arg.Any(),
- Arg.Any(),
- Arg.Any(),
- Arg.Any())
- .ThrowsAsync(new ArgumentException("Invalid health model format"));
-
- // Act
- var result = await ExecuteCommandAsync(
- "--entity", TestEntity,
- "--health-model", TestHealthModel,
- "--resource-group", TestResourceGroup,
- "--subscription", TestSubscription);
-
- // Assert
- Assert.NotNull(result);
- Assert.Equal(HttpStatusCode.BadRequest, result.Status);
- Assert.Contains("Invalid argument", result.Message);
-
- await Service.Received(1).GetEntityHealth(
- TestEntity,
- TestHealthModel,
- TestResourceGroup,
- TestSubscription,
- Arg.Any(),
- Arg.Any(),
- Arg.Any(),
- Arg.Any());
- }
-
- [Fact]
- public async Task ExecuteAsync_GeneralException_ReturnsInternalServerError()
- {
- // Arrange
- var expectedError = "Unexpected error occurred";
- Service.GetEntityHealth(
- TestEntity,
- TestHealthModel,
- TestResourceGroup,
- TestSubscription,
- Arg.Any(),
- Arg.Any(),
- Arg.Any(),
- Arg.Any())
- .ThrowsAsync(new Exception(expectedError));
-
- // Act
- var result = await ExecuteCommandAsync(
- "--entity", TestEntity,
- "--health-model", TestHealthModel,
- "--resource-group", TestResourceGroup,
- "--subscription", TestSubscription);
-
- // Assert
- Assert.NotNull(result);
- Assert.Equal(HttpStatusCode.InternalServerError, result.Status);
- Assert.Contains(expectedError, result.Message);
-
- await Service.Received(1).GetEntityHealth(
- TestEntity,
- TestHealthModel,
- TestResourceGroup,
- TestSubscription,
- Arg.Any(),
- Arg.Any(),
- Arg.Any(),
- Arg.Any());
- }
-
- [Fact]
- public async Task ExecuteAsync_WithAuthMethod_PassesAuthMethodToService()
- {
- // Arrange
- var mockResponse = new JsonObject([new("entityId", "entity123"), new("health", "Healthy")]);
- var authMethod = AuthMethod.Credential.ToString().ToLowerInvariant();
-
- Service.GetEntityHealth(
- TestEntity,
- TestHealthModel,
- TestResourceGroup,
- TestSubscription,
- Arg.Is(AuthMethod.Credential),
- Arg.Any(),
- Arg.Any(),
- Arg.Any())
- .Returns(mockResponse);
-
- // Act
- var result = await ExecuteCommandAsync(
- "--entity", TestEntity,
- "--health-model", TestHealthModel,
- "--resource-group", TestResourceGroup,
- "--subscription", TestSubscription,
- "--auth-method", authMethod);
-
- // Assert
- Assert.NotNull(result);
- Assert.Equal(HttpStatusCode.OK, result.Status);
-
- await Service.Received(1).GetEntityHealth(
- TestEntity,
- TestHealthModel,
- TestResourceGroup,
- TestSubscription,
- Arg.Is(AuthMethod.Credential),
- Arg.Any(),
- Arg.Any(),
- Arg.Any());
- }
-
- [Fact]
- public async Task ExecuteAsync_WithRetryPolicy_PassesRetryPolicyToService()
- {
- // Arrange
- var mockResponse = new JsonObject([new("entityId", "entity123"), new("health", "Healthy")]);
- const double RetryDelay = 3;
- const int MaxRetries = 5;
-
- Service.GetEntityHealth(
- TestEntity,
- TestHealthModel,
- TestResourceGroup,
- TestSubscription,
- Arg.Any(),
- Arg.Any(),
- Arg.Is(r => r.DelaySeconds == RetryDelay && r.MaxRetries == MaxRetries),
- Arg.Any())
- .Returns(mockResponse);
-
- // Act
- var result = await ExecuteCommandAsync(
- "--entity", TestEntity,
- "--health-model", TestHealthModel,
- "--resource-group", TestResourceGroup,
- "--subscription", TestSubscription,
- "--retry-delay", RetryDelay.ToString(),
- "--retry-max-retries", MaxRetries.ToString());
-
- // Assert
- Assert.NotNull(result);
- Assert.Equal(HttpStatusCode.OK, result.Status);
-
- await Service.Received(1).GetEntityHealth(
- TestEntity,
- TestHealthModel,
- TestResourceGroup,
- TestSubscription,
- Arg.Any(),
- Arg.Any(),
- Arg.Is(r => r.DelaySeconds == RetryDelay && r.MaxRetries == MaxRetries),
- Arg.Any());
- }
-}
diff --git a/tools/Azure.Mcp.Tools.Monitor/tests/Azure.Mcp.Tools.Monitor.Tests/HealthModels/HealthModelGetCommandTests.cs b/tools/Azure.Mcp.Tools.Monitor/tests/Azure.Mcp.Tools.Monitor.Tests/HealthModels/HealthModelGetCommandTests.cs
new file mode 100644
index 0000000000..7a291dbb4c
--- /dev/null
+++ b/tools/Azure.Mcp.Tools.Monitor/tests/Azure.Mcp.Tools.Monitor.Tests/HealthModels/HealthModelGetCommandTests.cs
@@ -0,0 +1,45 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Net;
+using Azure.Mcp.Tests.Commands;
+using Azure.Mcp.Tools.Monitor.Commands;
+using Azure.Mcp.Tools.Monitor.Commands.HealthModels;
+using Azure.Mcp.Tools.Monitor.Models.HealthModels;
+using Azure.Mcp.Tools.Monitor.Services;
+using Microsoft.Mcp.Core.Options;
+using NSubstitute;
+using Xunit;
+
+namespace Azure.Mcp.Tools.Monitor.Tests.HealthModels;
+
+public class HealthModelGetCommandTests : SubscriptionCommandUnitTestsBase
+{
+ private const string TestSubscription = "sub123";
+ private const string TestResourceGroup = "rg1";
+ private const string TestHealthModel = "hm-one";
+
+ [Fact]
+ public async Task ExecuteAsync_ReturnsHealthModel_WhenItExists()
+ {
+ Service.GetHealthModel(TestSubscription, TestResourceGroup, TestHealthModel, Arg.Any(), Arg.Any(), Arg.Any())
+ .Returns(new HealthModelDetail
+ {
+ Id = null,
+ Name = "hm-one",
+ ResourceGroup = "rg1",
+ Location = "eastus2",
+ ProvisioningState = "Succeeded",
+ HealthState = "Healthy",
+ Tags = null,
+ });
+
+ var response = await ExecuteCommandAsync("--subscription", TestSubscription, "--resource-group", TestResourceGroup, "--health-model", TestHealthModel);
+
+ Assert.Equal(HttpStatusCode.OK, response.Status);
+ var result = ValidateAndDeserializeResponse(response, MonitorJsonContext.Default.HealthModelGetCommandResult);
+ Assert.Equal("hm-one", result!.HealthModel.Name);
+ Assert.Equal("Succeeded", result.HealthModel.ProvisioningState);
+ Assert.Equal("Healthy", result.HealthModel.HealthState);
+ }
+}
diff --git a/tools/Azure.Mcp.Tools.Monitor/tests/Azure.Mcp.Tools.Monitor.Tests/HealthModels/HealthModelListCommandTests.cs b/tools/Azure.Mcp.Tools.Monitor/tests/Azure.Mcp.Tools.Monitor.Tests/HealthModels/HealthModelListCommandTests.cs
new file mode 100644
index 0000000000..c13213a3be
--- /dev/null
+++ b/tools/Azure.Mcp.Tools.Monitor/tests/Azure.Mcp.Tools.Monitor.Tests/HealthModels/HealthModelListCommandTests.cs
@@ -0,0 +1,64 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Net;
+using Azure.Mcp.Tests.Commands;
+using Azure.Mcp.Tools.Monitor.Commands;
+using Azure.Mcp.Tools.Monitor.Commands.HealthModels;
+using Azure.Mcp.Tools.Monitor.Models.HealthModels;
+using Azure.Mcp.Tools.Monitor.Services;
+using Microsoft.Mcp.Core.Options;
+using NSubstitute;
+using Xunit;
+
+namespace Azure.Mcp.Tools.Monitor.Tests.HealthModels;
+
+public class HealthModelListCommandTests : SubscriptionCommandUnitTestsBase
+{
+ private const string TestSubscription = "sub123";
+ private const string TestResourceGroup = "rg1";
+
+ [Fact]
+ public async Task ExecuteAsync_ReturnsLeanSummaries_WhenTheyExist()
+ {
+ List summaries =
+ [
+ new() { Id = "/subscriptions/sub123/resourceGroups/rg1/providers/Microsoft.CloudHealth/healthmodels/hm-one", Name = "hm-one", ResourceGroup = "rg1", Location = "eastus2", ProvisioningState = "Succeeded" },
+ new() { Id = "/subscriptions/sub123/resourceGroups/rg2/providers/Microsoft.CloudHealth/healthmodels/hm-two", Name = "hm-two", ResourceGroup = "rg2", Location = "westus2", ProvisioningState = "Provisioning" },
+ ];
+ Service.ListHealthModels(TestSubscription, Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())
+ .Returns(summaries);
+
+ var response = await ExecuteCommandAsync("--subscription", TestSubscription);
+
+ Assert.Equal(HttpStatusCode.OK, response.Status);
+ var result = ValidateAndDeserializeResponse(response, MonitorJsonContext.Default.ListHealthModelSummary);
+ Assert.Equal(2, result.Count);
+ Assert.Equal("hm-one", result[0].Name);
+ Assert.Equal("rg1", result[0].ResourceGroup);
+ Assert.Equal("eastus2", result[0].Location);
+ Assert.Equal("Succeeded", result[0].ProvisioningState);
+ Assert.Equal("Provisioning", result[1].ProvisioningState);
+
+ // Lean by construction: each serialized item carries ONLY the summary keys (no ARM envelope).
+ var json = System.Text.Json.JsonSerializer.Serialize(result, MonitorJsonContext.Default.ListHealthModelSummary);
+ var array = System.Text.Json.Nodes.JsonNode.Parse(json)!.AsArray();
+ foreach (var item in array)
+ {
+ var keys = ((System.Text.Json.Nodes.JsonObject)item!).Select(kv => kv.Key).OrderBy(k => k, StringComparer.Ordinal).ToArray();
+ Assert.Equal(["id", "location", "name", "provisioningState", "resourceGroup"], keys);
+ }
+ }
+
+ [Fact]
+ public async Task ExecuteAsync_ForwardsResourceGroup_WhenProvided()
+ {
+ Service.ListHealthModels(Arg.Any(), Arg.Is(TestResourceGroup), Arg.Any(), Arg.Any(), Arg.Any())
+ .Returns([]);
+
+ var response = await ExecuteCommandAsync("--subscription", TestSubscription, "--resource-group", TestResourceGroup);
+
+ Assert.Equal(HttpStatusCode.OK, response.Status);
+ await Service.Received(1).ListHealthModels(Arg.Any(), Arg.Is(TestResourceGroup), Arg.Any(), Arg.Any(), Arg.Any());
+ }
+}