diff --git a/.vscode/cspell.json b/.vscode/cspell.json index fe6ec9df9..cb928b5c9 100644 --- a/.vscode/cspell.json +++ b/.vscode/cspell.json @@ -477,6 +477,7 @@ "tfvars", "timechart", "timespan", + "toolset", "toolsets", "uaenorth", "uksouth", diff --git a/docs/new-command.md b/docs/new-command.md index 9b7a3f824..551adb14d 100644 --- a/docs/new-command.md +++ b/docs/new-command.md @@ -5,16 +5,18 @@ This document provides a comprehensive guide for implementing commands in Azure MCP following established patterns. -## Area Pattern: Organizing code by area +## Toolset Pattern: Organizing code by toolset -All new Azure services and their commands should use the Area pattern: +All new Azure services and their commands should use the Toolset pattern: -- **Area code** goes in `areas/{area-name}/src/AzureMcp.{AreaName}` (e.g., `areas/storage/src/AzureMcp.Storage`) -- **Tests** go in `areas/{area-name}/tests`, divided into UnitTests and LiveTests: - - `areas/{area-name}/tests/AzureMcp.{AreaName}.UnitTests` - - `areas/{area-name}/tests/AzureMcp.{AreaName}.LiveTests` +- **Toolset code** goes in `tools/Azure.Mcp.Tools.{Toolset}/src` (e.g., `tools/Azure.Mcp.Tools.Storage/src`) +- **Tests** go in `tools/Azure.Mcp.Tools.{Toolset}/tests`, divided into UnitTests and LiveTests: + - `tools/Azure.Mcp.Tools.{Toolset}/tests/Azure.Mcp.Tools.{Toolset}.UnitTests` + - `tools/Azure.Mcp.Tools.{Toolset}/tests/Azure.Mcp.Tools.{Toolset}.LiveTests` + - `tools/Azure.Mcp.Tools.Monitor/tests/Azure.Mcp.Tools.Monitor.UnitTests` + - `tools/Azure.Mcp.Tools.Monitor/tests/Azure.Mcp.Tools.Monitor.LiveTests` -This keeps all code, options, models, and tests for an area together. See `areas/storage` for a reference implementation. +This keeps all code, options, models, and tests for an toolset together. See `tools/Azure.Mcp.Tools.Storage` for a reference implementation. ## ⚠️ Test Infrastructure Requirements @@ -22,11 +24,11 @@ This keeps all code, options, models, and tests for an area together. See `areas ### **Azure Service Commands (REQUIRES Test Infrastructure)** If your command interacts with Azure resources (storage accounts, databases, VMs, etc.): -- ✅ **MUST create** `areas/{area-name}/tests/test-resources.bicep` -- ✅ **MUST create** `areas/{area-name}/tests/test-resources-post.ps1` (required even if basic template) +- ✅ **MUST create** `tools/Azure.Mcp.Tools.{Toolset}/tests/test-resources.bicep` +- ✅ **MUST create** `tools/Azure.Mcp.Tools.{Toolset}/tests/test-resources-post.ps1` (required even if basic template) - ✅ **MUST include** RBAC role assignments for test application -- ✅ **MUST validate** with `az bicep build --file areas/{area-name}/tests/test-resources.bicep` -- ✅ **MUST test deployment** with `./eng/scripts/Deploy-TestResources.ps1 -Area {area-name}` +- ✅ **MUST validate** with `az bicep build --file tools/Azure.Mcp.Tools.{Toolset}/tests/test-resources.bicep` +- ✅ **MUST test deployment** with `./eng/scripts/Deploy-TestResources.ps1 -Tool 'Azure.Mcp.Tools.{Toolset}'` ### **Non-Azure Commands (No Test Infrastructure Needed)** If your command is a wrapper/utility (CLI tools, best practices, documentation): @@ -82,8 +84,8 @@ If your command is a wrapper/utility (CLI tools, best practices, documentation): - `operation`: Action to perform (verb, lowercase) Each command is: - - In code, to avoid ambiguity between service classes and Azure services, we refer to Azure services as Areas - - Registered in the RegisterCommands method of its area's `areas/{area-name}/src/AzureMcp.{AreaName}/{AreaName}Setup.cs` file + - In code, to avoid ambiguity between service classes and Azure services, we refer to Azure services as Toolsets + - Registered in the RegisterCommands method of its toolset's `tools/Azure.Mcp.Tools.{Toolset}/src/{Toolset}Setup.cs` file - Organized in a hierarchy of command groups - Documented with a title, description and examples - Validated before execution @@ -107,20 +109,20 @@ If your command is a wrapper/utility (CLI tools, best practices, documentation): A complete command requires: -1. OptionDefinitions static class: `areas/{area-name}/src/AzureMcp.{AreaName}/Options/{AreaName}OptionDefinitions.cs` -2. Options class: `areas/{area-name}/src/AzureMcp.{AreaName}/Options/{Resource}/{Operation}Options.cs` -3. Command class: `areas/{area-name}/src/AzureMcp.{AreaName}/Commands/{Resource}/{Resource}{Operation}Command.cs` -4. Service interface: `areas/{area-name}/src/AzureMcp.{AreaName}/Services/I{ServiceName}Service.cs` -5. Service implementation: `areas/{area-name}/src/AzureMcp.{AreaName}/Services/{ServiceName}Service.cs` - - It's common for an area to have a single service class named after the - area but some areas will have multiple service classes -6. Unit test: `areas/{area-name}/tests/AzureMcp.{AreaName}.UnitTests/{Resource}/{Resource}{Operation}CommandTests.cs` -7. Integration test: `areas/{area-name}/tests/AzureMcp.{AreaName}.LiveTests/{AreaName}CommandTests.cs` -8. Command registration in RegisterCommands(): `areas/{area-name}/src/AzureMcp.{AreaName}/{AreaName}Setup.cs` -9. Area registration in RegisterAreas(): `core/src/AzureMcp.Cli/Program.cs` +1. OptionDefinitions static class: `tools/Azure.Mcp.Tools.{Toolset}/src/Options/{Toolset}OptionDefinitions.cs` +2. Options class: `tools/Azure.Mcp.Tools.{Toolset}/src/Options/{Resource}/{Operation}Options.cs` +3. Command class: `tools/Azure.Mcp.Tools.{Toolset}/src/Commands/{Resource}/{Resource}{Operation}Command.cs` +4. Service interface: `tools/Azure.Mcp.Tools.{Toolset}/src/Services/I{ServiceName}Service.cs` +5. Service implementation: `tools/Azure.Mcp.Tools.{Toolset}/src/Services/{ServiceName}Service.cs` + - It's common for an toolset to have a single service class named after the + toolset but some toolsets will have multiple service classes +6. Unit test: `tools/Azure.Mcp.Tools.{Toolset}/tests/Azure.Mcp.Tools.{Toolset}.UnitTests/{Resource}/{Resource}{Operation}CommandTests.cs` +7. Integration test: `tools/Azure.Mcp.Tools.{Toolset}/tests/Azure.Mcp.Tools.{Toolset}.LiveTests/{Toolset}CommandTests.cs` +8. Command registration in RegisterCommands(): `tools/Azure.Mcp.Tools.{Toolset}/src/{Toolset}Setup.cs` +9. Toolset registration in RegisterAreas(): `servers/Azure.Mcp.Server/src/Program.cs` 10. **Live test infrastructure** (for Azure service commands): - - Bicep template: `/areas/{area-name}/tests/test-resources.bicep` - - Post-deployment script: `/areas/{area-name}/tests/test-resources-post.ps1` (required, even if basic template) + - Bicep template: `tools/Azure.Mcp.Tools.{Toolset}/tests/test-resources.bicep` + - Post-deployment script: `tools/Azure.Mcp.Tools.{Toolset}/tests/test-resources-post.ps1` (required, even if basic template) ### File and Class Naming Convention @@ -153,16 +155,16 @@ This convention ensures: - Better IDE intellisense and code navigation - Easier maintenance and discovery -**IMPORTANT**: If implementing a new area, you must also ensure: +**IMPORTANT**: If implementing a new toolset, you must also ensure: - The Azure Resource Manager package is added to `Directory.Packages.props` first - Models, base commands, and option definitions follow the established patterns - JSON serialization context includes all new model types -- Service registration in the area setup ConfigureServices method -- **Live test infrastructure**: Add Bicep template to `/areas/{area-name}/tests` +- Service registration in the toolset setup ConfigureServices method +- **Live test infrastructure**: Add Bicep template to `tools/Azure.Mcp.Tools.{Toolset}/tests` - **Test resource deployment**: Ensure resources are properly configured with RBAC for test application - **Resource naming**: Follow consistent naming patterns - many services use just `baseName`, while others may need suffixes for disambiguation (e.g., `{baseName}-suffix`) - **Solution file integration**: Add new projects to `AzureMcp.sln` with proper GUID generation to avoid conflicts -- **Program.cs registration**: Register the new area in `Program.cs` `RegisterAreas()` method in alphabetical order +- **Program.cs registration**: Register the new toolset in `Program.cs` `RegisterAreas()` method in alphabetical order ## Implementation Guidelines @@ -174,13 +176,13 @@ When creating commands that interact with Azure services, you'll need to: For **Resource Graph queries** (using `BaseAzureResourceService`): - No additional packages required - `Azure.ResourceManager.ResourceGraph` is already included in the core project -- Only add area-specific packages if you need direct ARM operations beyond Resource Graph queries +- Only add toolset-specific packages if you need direct ARM operations beyond Resource Graph queries - Example: `` (only if needed for direct ARM operations) For **Direct ARM operations** (using `BaseAzureService`): - Add the appropriate Azure Resource Manager package to `Directory.Packages.props` - Example: `` -- Add the package reference in `AzureMcp.{AreaName}.csproj` +- Add the package reference in `Azure.Mcp.Tools.{Toolset}.csproj` - Example: `` - **Version Consistency**: Ensure the package version in `Directory.Packages.props` matches across all projects - **Build Order**: Add the package to `Directory.Packages.props` first, then reference it in project files to avoid build errors @@ -307,7 +309,7 @@ var databaseResource = await sqlServerResource.Value ### 2. Options Class ```csharp -public class {Resource}{Operation}Options : Base{Area}Options +public class {Resource}{Operation}Options : Base{Toolset}Options { // Only add properties not in base class public string? NewOption { get; set; } @@ -315,7 +317,7 @@ public class {Resource}{Operation}Options : Base{Area}Options ``` IMPORTANT: -- Inherit from appropriate base class (Base{Area}Options, GlobalOptions, etc.) +- Inherit from appropriate base class (Base{Toolset}Options, GlobalOptions, etc.) - Never redefine properties from base classes - Make properties nullable if not required - Use consistent parameter names across services: @@ -339,7 +341,7 @@ protected string? GetResourceGroup(); // Convenience accessor with validation ``` Key rules: -- Do NOT create area-specific optional resource group options. +- Do NOT create toolset-specific optional resource group options. - Do NOT override `_resourceGroupOption` or manually add `OptionDefinitions.Common.ResourceGroup` to commands. - Do NOT manually assign `options.ResourceGroup` in `BindOptions` – central binding in `GlobalCommand` handles this when a command calls either helper. - Validation for required resource group happens centrally (logical requirement), not at parser level. @@ -390,13 +392,13 @@ Rationale: ```csharp public sealed class {Resource}{Operation}Command(ILogger<{Resource}{Operation}Command> logger) - : Base{Area}Command<{Resource}{Operation}Options> + : Base{Toolset}Command<{Resource}{Operation}Options> { private const string CommandTitle = "Human Readable Title"; private readonly ILogger<{Resource}{Operation}Command> _logger = logger; // Define options from OptionDefinitions - private readonly Option _newOption = {Area}OptionDefinitions.NewOption; + private readonly Option _newOption = {Toolset}OptionDefinitions.NewOption; public override string Name => "operation"; @@ -444,7 +446,7 @@ public sealed class {Resource}{Operation}Command(ILogger<{Resource}{Operation}Co context.Activity?.WithSubscriptionTag(options); // Get the appropriate service from DI - var service = context.GetService(); + var service = context.GetService(); // Call service operation(s) with required parameters var results = await service.{Operation}( @@ -457,7 +459,7 @@ public sealed class {Resource}{Operation}Command(ILogger<{Resource}{Operation}Co context.Response.Results = results?.Count > 0 ? ResponseResult.Create( new {Operation}CommandResult(results), - {Area}JsonContext.Default.{Operation}CommandResult) : + {Toolset}JsonContext.Default.{Operation}CommandResult) : null; } catch (Exception ex) @@ -495,17 +497,17 @@ public sealed class {Resource}{Operation}Command(ILogger<{Resource}{Operation}Co ### 4. Service Interface and Implementation -Each area has its own service interface that defines the methods that commands will call. The interface will have an implementation that contains the actual logic. +Each toolset has its own service interface that defines the methods that commands will call. The interface will have an implementation that contains the actual logic. ```csharp -public interface IService +public interface IService { ... } ``` ```csharp -public class Service(ISubscriptionService subscriptionService, ITenantService tenantService, ICacheService cacheService) : BaseAzureService(tenantService), IService +public class Service(ISubscriptionService subscriptionService, ITenantService tenantService, ICacheService cacheService) : BaseAzureService(tenantService), IService { ... } @@ -533,30 +535,30 @@ Task> GetStorageAccounts(string subscription, string? tenant = null ### 5. Base Service Command Classes -Each area has its own hierarchy of base command classes that inherit from `GlobalCommand` or `SubscriptionCommand`. Service classes that work with Azure resources should inject `ISubscriptionService` for subscription resolution. For example: +Each toolset has its own hierarchy of base command classes that inherit from `GlobalCommand` or `SubscriptionCommand`. Service classes that work with Azure resources should inject `ISubscriptionService` for subscription resolution. For example: ```csharp // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System.Diagnostics.CodeAnalysis; -using AzureMcp.Core.Commands; -using AzureMcp.Core.Commands.Subscription; -using AzureMcp.{Area}.Options; +using Azure.Mcp.Core.Commands; +using Azure.Mcp.Core.Commands.Subscription; +using Azure.Mcp.Tools.{Toolset}.Options; -namespace AzureMcp.{Area}.Commands; +namespace Azure.Mcp.Tools.{Toolset}.Commands; // Base command for all service commands (if no members needed, use concise syntax) -public abstract class Base{Area}Command< +public abstract class Base{Toolset}Command< [DynamicallyAccessedMembers(TrimAnnotations.CommandAnnotations)] TOptions> - : SubscriptionCommand where TOptions : Base{Area}Options, new(); + : SubscriptionCommand where TOptions : Base{Toolset}Options, new(); // Base command for all service commands (if members are needed, use full syntax) -public abstract class Base{Area}Command< +public abstract class Base{Toolset}Command< [DynamicallyAccessedMembers(TrimAnnotations.CommandAnnotations)] TOptions> - : SubscriptionCommand where TOptions : Base{Area}Options, new() + : SubscriptionCommand where TOptions : Base{Toolset}Options, new() { - protected readonly Option _commonOption = {Area}OptionDefinitions.CommonOption; + protected readonly Option _commonOption = {Toolset}OptionDefinitions.CommonOption; protected readonly Option _resourceGroupOption = OptionDefinitions.Common.ResourceGroup; protected virtual bool RequiresResourceGroup => true; @@ -587,8 +589,8 @@ public abstract class Base{Area}Command< } // Service implementation example with subscription resolution -public class {Area}Service(ISubscriptionService subscriptionService, ITenantService tenantService) - : BaseAzureService(tenantService), I{Area}Service +public class {Toolset}Service(ISubscriptionService subscriptionService, ITenantService tenantService) + : BaseAzureService(tenantService), I{Toolset}Service { private readonly ISubscriptionService _subscriptionService = subscriptionService ?? throw new ArgumentNullException(nameof(subscriptionService)); @@ -612,7 +614,7 @@ Unit tests follow a standardized pattern that tests initialization, validation, public class {Resource}{Operation}CommandTests { private readonly IServiceProvider _serviceProvider; - private readonly I{Area}Service _service; + private readonly I{Toolset}Service _service; private readonly ILogger<{Resource}{Operation}Command> _logger; private readonly {Resource}{Operation}Command _command; private readonly CommandContext _context; @@ -620,7 +622,7 @@ public class {Resource}{Operation}CommandTests public {Resource}{Operation}CommandTests() { - _service = Substitute.For(); + _service = Substitute.For(); _logger = Substitute.For>(); var collection = new ServiceCollection().AddSingleton(_service); @@ -695,7 +697,7 @@ public class {Resource}{Operation}CommandTests Integration tests inherit from `CommandTestsBase` and use test fixtures: ```csharp -public class {Area}CommandTests(LiveTestFixture liveTestFixture, ITestOutputHelper output) +public class {Toolset}CommandTests(LiveTestFixture liveTestFixture, ITestOutputHelper output) : CommandTestsBase(liveTestFixture, output), IClassFixture { [Theory] @@ -705,7 +707,7 @@ public class {Area}CommandTests(LiveTestFixture liveTestFixture, ITestOutputHelp { // Arrange var result = await CallToolAsync( - "azmcp_{area}_{resource}_{operation}", + "azmcp_{Toolset}_{resource}_{operation}", new() { { "subscription", Settings.Subscription }, @@ -731,7 +733,7 @@ public class {Area}CommandTests(LiveTestFixture liveTestFixture, ITestOutputHelp public async Task Should_Return400_WithInvalidInput(string args) { var result = await CallToolAsync( - $"azmcp_{area}_{resource}_{operation} {args}"); + $"azmcp_{Toolset}_{resource}_{operation} {args}"); Assert.Equal(400, result.GetProperty("status").GetInt32()); Assert.Contains("required", @@ -746,8 +748,8 @@ public class {Area}CommandTests(LiveTestFixture liveTestFixture, ITestOutputHelp private void RegisterCommands(CommandGroup rootGroup, ILoggerFactory loggerFactory) { var service = new CommandGroup( - "{area}", - "{Area} operations"); + "{Toolset}", + "{Toolset} operations"); rootGroup.AddSubGroup(service); var resource = new CommandGroup( @@ -764,23 +766,23 @@ private void RegisterCommands(CommandGroup rootGroup, ILoggerFactory loggerFacto - ✅ Good: `"entraadmin"`, `"resourcegroup"`, `"storageaccount"`, `"entra-admin"` - ❌ Bad: `"entra_admin"`, `"resource_group"`, `"storage_account"` -### 9. Area Registration +### 9. Toolset Registration ```csharp - private static IAreaSetup[] RegisterAreas() + private static IToolsetSetup[] RegisterAreas() { return [ - // Register core areas - new AzureMcp.AzureBestPractices.AzureBestPracticesSetup(), - new AzureMcp.Extension.ExtensionSetup(), + // Register core toolsets + new Azure.Mcp.Tools.AzureBestPractices.AzureBestPracticesSetup(), + new Azure.Mcp.Tools.Extension.ExtensionSetup(), - // Register Azure service areas - new AzureMcp.{Area}.{Area}Setup(), - new AzureMcp.Storage.StorageSetup(), + // Register Azure service toolsets + new Azure.Mcp.Tools.{Toolset}.{Toolset}Setup(), + new Azure.Mcp.Tools.Storage.StorageSetup(), ]; } ``` -The area list in `RegisterAreas()` should stay sorted alphabetically. +The toolset list in `RegisterAreas()` should stay sorted alphabetically. ## Error Handling @@ -937,7 +939,7 @@ public async Task ExecuteAsync_HandlesServiceError() When developing new commands, run only your specific tests to save time: ```bash # Run all tests from the test project directory: -pushd ./areas/your-area/tests/AzureMcp.YourArea.UnitTests #or .LiveTests +pushd ./tools/Azure.Mcp.Tools.YourToolset/tests/Azure.Mcp.Tools.YourToolset.UnitTests #or .LiveTests # Run only tests for your specific command class dotnet test --filter "FullyQualifiedName~YourCommandNameTests" --verbosity normal @@ -945,18 +947,18 @@ dotnet test --filter "FullyQualifiedName~YourCommandNameTests" --verbosity norma # Example: Run only SQL AD Admin tests dotnet test --filter "FullyQualifiedName~EntraAdminListCommandTests" --verbosity normal -# Run all tests for a specific area +# Run all tests for a specific toolset dotnet test --verbosity normal ``` ### Integration Tests -Azure service commands requiring test resource deployment must add a bicep template, `tests/test-resources.bicep`, to their area directory. Additionally, all Azure service commands must include a `test-resources-post.ps1` file in the same directory, even if it contains only the basic template without custom logic. See `/areas/storage/tests/test-resources.bicep` and `/areas/storage/tests/test-resources-post.ps1` for canonical examples. +Azure service commands requiring test resource deployment must add a bicep template, `tests/test-resources.bicep`, to their toolset directory. Additionally, all Azure service commands must include a `test-resources-post.ps1` file in the same directory, even if it contains only the basic template without custom logic. See `/tools/Azure.Mcp.Tools.Storage/tests/test-resources.bicep` and `/tools/Azure.Mcp.Tools.Storage/tests/test-resources-post.ps1` for canonical examples. #### Live Test Resource Infrastructure -**1. Create Area Bicep Template (`/areas/{area-name}/tests/test-resources.bicep`)** +**1. Create Toolset Bicep Template (`/tools/Azure.Mcp.Tools.{Toolset}/tests/test-resources.bicep`)** -Follow this pattern for your area's infrastructure: +Follow this pattern for your toolset's infrastructure: ```bicep targetScope = 'resourceGroup' @@ -1031,14 +1033,14 @@ output testResourceName string = serviceResource::testResource.name - Use resource naming that clearly identifies test purposes **Common Resource Naming Patterns:** -- Deployments are on a per-area basis. Name collisions should not occur across area templates. +- Deployments are on a per-toolset basis. Name collisions should not occur across toolset templates. - Main service: `baseName` (most common, e.g., `mcp12345`) or `{baseName}{suffix}` if disambiguation needed - Child resources: `test{resource}` (e.g., `testdb`, `testcontainer`) - Follow Azure naming conventions and length limits - Ensure names are unique within resource group scope - Check existing `test-resources.bicep` files for consistent patterns -**2. Required: Post-Deployment Script (`/areas/{area-name}/tests/test-resources-post.ps1`)** +**2. Required: Post-Deployment Script (`tools/Azure.Mcp.Tools.{Toolset}/tests/test-resources-post.ps1`)** All Azure service commands must include this script, even if it contains only the basic template. Create with the standard template and add custom setup logic if needed: @@ -1060,11 +1062,11 @@ param ( [hashtable] $AdditionalParameters ) -Write-Host "Running {Area} post-deployment setup..." +Write-Host "Running {Toolset} post-deployment setup..." try { # Extract outputs from deployment - $serviceName = $DeploymentOutputs['{area}']['serviceResourceName']['value'] + $serviceName = $DeploymentOutputs['{Toolset}']['serviceResourceName']['value'] $resourceGroup = $AdditionalParameters['ResourceGroupName'] # Perform additional setup (e.g., create sample data, configure settings) @@ -1073,10 +1075,10 @@ try { # Example: Run Azure CLI commands for additional setup # az {service} {operation} --name $serviceName --resource-group $resourceGroup - Write-Host "{Area} post-deployment setup completed successfully." + Write-Host "{Toolset} post-deployment setup completed successfully." } catch { - Write-Error "Failed to complete {Area} post-deployment setup: $_" + Write-Error "Failed to complete {Toolset} post-deployment setup: $_" throw } ``` @@ -1086,9 +1088,9 @@ catch { Integration tests should use the deployed infrastructure: ```csharp -[Trait("Area", "{Area}")] +[Trait("Toolset", "{Toolset}")] [Trait("Category", "Live")] -public class {Area}CommandTests(LiveTestFixture liveTestFixture, ITestOutputHelper output) +public class {Toolset}CommandTests(LiveTestFixture liveTestFixture, ITestOutputHelper output) : CommandTestsBase(liveTestFixture, output), IClassFixture { [Fact] @@ -1099,7 +1101,7 @@ public class {Area}CommandTests(LiveTestFixture liveTestFixture, ITestOutputHelp var resourceName = "test{resource}"; var result = await CallToolAsync( - "azmcp_{area}_{resource}_show", + "azmcp_{Toolset}_{resource}_show", new() { { "subscription", Settings.SubscriptionId }, @@ -1127,7 +1129,7 @@ public class {Area}CommandTests(LiveTestFixture liveTestFixture, ITestOutputHelp var argsString = string.Join(" ", allArgs); var result = await CallToolAsync( - "azmcp_{area}_{resource}_show", + "azmcp_{Toolset}_{resource}_show", new() { { "args", argsString } @@ -1141,14 +1143,15 @@ public class {Area}CommandTests(LiveTestFixture liveTestFixture, ITestOutputHelp **5. Deploy and Test Resources** -Use the deployment script with your area: +Use the deployment script with your toolset: ```powershell -# Deploy test resources for your area -./eng/scripts/Deploy-TestResources.ps1 -Areas "{Area}" +# Deploy test resources for your toolset +./eng/scripts/Deploy-TestResources.ps1 -Tools "{Toolset}" # Run live tests -dotnet test --filter "Category=Live&Area={Area}" +pushd 'tools/Azure.Mcp.Tools.{Toolset}/tests/Azure.Mcp.Tools.{Toolset}.LiveTests' +dotnet test ``` Live test scenarios should include: @@ -1205,7 +1208,7 @@ When creating new C# files, start with only the using statements you actually ne ```csharp // Start minimal - only add what you actually use -using AzureMcp.Core.Commands; +using Azure.Mcp.Core.Commands; using Microsoft.Extensions.Logging; // Add more using statements as you implement the code @@ -1232,8 +1235,8 @@ The project already has `enable` in `Directory. Use these commands to detect and remove unused using statements: ```powershell -# Format specific area files (recommended during development) -dotnet format --include="areas/{area-name}/**/*.cs" --verbosity normal +# Format specific toolset files (recommended during development) +dotnet format --include="tools/Azure.Mcp.Tools.{Toolset}/**/*.cs" --verbosity normal # Format entire solution (use sparingly - takes longer) dotnet format ./AzureMcp.sln --verbosity normal @@ -1249,17 +1252,17 @@ dotnet build --verbosity normal | Select-String "warning" // Copied from another file but not all are needed using System.CommandLine; using System.CommandLine.Parsing; -using AzureMcp.Acr.Commands; // ← May not be needed -using AzureMcp.Acr.Options; // ← May not be needed -using AzureMcp.Acr.Options.Registry; // ← May not be needed -using AzureMcp.Acr.Services; +using Azure.Mcp.Tools.Acr.Commands; // ← May not be needed +using Azure.Mcp.Tools.Acr.Options; // ← May not be needed +using Azure.Mcp.Tools.Acr.Options.Registry; // ← May not be needed +using Azure.Mcp.Tools.Acr.Services; // ... 15 more using statements ``` ✅ **Start minimal and add as needed:** ```csharp // Only what's actually used in this file -using AzureMcp.Acr.Services; +using Azure.Mcp.Tools.Acr.Services; using Microsoft.Extensions.Logging; ``` @@ -1283,7 +1286,7 @@ The project checklist already includes cleaning up unused using statements: **Make this part of your development workflow:** 1. Write code with minimal using statements 2. Add using statements only as you need them -3. Run `dotnet format --include="areas/{area-name}/**/*.cs"` before committing +3. Run `dotnet format --include="tools/Azure.Mcp.Tools.{Toolset}/**/*.cs"` before committing 4. Use IDE features to clean up automatically ### Build Verification and AOT Compatibility @@ -1301,35 +1304,34 @@ dotnet test --filter "FullyQualifiedName~YourCommandTests" **2. AOT Compilation Verification:** -AOT (Ahead-of-Time) compilation is required for all new areas to ensure compatibility with native builds: +AOT (Ahead-of-Time) compilation is required for all new toolsets to ensure compatibility with native builds: ```powershell -# Test AOT compatibility - this is REQUIRED for all new areas +# Test AOT compatibility - this is REQUIRED for all new toolsets ./eng/scripts/Build-Local.ps1 -BuildNative ``` -**Expected Outcome**: If your area is properly implemented, the build should succeed. However, if AOT compilation fails (which is very likely for new areas), follow these steps: - +**Expected Outcome**: If your toolset is properly implemented, the build should succeed. However, if AOT compilation fails (which is very likely for new toolsets), follow these steps: **3. AOT Compilation Issue Resolution:** -When AOT compilation fails for your new area, you need to exclude it from native builds: +When AOT compilation fails for your new toolset, you need to exclude it from native builds: -**Step 1: Move area setup under BuildNative condition in Program.cs** +**Step 1: Move toolset setup under BuildNative condition in Program.cs** ```csharp -// Find your area setup call in Program.cs +// Find your toolset setup call in Program.cs // Move it inside the #if !BUILD_NATIVE block #if !BUILD_NATIVE - // ... other area setups ... - builder.Services.Add{YourArea}Setup(); // ← Move this line here + // ... other toolset setups ... + builder.Services.Add{YourToolset}Setup(); // ← Move this line here #endif ``` -**Step 2: Add ProjectReference-Remove condition in AzureMcp.Cli.csproj** +**Step 2: Add ProjectReference-Remove condition in Azure.Mcp.Server.csproj** ```xml - + - + ``` @@ -1348,7 +1350,7 @@ dotnet build - Third-party dependencies that don't support AOT - Dynamic JSON serialization without source generators -**Important**: This is a common and expected issue for new Azure service areas. The exclusion pattern is the standard solution and doesn't impact regular builds or functionality. +**Important**: This is a common and expected issue for new Azure service toolsets. The exclusion pattern is the standard solution and doesn't impact regular builds or functionality. ## Common Implementation Issues and Solutions @@ -1525,7 +1527,7 @@ catch (Exception ex) 6. Live Test Infrastructure: - Use minimal resource configurations for cost efficiency - - Follow naming conventions: `baseName` (most common) or `{baseName}-{area}` if needed + - Follow naming conventions: `baseName` (most common) or `{baseName}-{Toolset}` if needed - Include proper RBAC assignments for test application - Output all necessary identifiers for test consumption - Use appropriate Azure service API versions @@ -1551,14 +1553,14 @@ catch (Exception ex) - Use dashes in command group names 2. Always: - - Create a static {Area}OptionDefinitions class for the area + - Create a static {Toolset}OptionDefinitions class for the toolset - **For resource group handling**: Call `UseResourceGroup()` (optional) or `RequireResourceGroup()` (required). Never redefine the option or assign it manually. - **For Azure service commands**: Create test infrastructure (`test-resources.bicep`) before implementing live tests - Use OptionDefinitions for options - Follow exact file structure - Implement all base members - Add both unit and integration tests - - Register in area setup RegisterCommands method + - Register in toolset setup RegisterCommands method - Handle all error cases - Use primary constructors - Make command classes sealed @@ -1588,14 +1590,14 @@ catch (Exception ex) **Issue: Missing live test infrastructure for Azure service commands** - **Cause**: Forgetting to create `test-resources.bicep` template during development - **Solution**: Create Bicep template early in development process, not as an afterthought -- **Fix**: Create `areas/{area-name}/tests/test-resources.bicep` following established patterns +- **Fix**: Create `tools/Azure.Mcp.Tools.{Toolset}/tests/test-resources.bicep` following established patterns - **Prevention**: Check "Test Infrastructure Requirements" section at top of this document before starting implementation -- **Validation**: Run `az bicep build --file areas/{area-name}/tests/test-resources.bicep` to validate template +- **Validation**: Run `az bicep build --file tools/Azure.Mcp.Tools.{Toolset}/tests/test-resources.bicep` to validate template **Issue: Pipeline fails with "SelfContainedPostScript is not supported if there is no test-resources-post.ps1"** - **Cause**: Missing required `test-resources-post.ps1` file for Azure service commands - **Solution**: Create the post-deployment script file, even if it contains only the basic template -- **Fix**: Create `areas/{area-name}/tests/test-resources-post.ps1` using the standard template from existing areas +- **Fix**: Create `tools/Azure.Mcp.Tools.{Toolset}/tests/test-resources-post.ps1` using the standard template from existing toolsets - **Prevention**: All Azure service commands must include this file - it's required by the test infrastructure - **Note**: The file is mandatory even if no custom post-deployment logic is needed @@ -1605,7 +1607,7 @@ catch (Exception ex) - `using System.Text.Json;` for JSON serialization - `using Xunit;` for test framework - `using NSubstitute;` for mocking - - `using AzureMcp.Tests;` for test base classes + - `using Azure.Mcp.Tests;` for test base classes - **Fix**: Review test project template and ensure all necessary imports are included - **Prevention**: Use existing test projects as templates for import statements @@ -1651,7 +1653,7 @@ var subscriptionResource = await _subscriptionService.GetSubscription(subscripti **Issue: Bicep template validation fails** - **Cause**: Invalid parameter constraints, missing required properties, or API version issues -- **Solution**: Use `az bicep build --file areas/{area-name}/tests/test-resources.bicep` to validate template +- **Solution**: Use `az bicep build --file tools/Azure.Mcp.Tools.{Toolset}/tests/test-resources.bicep` to validate template - **Fix**: Check Azure Resource Manager template reference for correct syntax and required properties **Issue: Live tests fail with "Resource not found"** @@ -1668,14 +1670,14 @@ var subscriptionResource = await _subscriptionService.GetSubscription(subscripti - **Cause**: Parameter constraints, resource naming conflicts, or invalid configurations - **Solution**: - Review deployment logs and error messages - - Use `./eng/scripts/Deploy-TestResources.ps1 -Area {area-name} -Debug` for verbose deployment logs including resource provider errors. + - Use `./eng/scripts/Deploy-TestResources.ps1 -Toolset {Toolset} -Debug` for verbose deployment logs including resource provider errors. ### Live Test Project Configuration Issues **Issue: Live tests fail with "MCP server process exited unexpectedly" and "azmcp.exe not found"** -- **Cause**: Incorrect project configuration in `AzureMcp.{Area}.LiveTests.csproj` -- **Common Problem**: Referencing the area project (`AzureMcp.{Area}`) instead of the CLI project -- **Solution**: Live test projects must reference `AzureMcp.Cli.csproj` and include specific project properties +- **Cause**: Incorrect project configuration in `Azure.Mcp.Tools.{Toolset}.LiveTests.csproj` +- **Common Problem**: Referencing the toolset project (`Azure.Mcp.Tools.{Toolset}`) instead of the CLI project +- **Solution**: Live test projects must reference `Azure.Mcp.Server.csproj` and include specific project properties - **Required Configuration**: ```xml @@ -1689,16 +1691,16 @@ var subscriptionResource = await _subscriptionService.GetSubscription(subscripti - - + + ``` - **Key Requirements**: - `OutputType=Exe` - Required for live test execution - `IsTestProject=true` - Marks as test project - - Reference to `AzureMcp.Cli.csproj` - Provides the executable for MCP server - - Reference to area project - Provides the commands to test + - Reference to `Azure.Mcp.Server.csproj` - Provides the executable for MCP server + - Reference to toolset project - Provides the commands to test - **Common fixes**: - Adjust `@minLength`/`@maxLength` for service naming limits - Ensure unique resource names within scope @@ -1717,26 +1719,26 @@ var subscriptionResource = await _subscriptionService.GetSubscription(subscripti ### Service Implementation Issues **Issue: JSON Serialization Context missing new types** -- **Cause**: New model classes not included in `{Area}JsonContext` causing serialization failures +- **Cause**: New model classes not included in `{Toolset}JsonContext` causing serialization failures - **Solution**: Add all new model types to the JSON serialization context -- **Fix**: Update `{Area}JsonContext.cs` to include `[JsonSerializable(typeof(NewModelType))]` attributes +- **Fix**: Update `{Toolset}JsonContext.cs` to include `[JsonSerializable(typeof(NewModelType))]` attributes - **Prevention**: Always update JSON context when adding new model classes -**Issue: Area not registered in Program.cs** -- **Cause**: New area setup not added to `RegisterAreas()` method in `Program.cs` -- **Solution**: Add area registration to the array in alphabetical order -- **Fix**: Add `new AzureMcp.{Area}.{Area}Setup(),` to the `RegisterAreas()` return array -- **Prevention**: Follow the complete area setup checklist including Program.cs registration +**Issue: Toolset not registered in Program.cs** +- **Cause**: New toolset setup not added to `RegisterAreas()` method in `Program.cs` +- **Solution**: Add toolset registration to the array in alphabetical order +- **Fix**: Add `new Azure.Mcp.Tools.{Toolset}.{Toolset}Setup(),` to the `RegisterAreas()` return array +- **Prevention**: Follow the complete toolset setup checklist including Program.cs registration **Issue: Using required ResourceGroup option for optional filtering** - **Cause**: Using `OptionDefinitions.Common.ResourceGroup` which has `IsRequired = true` for commands that should support optional resource group filtering -- **Solution**: Create custom optional resource group option in area's OptionDefinitions +- **Solution**: Create custom optional resource group option in toolset's OptionDefinitions - **Fix**: - 1. Add `OptionalResourceGroup` option with `IsRequired = false` to `{Area}OptionDefinitions.cs` + 1. Add `OptionalResourceGroup` option with `IsRequired = false` to `{Toolset}OptionDefinitions.cs` 2. Override base `_resourceGroupOption` field with `new` keyword in command class - 3. Use the pattern: `private readonly new Option _resourceGroupOption = {Area}OptionDefinitions.OptionalResourceGroup;` + 3. Use the pattern: `private readonly new Option _resourceGroupOption = {Toolset}OptionDefinitions.OptionalResourceGroup;` - **Prevention**: Check if resource group should be optional (e.g., for list commands) and use the optional pattern -- **Examples**: Extension (AZQR), Monitor (Metrics), and ACR areas all implement this pattern correctly +- **Examples**: Extension (AZQR), Monitor (Metrics), and ACR toolsets all implement this pattern correctly **Issue: HandleException parameter mismatch** - **Cause**: Confusion about the correct HandleException signature @@ -1748,7 +1750,7 @@ var subscriptionResource = await _subscriptionService.GetSubscription(subscripti - **Solution**: Add `context.Activity?.WithSubscriptionTag(options);` or use `AddSubscriptionInformation(context.Activity, options);` **Issue: Service not registered in DI** -- **Cause**: Forgot to register service in area setup +- **Cause**: Forgot to register service in toolset setup - **Solution**: Add `services.AddSingleton();` in ConfigureServices ### Base Command Class Issues @@ -1758,7 +1760,7 @@ var subscriptionResource = await _subscriptionService.GetSubscription(subscripti - **Solution**: Use correct generic type: `ILogger>` **Issue: Missing using statements for TrimAnnotations** -- **Solution**: Add `using AzureMcp.Core.Commands;` for `TrimAnnotations.CommandAnnotations` +- **Solution**: Add `using Azure.Mcp.Core.Commands;` for `TrimAnnotations.CommandAnnotations` ### AOT Compilation Issues @@ -1767,16 +1769,16 @@ var subscriptionResource = await _subscriptionService.GetSubscription(subscripti - **Symptoms**: Build errors when running `./eng/scripts/Build-Local.ps1 -BuildNative` - **Solution**: Exclude non-AOT safe projects and packages for native builds - **Fix Steps**: - 1. **Move area setup under conditional compilation** in `core/src/AzureMcp.Cli/Program.cs`: + 1. **Move toolset setup under conditional compilation** in `servers/Azure.Mcp.Server/src/Program.cs`: ```csharp #if !BUILD_NATIVE - new AzureMcp.{Area}.{Area}Setup(), + new Azure.Mcp.Tools.{Toolset}.{Toolset}Setup(), #endif ``` - 2. **Add conditional project exclusion** in `core/src/AzureMcp.Cli/AzureMcp.Cli.csproj`: + 2. **Add conditional project exclusion** in `servers/Azure.Mcp.Server/src/Azure.Mcp.Server.csproj`: ```xml - + ``` 3. **Remove problematic package references** when building native (if applicable): @@ -1785,9 +1787,9 @@ var subscriptionResource = await _subscriptionService.GetSubscription(subscripti ``` -- **Examples**: See Cosmos, Monitor, Postgres, Search, VirtualDesktop, and BicepSchema areas in Program.cs and AzureMcp.Cli.csproj -- **Prevention**: Test AOT compilation early in development using `./eng/scripts/Build-Local.ps1 -BuildNative` -- **Note**: Areas excluded from AOT builds are still available in regular builds and deployments +- **Examples**: See Cosmos, Monitor, Postgres, Search, VirtualDesktop, and BicepSchema toolsets in Program.cs and Azure.Mcp.Server.csproj +-**Prevention**: Test AOT compilation early in development using `./eng/scripts/Build-Local.ps1 -BuildNative` +-**Note**: Toolsets excluded from AOT builds are still available in regular builds and deployments ## Checklist @@ -1800,7 +1802,7 @@ Before submitting: - [ ] Service interface and implementation complete - [ ] Unit tests cover all paths - [ ] Integration tests added -- [ ] Command registered in area setup RegisterCommands method +- [ ] Command registered in toolset setup RegisterCommands method - [ ] Follows file structure exactly - [ ] Error handling implemented - [ ] Documentation complete @@ -1809,13 +1811,13 @@ Before submitting: **⚠️ MANDATORY for any command that interacts with Azure resources:** -- [ ] **Live test infrastructure created** (`test-resources.bicep` template in `areas/{area-name}/tests`) -- [ ] **Post-deployment script created** (`test-resources-post.ps1` in `areas/{area-name}/tests` - required even if basic template) -- [ ] **Bicep template validated** with `az bicep build --file areas/{area-name}/tests/test-resources.bicep` -- [ ] **Live test resource template tested** with `./eng/scripts/Deploy-TestResources.ps1 -Area {area-name}` +- [ ] **Live test infrastructure created** (`test-resources.bicep` template in `tools/Azure.Mcp.Tools.{Toolset}/tests`) +- [ ] **Post-deployment script created** (`test-resources-post.ps1` in `tools/Azure.Mcp.Tools.{Toolset}/tests` - required even if basic template) +- [ ] **Bicep template validated** with `az bicep build --file tools/Azure.Mcp.Tools.{Toolset}/tests/test-resources.bicep` +- [ ] **Live test resource template tested** with `./eng/scripts/Deploy-TestResources.ps1 -Toolset {Toolset}` - [ ] **RBAC permissions configured** for test application in Bicep template (use appropriate built-in roles) - [ ] **Live test project configuration correct**: - - [ ] References `AzureMcp.Cli.csproj` (not just the area project) + - [ ] References `Azure.Mcp.Server.csproj` (not just the toolset project) - [ ] Includes `OutputType=Exe` property - [ ] Includes `IsTestProject=true` property - [ ] **Live tests use deployed resources** via `Settings.ResourceBaseName` pattern @@ -1825,10 +1827,10 @@ Before submitting: **Skip this section ONLY if your command does not interact with Azure resources (e.g., CLI wrappers, best practices tools).** ### Package and Project Setup -- [ ] Azure Resource Manager package added to both `Directory.Packages.props` and `AzureMcp.{Area}.csproj` +- [ ] Azure Resource Manager package added to both `Directory.Packages.props` and `Azure.Mcp.Tools.{Toolset}.csproj` - [ ] **Package version consistency**: Same version used in both `Directory.Packages.props` and project references - [ ] **Solution file integration**: Projects added to `AzureMcp.sln` with unique GUIDs (no GUID conflicts) -- [ ] **Area registration**: Added to `Program.cs` `RegisterAreas()` method in alphabetical order +- [ ] **Toolset registration**: Added to `Program.cs` `RegisterAreas()` method in alphabetical order - [ ] JSON serialization context includes all new model types ### Build and Code Quality @@ -1838,7 +1840,7 @@ Before submitting: - [ ] Code formatting applied with `dotnet format` - [ ] Spelling check passes with `.\eng\common\spelling\Invoke-Cspell.ps1` - [ ] **AOT compilation verified** with `./eng/scripts/Build-Local.ps1 -BuildNative` -- [ ] **Clean up unused using statements**: Run `dotnet format --include="areas/{area-name}/**/*.cs"` to remove unnecessary imports and ensure consistent formatting +- [ ] **Clean up unused using statements**: Run `dotnet format --include="tools/Azure.Mcp.Tools.{Toolset}/**/*.cs"` to remove unnecessary imports and ensure consistent formatting - [ ] Fix formatting issues with `dotnet format ./AzureMcp.sln` and ensure no warnings - [ ] Identify unused properties for Azure Resource with `.\eng\scripts\Check-Unused-ResourceProperties.ps1` @@ -1854,15 +1856,15 @@ Before submitting: - [ ] **CHANGELOG.md**: Add entry under "Unreleased" section describing the new command(s) - [ ] **docs/azmcp-commands.md**: Add command documentation with description, syntax, parameters, and examples -- [ ] **README.md**: Update the supported services table and add example prompts demonstrating the new command(s) in the appropriate area section -- [ ] **eng/vscode/README.md**: Update the VSIX README with new service area (if applicable) and add sample prompts to showcase new command capabilities +- [ ] **README.md**: Update the supported services table and add example prompts demonstrating the new command(s) in the appropriate toolset section +- [ ] **eng/vscode/README.md**: Update the VSIX README with new service toolset (if applicable) and add sample prompts to showcase new command capabilities - [ ] **docs/e2eTestPrompts.md**: Add test prompts for end-to-end validation of the new command(s) -- [ ] **.github/CODEOWNERS**: Add new area to CODEOWNERS file for proper ownership and review assignments +- [ ] **.github/CODEOWNERS**: Add new toolset to CODEOWNERS file for proper ownership and review assignments **Documentation Standards**: - Use consistent command paths in all documentation (e.g., `azmcp sql db show`, not `azmcp sql database show`) - Organize example prompts by service in README.md under service-specific sections (e.g., `### 🗄️ Azure SQL Database`) -- Place new commands in the appropriate area section, or create a new area section if needed +- Place new commands in the appropriate toolset section, or create a new toolset section if needed - Provide clear, actionable examples that users can run with placeholder values - Include parameter descriptions and required vs optional indicators in azmcp-commands.md - Keep CHANGELOG.md entries concise but descriptive of the capability added