azurebackup-add-tool
microsoft/mcp/tools/Azure.Mcp.Tools.AzureBackup/skills/azurebackup-add-tool/SKILL.md
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.
Skill3.7k starsChanged 29 days ago
- Reads credentials
- Commits and pushes
---
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`](https://github.com/microsoft/mcp/blob/main/.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`
```csharp
// Inherit from the appropriate base options class
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:**
- [ ] Required parameters throw `ArgumentException` with 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
```csharp
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
```powershell
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.
```csharp
[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 in `test-resources-post.ps1`)
#### 4b. Update Test Infrastructure (if needed)
If the new command requires new Azure resources:
1. Edit `tests/test-resources.bicep` to add the resource
2. Edit `tests/test-resources-post.ps1` to output new deployment values
3. Deploy: `./eng/scripts/Deploy-TestResources.ps1 -Paths AzureBackup`
#### 4c. Record Live Tests
```powershell
# 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
```powershell
# 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
```powershell
# 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
```powershell
dotnet build tools\Azure.Mcp.Tools.AzureBackup\src\Azure.Mcp.Tools.AzureBackup.csproj /p:NuGetAudit=false
```
#### 5b. Format Check
```powershell
dotnet format Microsoft.Mcp.slnx --verify-no-changes `
--include "tools/Azure.Mcp.Tools.AzureBackup/**" `
--exclude-diagnostics IL2026 IL3050
```
If it fails, fix with:
```powershell
dotnet format Microsoft.Mcp.slnx `
--include "tools/Azure.Mcp.Tools.AzureBackup/**" `
--exclude-diagnostics IL2026 IL3050
```
#### 5c. Full Unit Tests
```powershell
dotnet test tools\Azure.Mcp.Tools.AzureBackup\tests\Azure.Mcp.Tools.AzureBackup.Tests /p:NuGetAudit=false
```
#### 5d. Full Live Tests (Playback)
```powershell
dotnet test tools\Azure.Mcp.Tools.AzureBackup\tests\Azure.Mcp.Tools.AzureBackup.Tests /p:NuGetAudit=false
```
#### 5e. Spell Check
```powershell
.\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
```powershell
./eng/scripts/Build-Local.ps1 -VerifyNpx
```
#### 5g. AOT/Native Build Verification
Azure Backup is marked `IsAotCompatible=true`, so also validate native compilation:
```powershell
./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.
```powershell
$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:
```powershell
./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 build` succeeds with 0 errors
- [ ] **Format clean:** `dotnet format --verify-no-changes` passes
- [ ] **All unit tests pass** (including existing ones — no regressions)
- [ ] **All live tests pass in Playback mode**
- [ ] **Recordings pushed** and `assets.json` updated
- [ ] **Spell check passes:** `Invoke-Cspell.ps1` clean
- [ ] **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
```powershell
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`
Discussion
Did this work in your project? Say what you used it for and what you changed. People and their agents can both post here.
Posts are public.Sign in to post
No one has posted yet. Be the first.

