Skill v1.0.0
currentTrusted Publisher100/100version: "1.0.0" name: azurebackup-add-tool description: 'Add a new tool/command to the Azure Backup MCP toolset. Covers the full lifecycle: command implementation, option definitions, service layer, input validation, unit tests, live tests, recorded test playback, CI validation, spell check, changelog entry, tool description evaluation, and PR checklist. USE WHEN: add new backup command, create backup tool, implement backup operation, new azurebackup command, add MCP tool for backup, new vault operation, new policy command, new governance command.' argument-hint: 'Describe the new Azure Backup tool to add (e.g., "add security configure-mua command")'
Add a New Tool to Azure Backup MCP
Purpose
Step-by-step workflow for adding a new command to the Azure Backup MCP toolset, ensuring it passes all validation gates before PR submission.
When to Use
- Adding a new
azmcp azurebackup <group> <operation>command - Extending an existing command group (vault, policy, protecteditem, etc.)
- Adding a new command group (security, compliance, etc.)
Prerequisites
.NET 10 SDKinstalled (seeglobal.json)- Azure authentication configured (
az login/Connect-AzAccount) - Repository cloned with
upstreamremote pointing tomicrosoft/mcp - Branch created from
upstream/main
Procedure
Phase 1: Implementation
Follow `/.github/skills/add-azure-mcp-tools/SKILL.md` as the authoritative guide. The Azure Backup toolset lives in tools/Azure.Mcp.Tools.AzureBackup/.
1a. Create Option Definitions
File: src/Options/{Group}/{Resource}{Operation}Options.cs
// Inherit from the appropriate base options classpublic class MyNewOptions : BaseAzureBackupOptions{public string? MyParam { get; set; }}
- Use
OptionDefinitions.Common.*for shared options (subscription, resourceGroup) - Use
AzureBackupOptionDefinitions.VaultandAzureBackupOptionDefinitions.VaultTypefor vault options - Add new options to
AzureBackupOptionDefinitionsif reusable across commands - Use
.AsRequired()/.AsOptional()extension methods
1b. Add Service Method
File: src/Services/IAzureBackupService.cs and src/Services/AzureBackupService.cs
- Add the interface method first
- Route to
rsvOpsordppOpsbased on vault type usingResolveVaultTypeAsync - For RSV-only operations, add to
IRsvBackupOperations/RsvBackupOperations - For DPP-only operations, add to
IDppBackupOperations/DppBackupOperations
1c. Implement the Command
File: src/Commands/{Group}/{Resource}{Operation}Command.cs
Required patterns:
- Use
[CommandMetadata(...)]attribute (not property overrides) - Sealed class with primary constructor
- Inject
ILogger<T>andIAzureBackupService - Override
RegisterOptions,BindOptions,ExecuteAsync - Add telemetry tags via
AzureBackupTelemetryTags.AddVaultTags(context.Activity, ...) - Call
HandleException(context, ex)in catch blocks
1d. Register the Command
File: src/AzureBackupSetup.cs
- Add
services.AddSingleton<MyNewCommand>()inConfigureServices - Add
group.AddCommand<MyNewCommand>(serviceProvider)inRegisterCommands - Create a new
CommandGroupif needed for a new group
1e. Register JSON Serialization Context
File: src/Commands/AzureBackupJsonContext.cs
- Add
[JsonSerializable(typeof(MyNewCommand.MyResultType))]for AOT safety
Phase 2: Input Validation
Before writing tests, validate all inputs are handled correctly:
Checklist:
- [ ] Required parameters throw
ArgumentExceptionwith clear message when missing - [ ] Subscription format validated (GUID only) via
ValidateSubscriptionFormat - [ ] Vault type normalized correctly (rsv/dpp case-insensitive)
- [ ] Enum/string parameters validated against allowed values with helpful error listing
- [ ] Null/empty strings handled with
ArgumentException.ThrowIfNullOrWhiteSpace - [ ] ARM resource IDs parsed safely with try-catch on
new ResourceIdentifier(...) - [ ] Error messages are actionable (tell user what to provide, not just what failed)
Phase 3: Unit Tests
File: tests/Azure.Mcp.Tools.AzureBackup.Tests/{Group}/{Resource}{Operation}CommandTests.cs
Required Test Methods
public sealed class MyNewCommandTests : CommandUnitTestsBase<MyNewCommand, IAzureBackupService>{[Fact] public void Constructor_InitializesCommandCorrectly()[Fact] public void BindOptions_BindsOptionsCorrectly()[Fact] public async Task ExecuteAsync_ValidInput_ReturnsExpectedResult()[Fact] public async Task ExecuteAsync_HandlesServiceErrors()[Fact] public async Task ExecuteAsync_DeserializationValidation()// Add per-parameter validation tests:[Theory][InlineData(null)][InlineData("")]public async Task ExecuteAsync_InvalidVault_ThrowsArgumentException(string? vault)// Add edge case tests specific to the command}
Run Unit Tests
dotnet test tools\Azure.Mcp.Tools.AzureBackup\tests\Azure.Mcp.Tools.AzureBackup.Tests `/p:NuGetAudit=false `--filter "Category!=Live&FullyQualifiedName~MyNewCommandTests"
Verify all tests pass before proceeding.
Phase 4: Live Tests
File: tests/Azure.Mcp.Tools.AzureBackup.Tests/AzureBackupCommandTests.cs
4a. Add Test Methods
Azure Backup live tests use [Fact] on a RecordedCommandTestsBase subclass with CallToolAsync. There is no [RecordedTest] attribute in this toolset.
[Fact]public async Task MyNewCommand_RsvVault(){var result = await CallToolAsync("azurebackup", "mygroup", "myop",new Dictionary<string, object>{["subscription"] = SubscriptionId,["resourceGroup"] = ResourceGroupName,["vault"] = DeploymentOutputs["AZUREBACKUP_RSV_VAULT_NAME"],// add other params});Assert.NotNull(result);// assert on result content}
- Use
[Fact]for all live tests (not[RecordedTest]— that attribute is not used in Azure Backup) - Use
[LiveTestOnly]alongside[Fact]for long-running E2E tests that cannot reliably replay - Use test resource values from
DeploymentOutputs(set intest-resources-post.ps1)
4b. Update Test Infrastructure (if needed)
If the new command requires new Azure resources:
- Edit
tests/test-resources.bicepto add the resource - Edit
tests/test-resources-post.ps1to output new deployment values - Deploy:
./eng/scripts/Deploy-TestResources.ps1 -Paths AzureBackup
4c. Record Live Tests
# Set to Record mode$settings = @{TestMode = "Record"SubscriptionId = "<your-sub>"TenantId = "<your-tenant>"ResourceGroupName = "<your-rg>"ResourceBaseName = "<your-base>"} | ConvertTo-Json$settings | Set-Content "tools\Azure.Mcp.Tools.AzureBackup\tests\Azure.Mcp.Tools.AzureBackup.Tests\.testsettings.json"# Kill any stale proxy/server processesStop-Process -Name "Azure.Sdk.Tools.TestProxy","azmcp" -Force -ErrorAction SilentlyContinue# Run tests in Record modedotnet test tools\Azure.Mcp.Tools.AzureBackup\tests\Azure.Mcp.Tools.AzureBackup.Tests `/p:NuGetAudit=false --filter "FullyQualifiedName~MyNewCommand"
4d. Push Recordings
# Push recorded sessions to azure-sdk-assets.proxy\Azure.Sdk.Tools.TestProxy push `-a tools\Azure.Mcp.Tools.AzureBackup\tests\Azure.Mcp.Tools.AzureBackup.Tests\assets.json
This updates the Tag field in assets.json. Commit the updated `assets.json`.
4e. Verify Playback
# Switch to Playback mode$settings = @{ TestMode = "Playback"; SubscriptionId = "..."; TenantId = "..."; ResourceGroupName = "..."; ResourceBaseName = "..." } | ConvertTo-Json$settings | Set-Content "tools\Azure.Mcp.Tools.AzureBackup\tests\Azure.Mcp.Tools.AzureBackup.Tests\.testsettings.json"Stop-Process -Name "Azure.Sdk.Tools.TestProxy","azmcp" -Force -ErrorAction SilentlyContinuedotnet test tools\Azure.Mcp.Tools.AzureBackup\tests\Azure.Mcp.Tools.AzureBackup.Tests `/p:NuGetAudit=false --filter "FullyQualifiedName~MyNewCommand"
All recorded tests must pass in Playback mode.
Phase 5: CI Validation Gates
Run these checks in order. All must pass before creating a PR.
5a. Build
dotnet build tools\Azure.Mcp.Tools.AzureBackup\src\Azure.Mcp.Tools.AzureBackup.csproj /p:NuGetAudit=false
5b. Format Check
dotnet format Microsoft.Mcp.slnx --verify-no-changes `--include "tools/Azure.Mcp.Tools.AzureBackup/**" `--exclude-diagnostics IL2026 IL3050
If it fails, fix with:
dotnet format Microsoft.Mcp.slnx `--include "tools/Azure.Mcp.Tools.AzureBackup/**" `--exclude-diagnostics IL2026 IL3050
5c. Full Unit Tests
dotnet test tools\Azure.Mcp.Tools.AzureBackup\tests\Azure.Mcp.Tools.AzureBackup.Tests /p:NuGetAudit=false
5d. Full Live Tests (Playback)
dotnet test tools\Azure.Mcp.Tools.AzureBackup\tests\Azure.Mcp.Tools.AzureBackup.Tests /p:NuGetAudit=false
5e. Spell Check
.\eng\common\spelling\Invoke-Cspell.ps1
If new Azure Backup-specific technical terms are flagged, add them to tools/Azure.Mcp.Tools.AzureBackup/cspell.yaml. Add cross-cutting terms used by multiple projects to .vscode/cspell.json.
5f. Full Build Verification
./eng/scripts/Build-Local.ps1 -VerifyNpx
5g. AOT/Native Build Verification
Azure Backup is marked IsAotCompatible=true, so also validate native compilation:
./eng/scripts/Build-Local.ps1 -BuildNative
If this fails for a new Azure SDK dependency, the toolset may need to be excluded from native builds (see docs/aot-compatibility.md).
Phase 6: Tool Description Evaluation
Run the ToolDescriptionEvaluator to verify the new tool's description is discoverable by AI agents.
$env:AOAI_ENDPOINT = "<your-aoai-endpoint>"$env:TEXT_EMBEDDING_API_KEY = "<your-key>"dotnet run --project eng/tools/ToolDescriptionEvaluator/src/ToolDescriptionEvaluator.csproj `-- --tool-name "azurebackup_<group>_<operation>"
Target: Top 3 ranking with confidence score >= 0.4.
If the score is low, improve the command's Description in the [CommandMetadata] attribute:
- Include key verbs users would say ("configure", "enable", "list", "show")
- Mention specific resource types ("vault", "policy", "protected item")
- Describe what the output looks like
- Re-run until the score meets the threshold
Phase 7: Documentation
7a. Update Command Reference
File: servers/Azure.Mcp.Server/docs/azmcp-commands.md
Add the new command in alphabetical order within the azurebackup section.
Then regenerate the commands metadata:
./eng/scripts/Update-AzCommandsMetadata.ps1
This is required for CI validation.
7b. Add Test Prompts
File: servers/Azure.Mcp.Server/docs/e2eTestPrompts.md
Add 2-3 natural language prompts that should trigger the new tool, in alphabetical order.
7c. Create Changelog Entry
Follow docs/changelog-entries.md instructions. Use the -ChangelogPath parameter pointing to servers/Azure.Mcp.Server/CHANGELOG.md.
Phase 8: PR Submission
Final Checklist
Before creating the PR, verify:
- [ ] Build passes:
dotnet buildsucceeds with 0 errors - [ ] Format clean:
dotnet format --verify-no-changespasses - [ ] All unit tests pass (including existing ones — no regressions)
- [ ] All live tests pass in Playback mode
- [ ] Recordings pushed and
assets.jsonupdated - [ ] Spell check passes:
Invoke-Cspell.ps1clean - [ ] ToolDescriptionEvaluator: Score >= 0.4, top 3 ranking
- [ ] Command registered in
AzureBackupSetup.cs - [ ] JSON context registered for AOT safety
- [ ] Telemetry tags added via
AzureBackupTelemetryTags - [ ] Documentation updated (commands.md, e2eTestPrompts.md, changelog, README.md, eng/vscode/README.md)
- [ ] Commands metadata regenerated via
Update-AzCommandsMetadata.ps1 - [ ] AOT/native build passes (
Build-Local.ps1 -BuildNative) - [ ] One tool per PR (don't bundle unrelated changes)
Create the PR
git add tools/Azure.Mcp.Tools.AzureBackup/ servers/Azure.Mcp.Server/ README.md eng/vscode/README.mdgit commit -m "feat(azurebackup): Add <group> <operation> command<description of what the command does>"git push origin <branch-name>
Reference: File Locations
tools/Azure.Mcp.Tools.AzureBackup/├── src/│ ├── AzureBackupSetup.cs # Register here│ ├── Commands/│ │ ├── AzureBackupJsonContext.cs # AOT registration│ │ └── {Group}/{Resource}{Operation}Command.cs # Command impl│ ├── Options/│ │ ├── AzureBackupOptionDefinitions.cs # Shared options│ │ └── {Group}/{Resource}{Operation}Options.cs # Command options│ ├── Services/│ │ ├── IAzureBackupService.cs # Interface│ │ ├── AzureBackupService.cs # Routing│ │ ├── RsvBackupOperations.cs # RSV impl│ │ └── DppBackupOperations.cs # DPP impl│ └── Models/│ └── AzureBackupTelemetryTags.cs # Telemetry└── tests/├── Azure.Mcp.Tools.AzureBackup.Tests/│ ├── {Group}/{Resource}{Operation}CommandTests.cs│ ├── AzureBackupCommandTests.cs # Add tests here│ └── assets.json # Recording tag├── test-resources.bicep # Azure infra└── test-resources-post.ps1 # Post-deploy
Reference: Good Examples
Study these existing implementations as templates:
- Simple get/list:
Commands/Vault/VaultGetCommand.cs - Create with validation:
Commands/Policy/PolicyCreateCommand.cs - Governance toggle:
Commands/Governance/GovernanceSoftDeleteCommand.cs - Security command:
Commands/Security/SecurityConfigureMuaCommand.cs - Unit tests:
tests/Azure.Mcp.Tools.AzureBackup.Tests/Policy/PolicyCreateCommandTests.cs