microsoft/azurebackup-add-tool
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.
npx skills add https://github.com/microsoft/mcp --skill azurebackup-add-tool
Step-by-step workflow for adding a new command to the Azure Backup MCP toolset,
ensuring it passes all validation gates before PR submission.
azmcp azurebackup <group> <operation> command.NET 10 SDK installed (see global.json)az login / Connect-AzAccount)upstream remote pointing to microsoft/mcpupstream/mainFollow /.github/skills/add-azure-mcp-tools/SKILL.md as the authoritative guide.
The Azure Backup toolset lives in tools/Azure.Mcp.Tools.AzureBackup/.
File: src/Options/{Group}/{Resource}{Operation}Options.cs
// Inherit from the appropriate base options class
public class MyNewOptions : BaseAzureBackupOptions
{
public string? MyParam { get; set; }
}
OptionDefinitions.Common.* for shared options (subscription, resourceGroup)AzureBackupOptionDefinitions.Vault and AzureBackupOptionDefinitions.VaultType for vault optionsAzureBackupOptionDefinitions if reusable across commands.AsRequired() / .AsOptional() extension methodsFile: src/Services/IAzureBackupService.cs and src/Services/AzureBackupService.cs
rsvOps or dppOps based on vault type using ResolveVaultTypeAsyncIRsvBackupOperations / RsvBackupOperationsIDppBackupOperations / DppBackupOperationsFile: src/Commands/{Group}/{Resource}{Operation}Command.cs
Required patterns:
[CommandMetadata(...)] attribute (not property overrides)ILogger<T> and IAzureBackupServiceRegisterOptions, BindOptions, ExecuteAsyncAzureBackupTelemetryTags.AddVaultTags(context.Activity, ...)HandleException(context, ex) in catch blocksFile: src/AzureBackupSetup.cs
services.AddSingleton<MyNewCommand>() in ConfigureServicesgroup.AddCommand<MyNewCommand>(serviceProvider) in RegisterCommandsCommandGroup if needed for a new groupFile: src/Commands/AzureBackupJsonContext.cs
[JsonSerializable(typeof(MyNewCommand.MyResultType))] for AOT safetyBefore writing tests, validate all inputs are handled correctly:
Checklist:
ArgumentException with clear message when missingValidateSubscriptionFormatArgumentException.ThrowIfNullOrWhiteSpacenew ResourceIdentifier(...)File: tests/Azure.Mcp.Tools.AzureBackup.Tests/{Group}/{Resource}{Operation}CommandTests.cs
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
}
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.
File: tests/Azure.Mcp.Tools.AzureBackup.Tests/AzureBackupCommandTests.cs
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
}
[Fact] for all live tests (not [RecordedTest] — that attribute is not used in Azure Backup)[LiveTestOnly] alongside [Fact] for long-running E2E tests that cannot reliably replayDeploymentOutputs (set in test-resources-post.ps1)If the new command requires new Azure resources:
tests/test-resources.bicep to add the resourcetests/test-resources-post.ps1 to output new deployment values./eng/scripts/Deploy-TestResources.ps1 -Paths AzureBackup# 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 processes
Stop-Process -Name "Azure.Sdk.Tools.TestProxy","azmcp" -Force -ErrorAction SilentlyContinue
# Run tests in Record mode
dotnet test tools\Azure.Mcp.Tools.AzureBackup\tests\Azure.Mcp.Tools.AzureBackup.Tests `
/p:NuGetAudit=false --filter "FullyQualifiedName~MyNewCommand"
# 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.
# 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 SilentlyContinue
dotnet 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.
Run these checks in order. All must pass before creating a PR.
dotnet build tools\Azure.Mcp.Tools.AzureBackup\src\Azure.Mcp.Tools.AzureBackup.csproj /p:NuGetAudit=false
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
dotnet test tools\Azure.Mcp.Tools.AzureBackup\tests\Azure.Mcp.Tools.AzureBackup.Tests /p:NuGetAudit=false
dotnet test tools\Azure.Mcp.Tools.AzureBackup\tests\Azure.Mcp.Tools.AzureBackup.Tests /p:NuGetAudit=false
.\eng\common\spelling\Invoke-Cspell.ps1
If new technical terms are flagged, add them to .vscode/cspell.json.
./eng/scripts/Build-Local.ps1 -UsePaths -VerifyNpx
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).
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:
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.
File: servers/Azure.Mcp.Server/docs/e2eTestPrompts.md
Add 2-3 natural language prompts that should trigger the new tool, in alphabetical order.
Follow docs/changelog-entries.md instructions. Use the -ChangelogPath parameter pointing to
servers/Azure.Mcp.Server/CHANGELOG.md.
Before creating the PR, verify:
dotnet build succeeds with 0 errorsdotnet format --verify-no-changes passesassets.json updatedInvoke-Cspell.ps1 cleanAzureBackupSetup.csAzureBackupTelemetryTagsUpdate-AzCommandsMetadata.ps1Build-Local.ps1 -BuildNative)git add tools/Azure.Mcp.Tools.AzureBackup/ servers/Azure.Mcp.Server/ README.md eng/vscode/README.md
git commit -m "feat(azurebackup): Add <group> <operation> command
<description of what the command does>"
git push origin <branch-name>
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
Study these existing implementations as templates:
Commands/Vault/VaultGetCommand.csCommands/Policy/PolicyCreateCommand.csCommands/Governance/GovernanceSoftDeleteCommand.csCommands/Security/SecurityConfigureMuaCommand.cstests/Azure.Mcp.Tools.AzureBackup.Tests/Policy/PolicyCreateCommandTests.csTake microsoft/azurebackup-add-tool from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.