| 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 SDK installed (see global.json)
- Azure authentication configured (
az login / Connect-AzAccount)
- Repository cloned with
upstream remote pointing to microsoft/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
public class MyNewOptions : BaseAzureBackupOptions
{
public string? MyParam { get; set; }
}
- Use
OptionDefinitions.Common.* for shared options (subscription, resourceGroup)
- Use
AzureBackupOptionDefinitions.Vault and AzureBackupOptionDefinitions.VaultType for vault options
- Add new options to
AzureBackupOptionDefinitions if 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
rsvOps or dppOps based on vault type using ResolveVaultTypeAsync
- 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> and IAzureBackupService
- 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>() in ConfigureServices
- Add
group.AddCommand<MyNewCommand>(serviceProvider) in RegisterCommands
- Create a new
CommandGroup if 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:
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()
[Theory]
[InlineData(null)]
[InlineData("")]
public async Task ExecuteAsync_InvalidVault_ThrowsArgumentException(string? vault)
}
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"],
});
Assert.NotNull(result);
}
- 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 in test-resources-post.ps1)
4b. Update Test Infrastructure (if needed)
If the new command requires new Azure resources:
- Edit
tests/test-resources.bicep to add the resource
- Edit
tests/test-resources-post.ps1 to 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 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"
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 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.
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:
Create the PR
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>
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