From 79c87a90ec3cd3e897122c3e230adb6bb00a4851 Mon Sep 17 00:00:00 2001 From: Khalil Sayed Date: Thu, 8 Oct 2026 22:31:02 +0000 Subject: [PATCH 1/5] Add Device Provisioning Service get tool Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: ae3e90da-ad11-496c-ac8d-5e77c48af218 --- .github/CODEOWNERS | 6 + .vscode/cspell.json | 1 + Microsoft.Mcp.slnx | 7 + .../Azure.Mcp.Server/Azure.Mcp.Server.slnx | 7 + servers/Azure.Mcp.Server/README.md | 7 + ...halil-device-provisioning-service-get.yaml | 3 + .../Azure.Mcp.Server/docs/azmcp-commands.md | 11 + .../Azure.Mcp.Server/docs/e2eTestPrompts.md | 8 + servers/Azure.Mcp.Server/src/Program.cs | 1 + .../src/Resources/consolidated-tools.json | 33 +++ .../cspell.yaml | 9 + .../Azure.Mcp.Tools.DeviceProvisioning.csproj | 17 ++ .../Commands/DeviceProvisioningJsonContext.cs | 21 ++ .../DeviceProvisioningServiceGetCommand.cs | 114 ++++++++++ .../src/DeviceProvisioningSetup.cs | 40 ++++ .../Models/DeviceProvisioningProperties.cs | 25 +++ .../DeviceProvisioningServiceDescription.cs | 23 ++ .../src/Models/LinkedIoTHubDescription.cs | 11 + .../src/Models/LinkedIoTHubProperties.cs | 15 ++ .../DeviceProvisioningServiceGetOptions.cs | 22 ++ .../src/Services/DeviceProvisioningService.cs | 94 ++++++++ .../Services/IDeviceProvisioningService.cs | 17 ++ ....Mcp.Tools.DeviceProvisioning.Tests.csproj | 19 ++ .../DeviceProvisioningCommandTests.cs | 46 ++++ .../DeviceProvisioningPropertiesTests.cs | 55 +++++ ...eviceProvisioningServiceGetCommandTests.cs | 200 ++++++++++++++++++ .../assets.json | 7 + .../tests/test-resources-post.ps1 | 17 ++ .../tests/test-resources.bicep | 39 ++++ 29 files changed, 875 insertions(+) create mode 100644 servers/Azure.Mcp.Server/changelog-entries/khalil-device-provisioning-service-get.yaml create mode 100644 tools/Azure.Mcp.Tools.DeviceProvisioning/cspell.yaml create mode 100644 tools/Azure.Mcp.Tools.DeviceProvisioning/src/Azure.Mcp.Tools.DeviceProvisioning.csproj create mode 100644 tools/Azure.Mcp.Tools.DeviceProvisioning/src/Commands/DeviceProvisioningJsonContext.cs create mode 100644 tools/Azure.Mcp.Tools.DeviceProvisioning/src/Commands/Service/DeviceProvisioningServiceGetCommand.cs create mode 100644 tools/Azure.Mcp.Tools.DeviceProvisioning/src/DeviceProvisioningSetup.cs create mode 100644 tools/Azure.Mcp.Tools.DeviceProvisioning/src/Models/DeviceProvisioningProperties.cs create mode 100644 tools/Azure.Mcp.Tools.DeviceProvisioning/src/Models/DeviceProvisioningServiceDescription.cs create mode 100644 tools/Azure.Mcp.Tools.DeviceProvisioning/src/Models/LinkedIoTHubDescription.cs create mode 100644 tools/Azure.Mcp.Tools.DeviceProvisioning/src/Models/LinkedIoTHubProperties.cs create mode 100644 tools/Azure.Mcp.Tools.DeviceProvisioning/src/Options/Service/DeviceProvisioningServiceGetOptions.cs create mode 100644 tools/Azure.Mcp.Tools.DeviceProvisioning/src/Services/DeviceProvisioningService.cs create mode 100644 tools/Azure.Mcp.Tools.DeviceProvisioning/src/Services/IDeviceProvisioningService.cs create mode 100644 tools/Azure.Mcp.Tools.DeviceProvisioning/tests/Azure.Mcp.Tools.DeviceProvisioning.Tests/Azure.Mcp.Tools.DeviceProvisioning.Tests.csproj create mode 100644 tools/Azure.Mcp.Tools.DeviceProvisioning/tests/Azure.Mcp.Tools.DeviceProvisioning.Tests/DeviceProvisioningCommandTests.cs create mode 100644 tools/Azure.Mcp.Tools.DeviceProvisioning/tests/Azure.Mcp.Tools.DeviceProvisioning.Tests/Models/DeviceProvisioningPropertiesTests.cs create mode 100644 tools/Azure.Mcp.Tools.DeviceProvisioning/tests/Azure.Mcp.Tools.DeviceProvisioning.Tests/Service/DeviceProvisioningServiceGetCommandTests.cs create mode 100644 tools/Azure.Mcp.Tools.DeviceProvisioning/tests/Azure.Mcp.Tools.DeviceProvisioning.Tests/assets.json create mode 100644 tools/Azure.Mcp.Tools.DeviceProvisioning/tests/test-resources-post.ps1 create mode 100644 tools/Azure.Mcp.Tools.DeviceProvisioning/tests/test-resources.bicep diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 9e03ed4f18..395ee5b1e3 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -241,6 +241,12 @@ # ServiceLabel: %tools-Deploy # ServiceOwners: @qianwens @xfz11 @wchigit +# PRLabel: %tools-DeviceProvisioning +/tools/Azure.Mcp.Tools.DeviceProvisioning/ @microsoft/azure-mcp + +# ServiceLabel: %tools-DeviceProvisioning +# ServiceOwners: @microsoft/azure-mcp + # PRLabel: %tools-DeviceRegistry /tools/Azure.Mcp.Tools.DeviceRegistry/ @microsoft/azure-mcp diff --git a/.vscode/cspell.json b/.vscode/cspell.json index d14be171d1..ddf39b26ae 100644 --- a/.vscode/cspell.json +++ b/.vscode/cspell.json @@ -304,6 +304,7 @@ "deploymentid", "deregistering", "devbox", + "deviceprovisioning", "deviceregistry", "dfff", "disasterrecovery", diff --git a/Microsoft.Mcp.slnx b/Microsoft.Mcp.slnx index b45b6a3212..c1b06b555f 100644 --- a/Microsoft.Mcp.slnx +++ b/Microsoft.Mcp.slnx @@ -260,6 +260,13 @@ + + + + + + + diff --git a/servers/Azure.Mcp.Server/Azure.Mcp.Server.slnx b/servers/Azure.Mcp.Server/Azure.Mcp.Server.slnx index 25f381e051..05c2e22783 100644 --- a/servers/Azure.Mcp.Server/Azure.Mcp.Server.slnx +++ b/servers/Azure.Mcp.Server/Azure.Mcp.Server.slnx @@ -187,6 +187,13 @@ + + + + + + + diff --git a/servers/Azure.Mcp.Server/README.md b/servers/Azure.Mcp.Server/README.md index a7720a29f5..94b75af836 100644 --- a/servers/Azure.Mcp.Server/README.md +++ b/servers/Azure.Mcp.Server/README.md @@ -1186,6 +1186,12 @@ For customer-hosted ADME instances, pass the resource application ID or App ID U * "Generate insights about my Azure environment to help me plan a new data analytics platform" * "What insights can you derive about my subscription to help me plan a containerized microservices workload on AKS?" +### 🔧 Azure Device Provisioning Service + +* "Show me Device Provisioning Service 'my-dps' in resource group 'my-resource-group' of my subscription 'my-subscription'" +* "Get the ID scope, allocation policy, endpoints, and linked IoT Hubs for DPS instance 'my-dps'" +* "Show the configuration of DPS instance 'my-dps' without returning connection strings or keys" + ### 🌐 Azure IoT Hub * "Show me IoT Hub 'my-iot-hub' in resource group 'my-resource-group' of my subscription 'my-subscription'" @@ -1438,6 +1444,7 @@ The Azure MCP Server provides tools for interacting with **45+ Azure service are - 🧮 **Azure Data Explorer** - Analytics queries and KQL - 🐬 **Azure Database for MySQL** - MySQL database management - 🐘 **Azure Database for PostgreSQL** - PostgreSQL database management +- 🔧 **Azure Device Provisioning Service** - Device Provisioning Service resource configuration and linked IoT Hub metadata - 🏭 **Azure Device Registry** - Device Registry namespace management - 📊 **Azure Event Grid** - Event routing and management - ⚡ **Azure Event Hubs** - Namespace, event hub, and consumer group management diff --git a/servers/Azure.Mcp.Server/changelog-entries/khalil-device-provisioning-service-get.yaml b/servers/Azure.Mcp.Server/changelog-entries/khalil-device-provisioning-service-get.yaml new file mode 100644 index 0000000000..e0173fcf46 --- /dev/null +++ b/servers/Azure.Mcp.Server/changelog-entries/khalil-device-provisioning-service-get.yaml @@ -0,0 +1,3 @@ +changes: + - section: "Features Added" + description: "Added a read-only Azure Device Provisioning Service tool for retrieving service configuration and linked IoT Hub metadata without exposing connection strings or keys." diff --git a/servers/Azure.Mcp.Server/docs/azmcp-commands.md b/servers/Azure.Mcp.Server/docs/azmcp-commands.md index 16c4853727..05eb1512ab 100644 --- a/servers/Azure.Mcp.Server/docs/azmcp-commands.md +++ b/servers/Azure.Mcp.Server/docs/azmcp-commands.md @@ -3161,6 +3161,17 @@ azmcp insights get --scope tenant \ --nocache ``` +### Azure Device Provisioning Service Operations + +```bash +# Get Azure Device Provisioning Service details by service name in a specific resource group. +# The response excludes linked-hub connection strings, authorization policies, and keys. +# ❌ Destructive | ✅ Idempotent | ❌ OpenWorld | ✅ ReadOnly | ❌ Secret | ❌ LocalRequired +azmcp deviceprovisioning service get --subscription \ + --resource-group \ + --service +``` + ### Azure IoT Hub Operations ```bash diff --git a/servers/Azure.Mcp.Server/docs/e2eTestPrompts.md b/servers/Azure.Mcp.Server/docs/e2eTestPrompts.md index 67e6d52670..72123d6909 100644 --- a/servers/Azure.Mcp.Server/docs/e2eTestPrompts.md +++ b/servers/Azure.Mcp.Server/docs/e2eTestPrompts.md @@ -757,6 +757,14 @@ The `Interaction` column describes whether a prompt can invoke its tool immediat | insights_get | Generate insights about my Azure environment to help me plan a new data analytics platform | none | | insights_get | What insights can you derive about my subscription to help me plan a containerized microservices workload on AKS? | none | +## Azure Device Provisioning Service + +| Tool Name | Test Prompt | Interaction | +|:----------|:------------|:------------| +| deviceprovisioning_service_get | Get details for Device Provisioning Service in resource group | none | +| deviceprovisioning_service_get | Show the configuration of DPS instance in subscription | none | +| deviceprovisioning_service_get | Retrieve the ID scope, allocation policy, endpoints, and linked IoT Hubs for DPS instance | none | + ## Azure IoT Hub | Tool Name | Test Prompt | Interaction | diff --git a/servers/Azure.Mcp.Server/src/Program.cs b/servers/Azure.Mcp.Server/src/Program.cs index 3a5edb6e52..3d2526269c 100644 --- a/servers/Azure.Mcp.Server/src/Program.cs +++ b/servers/Azure.Mcp.Server/src/Program.cs @@ -194,6 +194,7 @@ internal static IAreaSetup[] RegisterAreas() new Azure.Mcp.Tools.AzureTerraform.AzureTerraformSetup(), new Azure.Mcp.Tools.AzureTerraformBestPractices.AzureTerraformBestPracticesSetup(), new Azure.Mcp.Tools.Deploy.DeploySetup(), + new Azure.Mcp.Tools.DeviceProvisioning.DeviceProvisioningSetup(), new Azure.Mcp.Tools.DeviceRegistry.DeviceRegistrySetup(), new Azure.Mcp.Tools.EventGrid.EventGridSetup(), new Azure.Mcp.Tools.Acr.AcrSetup(), diff --git a/servers/Azure.Mcp.Server/src/Resources/consolidated-tools.json b/servers/Azure.Mcp.Server/src/Resources/consolidated-tools.json index 3933ccb6d3..4f8e15b9fc 100644 --- a/servers/Azure.Mcp.Server/src/Resources/consolidated-tools.json +++ b/servers/Azure.Mcp.Server/src/Resources/consolidated-tools.json @@ -2944,6 +2944,39 @@ "eventhubs_eventhub_consumergroup_get" ] }, + { + "name": "get_azure_device_provisioning_service", + "description": "Get details about an Azure Device Provisioning Service instance, including service state, endpoints, ID scope, allocation policy, network access, data residency, and linked IoT Hub metadata. Connection strings, authorization policies, and keys are not returned.", + "toolMetadata": { + "destructive": { + "value": false, + "description": "This tool only retrieves information about an Azure Device Provisioning Service instance without deleting or modifying existing resources." + }, + "idempotent": { + "value": true, + "description": "Calling this tool multiple times with the same parameters returns the current resource details without causing side effects." + }, + "openWorld": { + "value": false, + "description": "This tool interacts only with Azure Resource Manager." + }, + "readOnly": { + "value": true, + "description": "This tool only reads Azure Device Provisioning Service resource information." + }, + "secret": { + "value": false, + "description": "This tool excludes connection strings, authorization policies, and keys from its response." + }, + "localRequired": { + "value": false, + "description": "This tool is available in both local and remote server modes." + } + }, + "mappedToolList": [ + "deviceprovisioning_service_get" + ] + }, { "name": "get_azure_iot_hub", "description": "Get details about an Azure IoT Hub.", diff --git a/tools/Azure.Mcp.Tools.DeviceProvisioning/cspell.yaml b/tools/Azure.Mcp.Tools.DeviceProvisioning/cspell.yaml new file mode 100644 index 0000000000..bcae32a40b --- /dev/null +++ b/tools/Azure.Mcp.Tools.DeviceProvisioning/cspell.yaml @@ -0,0 +1,9 @@ +# Spell-check overrides for this toolset/server. +# Inherits the global config at the repo root. +version: "0.2" +import: + - ../../.vscode/cspell.json +words: + - idscope + - iothubs + - provisioningserviceowner diff --git a/tools/Azure.Mcp.Tools.DeviceProvisioning/src/Azure.Mcp.Tools.DeviceProvisioning.csproj b/tools/Azure.Mcp.Tools.DeviceProvisioning/src/Azure.Mcp.Tools.DeviceProvisioning.csproj new file mode 100644 index 0000000000..ca6bc32dd3 --- /dev/null +++ b/tools/Azure.Mcp.Tools.DeviceProvisioning/src/Azure.Mcp.Tools.DeviceProvisioning.csproj @@ -0,0 +1,17 @@ + + + true + + + + + + + + + + + + + + diff --git a/tools/Azure.Mcp.Tools.DeviceProvisioning/src/Commands/DeviceProvisioningJsonContext.cs b/tools/Azure.Mcp.Tools.DeviceProvisioning/src/Commands/DeviceProvisioningJsonContext.cs new file mode 100644 index 0000000000..b1f3cb4613 --- /dev/null +++ b/tools/Azure.Mcp.Tools.DeviceProvisioning/src/Commands/DeviceProvisioningJsonContext.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Text.Json.Serialization; +using Azure.Mcp.Tools.DeviceProvisioning.Commands.Service; +using Azure.Mcp.Tools.DeviceProvisioning.Models; + +namespace Azure.Mcp.Tools.DeviceProvisioning.Commands; + +[JsonSerializable(typeof(DeviceProvisioningProperties))] +[JsonSerializable(typeof(DeviceProvisioningServiceDescription))] +[JsonSerializable(typeof(LinkedIoTHubDescription))] +[JsonSerializable(typeof(List))] +[JsonSerializable(typeof(DeviceProvisioningServiceGetCommand.DeviceProvisioningServiceGetCommandResult))] +[JsonSourceGenerationOptions( + PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)] +internal sealed partial class DeviceProvisioningJsonContext : JsonSerializerContext +{ +} + diff --git a/tools/Azure.Mcp.Tools.DeviceProvisioning/src/Commands/Service/DeviceProvisioningServiceGetCommand.cs b/tools/Azure.Mcp.Tools.DeviceProvisioning/src/Commands/Service/DeviceProvisioningServiceGetCommand.cs new file mode 100644 index 0000000000..37bd66f2af --- /dev/null +++ b/tools/Azure.Mcp.Tools.DeviceProvisioning/src/Commands/Service/DeviceProvisioningServiceGetCommand.cs @@ -0,0 +1,114 @@ +// 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.DeviceProvisioning.Models; +using Azure.Mcp.Tools.DeviceProvisioning.Options.Service; +using Azure.Mcp.Tools.DeviceProvisioning.Services; +using Microsoft.Extensions.Logging; +using Microsoft.Mcp.Core.Commands; +using Microsoft.Mcp.Core.Models.Command; + +namespace Azure.Mcp.Tools.DeviceProvisioning.Commands.Service; + +[CommandMetadata( + Id = "3bf66226-33b8-438b-9f71-3abe62a6bf6c", + Name = "get", + Title = "Get Device Provisioning Service", + Description = """ + Get an Azure Device Provisioning Service instance by name in a resource group. + Returns resource identity, SKU, service state, endpoints, ID scope, allocation policy, + public network access, data residency, and linked IoT Hub metadata. + Connection strings, authorization policies, and keys are not returned. + """, + OperationPlane = ToolOperationPlane.Control, + Destructive = false, + Idempotent = true, + OpenWorld = false, + ReadOnly = true, + Secret = false, + LocalRequired = false)] +public sealed class DeviceProvisioningServiceGetCommand( + ILogger logger, + IDeviceProvisioningService service, + ISubscriptionResolver subscriptionResolver) + : SubscriptionCommand(subscriptionResolver) +{ + private readonly ILogger _logger = logger; + private readonly IDeviceProvisioningService _service = service; + + public override void ValidateOptions( + DeviceProvisioningServiceGetOptions options, + ValidationResult validationResult) + { + base.ValidateOptions(options, validationResult); + + if (!IsValidServiceName(options.Service)) + { + validationResult.Errors.Add( + "--service must be 3-64 characters long, contain only letters, numbers, or hyphens, and start and end with a letter or number."); + } + } + + public override async Task ExecuteAsync( + CommandContext context, + DeviceProvisioningServiceGetOptions options, + CancellationToken cancellationToken) + { + try + { + var provisioningService = await _service.GetService( + options.Service, + options.ResourceGroup, + options.Subscription!, + options.Tenant, + cancellationToken); + + context.Response.Results = ResponseResult.Create( + new DeviceProvisioningServiceGetCommandResult( + provisioningService, + AreResultsTruncated: false), + DeviceProvisioningJsonContext.Default.DeviceProvisioningServiceGetCommandResult); + } + catch (Exception ex) + { + _logger.LogError( + ex, + "Error getting Device Provisioning Service '{ServiceName}' in resource group '{ResourceGroup}' and subscription '{Subscription}'.", + options.Service, + options.ResourceGroup, + options.Subscription); + HandleException(context, ex); + } + + return context.Response; + } + + public record DeviceProvisioningServiceGetCommandResult( + DeviceProvisioningServiceDescription DeviceProvisioningService, + bool AreResultsTruncated); + + private static bool IsValidServiceName(string value) + { + if (value.Length is < 3 or > 64 || + value[0] == '-' || + value[^1] == '-') + { + return false; + } + + foreach (var ch in value) + { + var isAlphaNumeric = (ch >= 'a' && ch <= 'z') || + (ch >= 'A' && ch <= 'Z') || + (ch >= '0' && ch <= '9'); + if (!isAlphaNumeric && ch != '-') + { + return false; + } + } + + return true; + } +} diff --git a/tools/Azure.Mcp.Tools.DeviceProvisioning/src/DeviceProvisioningSetup.cs b/tools/Azure.Mcp.Tools.DeviceProvisioning/src/DeviceProvisioningSetup.cs new file mode 100644 index 0000000000..223519366a --- /dev/null +++ b/tools/Azure.Mcp.Tools.DeviceProvisioning/src/DeviceProvisioningSetup.cs @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Azure.Mcp.Tools.DeviceProvisioning.Commands.Service; +using Azure.Mcp.Tools.DeviceProvisioning.Services; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Mcp.Core.Areas; +using Microsoft.Mcp.Core.Commands; + +namespace Azure.Mcp.Tools.DeviceProvisioning; + +public class DeviceProvisioningSetup : IAreaSetup +{ + public string Name => "deviceprovisioning"; + + public string Title => "Manage Azure Device Provisioning Service"; + + public void ConfigureServices(IServiceCollection services) + { + services.AddSingleton(); + services.AddSingleton(); + } + + public CommandGroup RegisterCommands(IServiceProvider serviceProvider) + { + var deviceProvisioning = new CommandGroup( + Name, + "Azure Device Provisioning Service operations.", + Title); + + var service = new CommandGroup( + "service", + "Azure Device Provisioning Service resource operations."); + deviceProvisioning.AddSubGroup(service); + service.AddCommand(serviceProvider); + + return deviceProvisioning; + } +} + diff --git a/tools/Azure.Mcp.Tools.DeviceProvisioning/src/Models/DeviceProvisioningProperties.cs b/tools/Azure.Mcp.Tools.DeviceProvisioning/src/Models/DeviceProvisioningProperties.cs new file mode 100644 index 0000000000..7f33a78156 --- /dev/null +++ b/tools/Azure.Mcp.Tools.DeviceProvisioning/src/Models/DeviceProvisioningProperties.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Azure.Mcp.Tools.DeviceProvisioning.Models; + +public class DeviceProvisioningProperties +{ + public string? State { get; set; } + + public string? ProvisioningState { get; set; } + + public string? ServiceOperationsHostName { get; set; } + + public string? DeviceProvisioningHostName { get; set; } + + public string? IdScope { get; set; } + + public string? AllocationPolicy { get; set; } + + public string? PublicNetworkAccess { get; set; } + + public bool? EnableDataResidency { get; set; } + + public List? IotHubs { get; set; } +} diff --git a/tools/Azure.Mcp.Tools.DeviceProvisioning/src/Models/DeviceProvisioningServiceDescription.cs b/tools/Azure.Mcp.Tools.DeviceProvisioning/src/Models/DeviceProvisioningServiceDescription.cs new file mode 100644 index 0000000000..719d5e0810 --- /dev/null +++ b/tools/Azure.Mcp.Tools.DeviceProvisioning/src/Models/DeviceProvisioningServiceDescription.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Azure.Mcp.Tools.DeviceProvisioning.Models; + +public record DeviceProvisioningServiceDescription( + string Id, + string Name, + string Location, + string ResourceGroup, + string SubscriptionId, + string Sku, + long Capacity, + string State, + string ProvisioningState, + string ServiceOperationsHostName, + string DeviceProvisioningHostName, + string IdScope, + string AllocationPolicy, + string PublicNetworkAccess, + bool? EnableDataResidency, + List IotHubs); + diff --git a/tools/Azure.Mcp.Tools.DeviceProvisioning/src/Models/LinkedIoTHubDescription.cs b/tools/Azure.Mcp.Tools.DeviceProvisioning/src/Models/LinkedIoTHubDescription.cs new file mode 100644 index 0000000000..4a1b51307e --- /dev/null +++ b/tools/Azure.Mcp.Tools.DeviceProvisioning/src/Models/LinkedIoTHubDescription.cs @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Azure.Mcp.Tools.DeviceProvisioning.Models; + +public record LinkedIoTHubDescription( + string Name, + string Location, + bool? ApplyAllocationPolicy, + int? AllocationWeight); + diff --git a/tools/Azure.Mcp.Tools.DeviceProvisioning/src/Models/LinkedIoTHubProperties.cs b/tools/Azure.Mcp.Tools.DeviceProvisioning/src/Models/LinkedIoTHubProperties.cs new file mode 100644 index 0000000000..0a18551502 --- /dev/null +++ b/tools/Azure.Mcp.Tools.DeviceProvisioning/src/Models/LinkedIoTHubProperties.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Azure.Mcp.Tools.DeviceProvisioning.Models; + +public class LinkedIoTHubProperties +{ + public string? Name { get; set; } + + public string? Location { get; set; } + + public bool? ApplyAllocationPolicy { get; set; } + + public int? AllocationWeight { get; set; } +} diff --git a/tools/Azure.Mcp.Tools.DeviceProvisioning/src/Options/Service/DeviceProvisioningServiceGetOptions.cs b/tools/Azure.Mcp.Tools.DeviceProvisioning/src/Options/Service/DeviceProvisioningServiceGetOptions.cs new file mode 100644 index 0000000000..e0c2a0ff1c --- /dev/null +++ b/tools/Azure.Mcp.Tools.DeviceProvisioning/src/Options/Service/DeviceProvisioningServiceGetOptions.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.DeviceProvisioning.Options.Service; + +public sealed class DeviceProvisioningServiceGetOptions : ISubscriptionOption +{ + [Option(Description = "The name of the Azure Device Provisioning Service instance.")] + public required string Service { get; set; } + + [Option(Description = OptionDescriptions.ResourceGroup)] + public required string ResourceGroup { get; set; } + + [Option(Description = OptionDescriptions.Subscription)] + public string? Subscription { get; set; } + + [Option(Description = OptionDescriptions.Tenant)] + public string? Tenant { get; set; } +} diff --git a/tools/Azure.Mcp.Tools.DeviceProvisioning/src/Services/DeviceProvisioningService.cs b/tools/Azure.Mcp.Tools.DeviceProvisioning/src/Services/DeviceProvisioningService.cs new file mode 100644 index 0000000000..3aa5cffefe --- /dev/null +++ b/tools/Azure.Mcp.Tools.DeviceProvisioning/src/Services/DeviceProvisioningService.cs @@ -0,0 +1,94 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Azure.Core; +using Azure.Mcp.Core.Services.Azure; +using Azure.Mcp.Tools.DeviceProvisioning.Commands; +using Azure.Mcp.Tools.DeviceProvisioning.Models; +using Azure.ResourceManager.Resources; +using Microsoft.Extensions.Logging; + +namespace Azure.Mcp.Tools.DeviceProvisioning.Services; + +public class DeviceProvisioningService( + IAzureService azureService, + ILogger logger) + : BaseAzureService(azureService), IDeviceProvisioningService +{ + private readonly ILogger _logger = + logger ?? throw new ArgumentNullException(nameof(logger)); + + public async Task GetService( + string serviceName, + string resourceGroup, + string subscription, + string? tenant = null, + CancellationToken cancellationToken = default) + { + ValidateRequiredParameters( + (nameof(subscription), subscription), + (nameof(resourceGroup), resourceGroup), + (nameof(serviceName), serviceName)); + + try + { + var subscriptionResource = await AzureService.GetSubscription( + subscription, + tenant, + cancellationToken: cancellationToken); + var armClient = await CreateArmClientAsync( + tenant, + cancellationToken: cancellationToken); + var serviceResourceId = new ResourceIdentifier( + $"/subscriptions/{subscriptionResource.Data.SubscriptionId}/resourceGroups/{resourceGroup}/providers/Microsoft.Devices/provisioningServices/{serviceName}"); + var service = await armClient + .GetGenericResource(serviceResourceId) + .GetAsync(cancellationToken); + + return ConvertToDescription(service.Value.Data); + } + catch (Exception ex) + { + _logger.LogError( + ex, + "Error retrieving Device Provisioning Service '{ServiceName}' in resource group '{ResourceGroup}' and subscription '{Subscription}'.", + serviceName, + resourceGroup, + subscription); + throw; + } + } + + private static DeviceProvisioningServiceDescription ConvertToDescription( + GenericResourceData service) + { + var properties = service.Properties?.ToObjectFromJson( + DeviceProvisioningJsonContext.Default.DeviceProvisioningProperties); + var linkedHubs = properties?.IotHubs? + .Select(hub => new LinkedIoTHubDescription( + hub.Name ?? string.Empty, + hub.Location ?? string.Empty, + hub.ApplyAllocationPolicy, + hub.AllocationWeight)) + .ToList() ?? []; + + return new DeviceProvisioningServiceDescription( + service.Id.ToString(), + service.Name, + service.Location.ToString(), + service.Id?.ResourceGroupName ?? string.Empty, + service.Id?.SubscriptionId ?? string.Empty, + service.Sku?.Name ?? string.Empty, + service.Sku?.Capacity ?? 0, + properties?.State ?? string.Empty, + properties?.ProvisioningState ?? string.Empty, + properties?.ServiceOperationsHostName ?? string.Empty, + properties?.DeviceProvisioningHostName ?? string.Empty, + properties?.IdScope ?? string.Empty, + properties?.AllocationPolicy ?? string.Empty, + properties?.PublicNetworkAccess ?? string.Empty, + properties?.EnableDataResidency, + linkedHubs); + } +} + diff --git a/tools/Azure.Mcp.Tools.DeviceProvisioning/src/Services/IDeviceProvisioningService.cs b/tools/Azure.Mcp.Tools.DeviceProvisioning/src/Services/IDeviceProvisioningService.cs new file mode 100644 index 0000000000..c9048d4187 --- /dev/null +++ b/tools/Azure.Mcp.Tools.DeviceProvisioning/src/Services/IDeviceProvisioningService.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Azure.Mcp.Tools.DeviceProvisioning.Models; + +namespace Azure.Mcp.Tools.DeviceProvisioning.Services; + +public interface IDeviceProvisioningService +{ + Task GetService( + string serviceName, + string resourceGroup, + string subscription, + string? tenant = null, + CancellationToken cancellationToken = default); +} + diff --git a/tools/Azure.Mcp.Tools.DeviceProvisioning/tests/Azure.Mcp.Tools.DeviceProvisioning.Tests/Azure.Mcp.Tools.DeviceProvisioning.Tests.csproj b/tools/Azure.Mcp.Tools.DeviceProvisioning/tests/Azure.Mcp.Tools.DeviceProvisioning.Tests/Azure.Mcp.Tools.DeviceProvisioning.Tests.csproj new file mode 100644 index 0000000000..8da6b72ed3 --- /dev/null +++ b/tools/Azure.Mcp.Tools.DeviceProvisioning/tests/Azure.Mcp.Tools.DeviceProvisioning.Tests/Azure.Mcp.Tools.DeviceProvisioning.Tests.csproj @@ -0,0 +1,19 @@ + + + true + Exe + true + true + + + + + + + + + + + + + diff --git a/tools/Azure.Mcp.Tools.DeviceProvisioning/tests/Azure.Mcp.Tools.DeviceProvisioning.Tests/DeviceProvisioningCommandTests.cs b/tools/Azure.Mcp.Tools.DeviceProvisioning/tests/Azure.Mcp.Tools.DeviceProvisioning.Tests/DeviceProvisioningCommandTests.cs new file mode 100644 index 0000000000..023ee83d78 --- /dev/null +++ b/tools/Azure.Mcp.Tools.DeviceProvisioning/tests/Azure.Mcp.Tools.DeviceProvisioning.Tests/DeviceProvisioningCommandTests.cs @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Text.Json; +using Microsoft.Mcp.Tests; +using Microsoft.Mcp.Tests.Client; +using Microsoft.Mcp.Tests.Client.Helpers; +using Xunit; + +namespace Azure.Mcp.Tools.DeviceProvisioning.Tests; + +public class DeviceProvisioningCommandTests( + ITestOutputHelper output, + TestProxyFixture fixture, + LiveServerFixture liveServerFixture) + : RecordedCommandTestsBase(output, fixture, liveServerFixture) +{ + [Fact] + public async Task ShouldGetDeviceProvisioningService() + { + var result = await CallToolAsync( + "deviceprovisioning_service_get", + new() + { + { "service", Settings.ResourceBaseName }, + { "resource-group", Settings.ResourceGroupName }, + { "subscription", Settings.SubscriptionId }, + { "tenant", Settings.TenantId } + }); + + Assert.NotNull(result); + + var payload = result.Value; + var provisioningService = payload.AssertProperty("deviceProvisioningService"); + Assert.Equal(JsonValueKind.Object, provisioningService.ValueKind); + provisioningService.AssertProperty("name"); + Assert.NotEmpty( + provisioningService.AssertProperty("idScope").GetString() ?? string.Empty); + + var linkedHubs = provisioningService.AssertProperty("iotHubs"); + Assert.Equal(JsonValueKind.Array, linkedHubs.ValueKind); + + var areResultsTruncated = payload.AssertProperty("areResultsTruncated"); + Assert.False(areResultsTruncated.GetBoolean()); + } +} diff --git a/tools/Azure.Mcp.Tools.DeviceProvisioning/tests/Azure.Mcp.Tools.DeviceProvisioning.Tests/Models/DeviceProvisioningPropertiesTests.cs b/tools/Azure.Mcp.Tools.DeviceProvisioning/tests/Azure.Mcp.Tools.DeviceProvisioning.Tests/Models/DeviceProvisioningPropertiesTests.cs new file mode 100644 index 0000000000..61b1a2548f --- /dev/null +++ b/tools/Azure.Mcp.Tools.DeviceProvisioning/tests/Azure.Mcp.Tools.DeviceProvisioning.Tests/Models/DeviceProvisioningPropertiesTests.cs @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Text.Json; +using Azure.Mcp.Tools.DeviceProvisioning.Commands; +using Xunit; + +namespace Azure.Mcp.Tools.DeviceProvisioning.Tests.Models; + +public class DeviceProvisioningPropertiesTests +{ + [Fact] + public void Deserialize_IgnoresSecretsAndPreservesSafeLinkedHubMetadata() + { + const string json = """ + { + "state": "Active", + "authorizationPolicies": [ + { + "keyName": "provisioningserviceowner", + "primaryKey": "secret-primary", + "secondaryKey": "secret-secondary" + } + ], + "iotHubs": [ + { + "name": "hub1.azure-devices.net", + "location": "eastus", + "applyAllocationPolicy": true, + "allocationWeight": 1, + "connectionString": "HostName=hub1.azure-devices.net;SharedAccessKey=secret" + } + ] + } + """; + + var properties = JsonSerializer.Deserialize( + json, + DeviceProvisioningJsonContext.Default.DeviceProvisioningProperties); + + Assert.NotNull(properties); + Assert.Equal("Active", properties.State); + var linkedHub = Assert.Single(properties.IotHubs!); + Assert.Equal("hub1.azure-devices.net", linkedHub.Name); + Assert.Equal("eastus", linkedHub.Location); + + var serialized = JsonSerializer.Serialize( + properties, + DeviceProvisioningJsonContext.Default.DeviceProvisioningProperties); + Assert.DoesNotContain("authorizationPolicies", serialized); + Assert.DoesNotContain("primaryKey", serialized); + Assert.DoesNotContain("connectionString", serialized); + Assert.DoesNotContain("secret", serialized); + } +} diff --git a/tools/Azure.Mcp.Tools.DeviceProvisioning/tests/Azure.Mcp.Tools.DeviceProvisioning.Tests/Service/DeviceProvisioningServiceGetCommandTests.cs b/tools/Azure.Mcp.Tools.DeviceProvisioning/tests/Azure.Mcp.Tools.DeviceProvisioning.Tests/Service/DeviceProvisioningServiceGetCommandTests.cs new file mode 100644 index 0000000000..f2a7dd76d9 --- /dev/null +++ b/tools/Azure.Mcp.Tools.DeviceProvisioning/tests/Azure.Mcp.Tools.DeviceProvisioning.Tests/Service/DeviceProvisioningServiceGetCommandTests.cs @@ -0,0 +1,200 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Net; +using Azure.Mcp.Tests.Commands; +using Azure.Mcp.Tools.DeviceProvisioning.Commands; +using Azure.Mcp.Tools.DeviceProvisioning.Commands.Service; +using Azure.Mcp.Tools.DeviceProvisioning.Models; +using Azure.Mcp.Tools.DeviceProvisioning.Services; +using NSubstitute; +using NSubstitute.ExceptionExtensions; +using Xunit; + +namespace Azure.Mcp.Tools.DeviceProvisioning.Tests.Service; + +public class DeviceProvisioningServiceGetCommandTests + : SubscriptionCommandUnitTestsBase +{ + [Fact] + public void Constructor_InitializesCommandCorrectly() + { + var command = Command.GetCommand(); + Assert.Equal("get", command.Name); + Assert.NotNull(command.Description); + Assert.NotEmpty(command.Description); + } + + [Theory] + [InlineData("--subscription sub123 --service dps1", false)] + [InlineData("--subscription sub123 --resource-group rg1 --service dps1", true)] + [InlineData("--subscription sub123", false)] + public async Task ExecuteAsync_ValidatesInputCorrectly( + string args, + bool shouldSucceed) + { + if (shouldSucceed) + { + Service.GetService( + Arg.Any(), + Arg.Any(), + Arg.Any(), + Arg.Any(), + Arg.Any()) + .Returns(CreateDescription()); + } + + var response = await ExecuteCommandAsync(args); + + Assert.Equal( + shouldSucceed ? HttpStatusCode.OK : HttpStatusCode.BadRequest, + response.Status); + if (!shouldSucceed) + { + Assert.Contains("required", response.Message.ToLowerInvariant()); + } + } + + [Theory] + [InlineData("ab")] + [InlineData("-dps-name")] + [InlineData("dps-name-")] + [InlineData("dps!name")] + [InlineData("dps' OR 1=1")] + public async Task ExecuteAsync_RejectsInvalidServiceName(string invalidName) + { + var response = await ExecuteCommandAsync( + "--subscription", + "sub123", + "--resource-group", + "rg1", + "--service", + invalidName); + + Assert.Equal(HttpStatusCode.BadRequest, response.Status); + Assert.Contains( + "--service must be 3-64 characters long", + response.Message); + } + + [Fact] + public async Task ExecuteAsync_RejectsOversizedServiceName() + { + var response = await ExecuteCommandAsync( + "--subscription", + "sub123", + "--resource-group", + "rg1", + "--service", + new string('a', 65)); + + Assert.Equal(HttpStatusCode.BadRequest, response.Status); + Assert.Contains( + "--service must be 3-64 characters long", + response.Message); + } + + [Fact] + public async Task ExecuteAsync_DeserializationValidation() + { + Service.GetService( + Arg.Any(), + Arg.Any(), + Arg.Any(), + Arg.Any(), + Arg.Any()) + .Returns(CreateDescription()); + + var response = await ExecuteCommandAsync( + "--subscription", + "sub123", + "--resource-group", + "rg1", + "--service", + "dps1"); + + var result = ValidateAndDeserializeResponse( + response, + DeviceProvisioningJsonContext.Default.DeviceProvisioningServiceGetCommandResult); + Assert.Equal("dps1", result.DeviceProvisioningService.Name); + Assert.Single(result.DeviceProvisioningService.IotHubs); + Assert.False(result.AreResultsTruncated); + } + + [Fact] + public async Task ExecuteAsync_HandlesServiceErrors() + { + Service.GetService( + Arg.Any(), + Arg.Any(), + Arg.Any(), + Arg.Any(), + Arg.Any()) + .ThrowsAsync(new Exception("Test error")); + + var response = await ExecuteCommandAsync( + "--subscription", + "sub123", + "--resource-group", + "rg1", + "--service", + "dps1"); + + Assert.Equal(HttpStatusCode.InternalServerError, response.Status); + Assert.Contains("Test error", response.Message); + Assert.Contains("troubleshooting", response.Message); + } + + [Fact] + public async Task ExecuteAsync_HandlesNotFound() + { + Service.GetService( + Arg.Any(), + Arg.Any(), + Arg.Any(), + Arg.Any(), + Arg.Any()) + .ThrowsAsync(new RequestFailedException( + (int)HttpStatusCode.NotFound, + "Resource not found")); + + var response = await ExecuteCommandAsync( + "--subscription", + "sub123", + "--resource-group", + "rg1", + "--service", + "dps1"); + + Assert.Equal(HttpStatusCode.NotFound, response.Status); + Assert.Contains("Resource not found", response.Message); + } + + private static DeviceProvisioningServiceDescription CreateDescription() + { + return new DeviceProvisioningServiceDescription( + Id: "/subscriptions/sub123/resourceGroups/rg1/providers/Microsoft.Devices/provisioningServices/dps1", + Name: "dps1", + Location: "eastus", + ResourceGroup: "rg1", + SubscriptionId: "sub123", + Sku: "S1", + Capacity: 1, + State: "Active", + ProvisioningState: "Succeeded", + ServiceOperationsHostName: "dps1.azure-devices-provisioning.net", + DeviceProvisioningHostName: "global.azure-devices-provisioning.net", + IdScope: "0ne00000001", + AllocationPolicy: "Hashed", + PublicNetworkAccess: "Enabled", + EnableDataResidency: false, + IotHubs: + [ + new LinkedIoTHubDescription( + "hub1.azure-devices.net", + "eastus", + true, + 1) + ]); + } +} diff --git a/tools/Azure.Mcp.Tools.DeviceProvisioning/tests/Azure.Mcp.Tools.DeviceProvisioning.Tests/assets.json b/tools/Azure.Mcp.Tools.DeviceProvisioning/tests/Azure.Mcp.Tools.DeviceProvisioning.Tests/assets.json new file mode 100644 index 0000000000..82d3da9a69 --- /dev/null +++ b/tools/Azure.Mcp.Tools.DeviceProvisioning/tests/Azure.Mcp.Tools.DeviceProvisioning.Tests/assets.json @@ -0,0 +1,7 @@ +{ + "AssetsRepo": "Azure/azure-sdk-assets", + "AssetsRepoPrefixPath": "", + "TagPrefix": "Azure.Mcp.Tools.DeviceProvisioning.Tests", + "Tag": "" +} + diff --git a/tools/Azure.Mcp.Tools.DeviceProvisioning/tests/test-resources-post.ps1 b/tools/Azure.Mcp.Tools.DeviceProvisioning/tests/test-resources-post.ps1 new file mode 100644 index 0000000000..858659fe63 --- /dev/null +++ b/tools/Azure.Mcp.Tools.DeviceProvisioning/tests/test-resources-post.ps1 @@ -0,0 +1,17 @@ +param( + [string] $TenantId, + [string] $TestApplicationId, + [string] $ResourceGroupName, + [string] $BaseName, + [hashtable] $DeploymentOutputs, + [hashtable] $AdditionalParameters +) + +$ErrorActionPreference = "Stop" + +. "$PSScriptRoot/../../../eng/common/scripts/common.ps1" +. "$PSScriptRoot/../../../eng/scripts/helpers/TestResourcesHelpers.ps1" + +New-TestSettings @PSBoundParameters -OutputPath $PSScriptRoot + +Write-Host "Azure Device Provisioning Service test setup complete." diff --git a/tools/Azure.Mcp.Tools.DeviceProvisioning/tests/test-resources.bicep b/tools/Azure.Mcp.Tools.DeviceProvisioning/tests/test-resources.bicep new file mode 100644 index 0000000000..9fa27bb803 --- /dev/null +++ b/tools/Azure.Mcp.Tools.DeviceProvisioning/tests/test-resources.bicep @@ -0,0 +1,39 @@ +targetScope = 'resourceGroup' + +@minLength(3) +@maxLength(64) +@description('The base resource name.') +param baseName string = resourceGroup().name + +@description('The client OID to grant access to test resources.') +param testApplicationOid string = deployer().objectId + +@description('The location of the resource. By default, this is the same as the resource group.') +param location string = resourceGroup().location + +resource provisioningService 'Microsoft.Devices/provisioningServices@2022-12-12' = { + name: baseName + location: location + sku: { + name: 'S1' + capacity: 1 + } + properties: {} +} + +resource readerRoleDefinition 'Microsoft.Authorization/roleDefinitions@2018-01-01-preview' existing = { + scope: subscription() + name: 'acdd72a7-3385-48ef-bd42-f606fba81ae7' +} + +resource readerRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(readerRoleDefinition.id, testApplicationOid, resourceGroup().id) + scope: resourceGroup() + properties: { + principalId: testApplicationOid + roleDefinitionId: readerRoleDefinition.id + } +} + +output DEVICE_PROVISIONING_SERVICE_NAME string = provisioningService.name + From 28cf2f1425c65a4cc6e0df2287790edd993595b5 Mon Sep 17 00:00:00 2001 From: Khalil Sayed Date: Thu, 8 Oct 2026 23:42:09 +0000 Subject: [PATCH 2/5] Sanitize DPS live test recordings Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: ae3e90da-ad11-496c-ac8d-5e77c48af218 --- .../DeviceProvisioningCommandTests.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tools/Azure.Mcp.Tools.DeviceProvisioning/tests/Azure.Mcp.Tools.DeviceProvisioning.Tests/DeviceProvisioningCommandTests.cs b/tools/Azure.Mcp.Tools.DeviceProvisioning/tests/Azure.Mcp.Tools.DeviceProvisioning.Tests/DeviceProvisioningCommandTests.cs index 023ee83d78..674650a150 100644 --- a/tools/Azure.Mcp.Tools.DeviceProvisioning/tests/Azure.Mcp.Tools.DeviceProvisioning.Tests/DeviceProvisioningCommandTests.cs +++ b/tools/Azure.Mcp.Tools.DeviceProvisioning/tests/Azure.Mcp.Tools.DeviceProvisioning.Tests/DeviceProvisioningCommandTests.cs @@ -5,6 +5,7 @@ using Microsoft.Mcp.Tests; using Microsoft.Mcp.Tests.Client; using Microsoft.Mcp.Tests.Client.Helpers; +using Microsoft.Mcp.Tests.Generated.Models; using Xunit; namespace Azure.Mcp.Tools.DeviceProvisioning.Tests; @@ -15,6 +16,24 @@ public class DeviceProvisioningCommandTests( LiveServerFixture liveServerFixture) : RecordedCommandTestsBase(output, fixture, liveServerFixture) { + // Resource-group names can include the recording user's alias. + public override List BodyKeySanitizers => + [ + ..base.BodyKeySanitizers, + new(new BodyKeySanitizerBody("$..resourcegroup")) + ]; + + public override List BodyRegexSanitizers => + [ + ..base.BodyRegexSanitizers, + new(new BodyRegexSanitizerBody + { + Regex = "resource[Gg]roups/(?[^/?\"]+)", + GroupForReplace = "resourceGroup", + Value = "Sanitized" + }) + ]; + [Fact] public async Task ShouldGetDeviceProvisioningService() { From 2848dc4325257f500779c447c1a6da4cb6c1cfc5 Mon Sep 17 00:00:00 2001 From: Khalil Sayed Date: Fri, 9 Oct 2026 18:42:52 +0000 Subject: [PATCH 3/5] Reference DPS live test recording Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: ae3e90da-ad11-496c-ac8d-5e77c48af218 --- .../tests/Azure.Mcp.Tools.DeviceProvisioning.Tests/assets.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tools/Azure.Mcp.Tools.DeviceProvisioning/tests/Azure.Mcp.Tools.DeviceProvisioning.Tests/assets.json b/tools/Azure.Mcp.Tools.DeviceProvisioning/tests/Azure.Mcp.Tools.DeviceProvisioning.Tests/assets.json index 82d3da9a69..eb7c83f909 100644 --- a/tools/Azure.Mcp.Tools.DeviceProvisioning/tests/Azure.Mcp.Tools.DeviceProvisioning.Tests/assets.json +++ b/tools/Azure.Mcp.Tools.DeviceProvisioning/tests/Azure.Mcp.Tools.DeviceProvisioning.Tests/assets.json @@ -2,6 +2,5 @@ "AssetsRepo": "Azure/azure-sdk-assets", "AssetsRepoPrefixPath": "", "TagPrefix": "Azure.Mcp.Tools.DeviceProvisioning.Tests", - "Tag": "" + "Tag": "Azure.Mcp.Tools.DeviceProvisioning.Tests_c0c784a6b9" } - From 09133a27001551bcc896d8f67ae7d71475e7cf5f Mon Sep 17 00:00:00 2001 From: Khalil Sayed Date: Fri, 9 Oct 2026 20:51:08 +0000 Subject: [PATCH 4/5] Remove unpublished recording tag Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: ae3e90da-ad11-496c-ac8d-5e77c48af218 --- .../tests/Azure.Mcp.Tools.DeviceProvisioning.Tests/assets.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/Azure.Mcp.Tools.DeviceProvisioning/tests/Azure.Mcp.Tools.DeviceProvisioning.Tests/assets.json b/tools/Azure.Mcp.Tools.DeviceProvisioning/tests/Azure.Mcp.Tools.DeviceProvisioning.Tests/assets.json index eb7c83f909..38c255781c 100644 --- a/tools/Azure.Mcp.Tools.DeviceProvisioning/tests/Azure.Mcp.Tools.DeviceProvisioning.Tests/assets.json +++ b/tools/Azure.Mcp.Tools.DeviceProvisioning/tests/Azure.Mcp.Tools.DeviceProvisioning.Tests/assets.json @@ -2,5 +2,5 @@ "AssetsRepo": "Azure/azure-sdk-assets", "AssetsRepoPrefixPath": "", "TagPrefix": "Azure.Mcp.Tools.DeviceProvisioning.Tests", - "Tag": "Azure.Mcp.Tools.DeviceProvisioning.Tests_c0c784a6b9" + "Tag": "" } From 6a5b6a585abb1d2a9e9b420d0fe629056348fc9e Mon Sep 17 00:00:00 2001 From: Khalil Sayed Date: Thu, 15 Oct 2026 14:00:42 +0000 Subject: [PATCH 5/5] Publish DPS live test recording Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: ae3e90da-ad11-496c-ac8d-5e77c48af218 --- .../tests/Azure.Mcp.Tools.DeviceProvisioning.Tests/assets.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/Azure.Mcp.Tools.DeviceProvisioning/tests/Azure.Mcp.Tools.DeviceProvisioning.Tests/assets.json b/tools/Azure.Mcp.Tools.DeviceProvisioning/tests/Azure.Mcp.Tools.DeviceProvisioning.Tests/assets.json index 38c255781c..eb7c83f909 100644 --- a/tools/Azure.Mcp.Tools.DeviceProvisioning/tests/Azure.Mcp.Tools.DeviceProvisioning.Tests/assets.json +++ b/tools/Azure.Mcp.Tools.DeviceProvisioning/tests/Azure.Mcp.Tools.DeviceProvisioning.Tests/assets.json @@ -2,5 +2,5 @@ "AssetsRepo": "Azure/azure-sdk-assets", "AssetsRepoPrefixPath": "", "TagPrefix": "Azure.Mcp.Tools.DeviceProvisioning.Tests", - "Tag": "" + "Tag": "Azure.Mcp.Tools.DeviceProvisioning.Tests_c0c784a6b9" }