# AGENTS.md ## Do - Use primary constructors for all C# classes - Use `System.Text.Json` - Make command classes sealed unless designed for inheritance - Make members static when possible for AOT compatibility - Put each class and interface in separate files - Use `subscription` parameter (never `subscriptionId`) - supports both IDs and names - Use `resourceGroup` (not `resourceGroupName`) - Use singular nouns for resource names (e.g., `server`, not `serverName`) - Run `dotnet build` after making changes - Follow the `{Resource}{Operation}Command` naming pattern - Use the two-generic pattern `SubscriptionCommand` for new commands - Use `[Option]` attributes on flat POCO option classes for option declaration - Use `ISubscriptionOption` interface on options classes that need subscription/tenant - Always call `HandleException(context, ex)` in catch blocks - Include live tests for all commands that interact with Azure resources - Create Bicep templates for Azure service commands (`test-resources.bicep`) - Include post-deployment scripts (`test-resources-post.ps1`) - Record all live tests according to the guidelines in `/docs/recorded-tests.md` - Submit one tool per pull request - Use `BaseAzureResourceService` for Resource Graph queries when possible - Register all response models in JSON serialization context for AOT safety - Register all commands in the appropriate Setup.cs file - Use concatenated lowercase for command group names (no dashes) - Prefer file-scoped changes over project-wide modifications when possible - Always review your own code for consistency, maintainability, and testability - Always ask for clarifications if the request is ambiguous or lacks sufficient context - Write transport-agnostic commands that work in both stdio and HTTP modes - Keep commands stateless and thread-safe for multi-user remote scenarios - Test commands with different RBAC permissions for OBO scenarios ## Don't - Use `subscriptionId` parameter name - Add unnecessary "-name" suffixes (use `--account` vs `--account-name`) - Use the old one-generic `RegisterOptions`/`BindOptions` pattern for new commands (see `/docs/option-conversion.md`) - Use `OptionDefinitions` static classes for new commands (use `[Option]` attributes instead) - Use options class inheritance hierarchies (use flat POCOs with interface constraints) - Skip live tests, live test infrastructure, or test recordings for Azure service commands - Redefine base class properties in Options classes - Leave commands unregistered - Skip error handling or comprehensive tests - Use dashes in command group names (use concatenated lowercase) - Make project-wide changes when file-scoped changes suffice - Check transport type in commands (stdio vs HTTP) - Store per-request state in command instance fields - Access HttpContext directly from commands - Make transport-specific decisions in command logic - Assume single-user scenarios when implementing services - Use `git stash` in linked worktrees; the stash stack is shared across worktrees and can collide during concurrent work ## Commands ### File-scoped commands (preferred for faster feedback) ```powershell # Build single project dotnet build tools/Azure.Mcp.Tools.Storage/src # Format specific files dotnet format --include="tools/Azure.Mcp.Tools.Storage/**/*.cs" # Test specific class dotnet test --filter "FullyQualifiedName~StorageAccountListCommandTests" # Type check and validate ./eng/scripts/Build-Local.ps1 -VerifyNpx # Note: Don't run local builds to check pipeline YAML files (e.g., files in `eng/pipelines/` with `.yml` extension) ``` ### Project-wide commands (use sparingly) ```powershell # Full build (when explicitly requested) dotnet build # All tests (when needed) ./eng/scripts/Test-Code.ps1 # AOT compatibility check (for new toolsets) ./eng/scripts/Build-Local.ps1 -BuildNative ``` ## Safety and Permissions ### Allowed without prompt - Read files, list directories - Single file builds (`dotnet build path/to/project`) - Code formatting (`dotnet format --include="specific/path/**"`) - Spelling checks (`.\eng\common\spelling\Invoke-Cspell.ps1`) - Unit tests for specific classes - Creating/updating documentation ### Ask first - Installing new packages or dependencies - Running project-wide builds or tests - Modifying `.csproj`, `.slnx`, or configuration files - Deploying test resources (`New-TestResources.ps1`) - Making breaking changes to public APIs - Adding new toolsets to the solution ## Project Overview Microsoft MCP (Model Context Protocol) servers provide AI agents with structured access to Azure, Microsoft Fabric, and other Microsoft services. This repository contains the core libraries, multiple MCP servers, service-specific tools, and comprehensive testing infrastructure for building agent-integrated Microsoft service interactions. **Key Components:** - **Azure MCP Server**: Complete Azure service integration with 100+ tools - **Microsoft Fabric MCP Server**: Fabric workspace and data platform operations - **Core Libraries**: Shared infrastructure for command patterns, authentication, and MCP protocol - **Toolsets**: Individual Azure service implementations (Storage, SQL, KeyVault, etc.) - **Engineering System**: Build pipelines, testing infrastructure, and deployment automation ## Project Structure ### Key directories - `core/Azure.Mcp.Core/` - Azure MCP core library with shared infrastructure - `servers/Azure.Mcp.Server/` - Main Azure MCP server implementation - `tools/Azure.Mcp.Tools.{Service}/` - Individual service toolsets (Storage, SQL, etc.) - `eng/scripts/` - Build, test, and deployment PowerShell scripts - `.github/skills/add-azure-mcp-tools/SKILL.md` - Implementation guide for new commands - `docs/option-conversion.md` - Guide for converting to two-generic option pattern - `CONTRIBUTING.md` - Contribution guidelines and workflows ### Good examples to follow - Command implementation: `tools/Azure.Mcp.Tools.Storage/src/Commands/Account/AccountGetCommand.cs` - Service pattern: `tools/Azure.Mcp.Tools.Storage/src/Services/StorageService.cs` - Unit tests: `tools/Azure.Mcp.Tools.Storage/tests/Azure.Mcp.Tools.Storage.Tests/Account/StorageAccountGetCommandTests.cs` - Integration tests: `tools/Azure.Mcp.Tools.Storage/tests/Azure.Mcp.Tools.Storage.Tests/StorageCommandTests.cs` - Live test infrastructure: `tools/Azure.Mcp.Tools.Storage/tests/test-resources.bicep` - Option conversion guide: `docs/option-conversion.md` ### Legacy patterns to avoid - Old one-generic `RegisterOptions`/`BindOptions` pattern (use two-generic with `[Option]` attributes) - `OptionDefinitions` static classes with `.AsRequired()`/`.AsOptional()` (use `[Option]` on flat POCOs) - Options class inheritance hierarchies (use flat classes with interface constraints) - Commands without proper error handling - Missing live test infrastructure for Azure services - Non-sealed command classes ## Development Environment Setup ### Prerequisites 1. **Visual Studio Code**: [VS Code Stable](https://code.visualstudio.com/download) or [Insiders](https://code.visualstudio.com/insiders) 2. **GitHub Copilot**: Install [GitHub Copilot](https://marketplace.visualstudio.com/items?itemName=GitHub.copilot) and [GitHub Copilot Chat](https://marketplace.visualstudio.com/items?itemName=GitHub.copilot-chat) extensions 3. **Node.js**: [Latest Node.js LTS](https://nodejs.org/en/download) (ensure `node` and `npm` are in PATH) 4. **PowerShell**: [PowerShell 7.0+](https://learn.microsoft.com/powershell/scripting/install/installing-powershell) (required for build/test scripts) 5. **.NET SDK**: .NET 10 SDK (configured in `global.json`) 6. **Azure PowerShell**: For live tests - [Install Azure PowerShell](https://learn.microsoft.com/powershell/azure/install-azure-powershell) 7. **Azure Bicep**: For test infrastructure - [Install Azure Bicep](https://learn.microsoft.com/azure/azure-resource-manager/bicep/install#install-manually) ### Quick Start Commands ```powershell # Clone and build the project git clone https://github.com/microsoft/mcp.git cd mcp dotnet build # Verify everything works ./eng/scripts/Build-Local.ps1 -VerifyNpx # Run unit tests for specific toolset ./eng/scripts/Test-Code.ps1 -Paths Storage # Run all unit tests ./eng/scripts/Test-Code.ps1 ``` ## API Docs and References - API documentation: `/servers/Azure.Mcp.Server/docs/azmcp-commands.md` - Complete command reference - Implementation guide: `/.github/skills/add-azure-mcp-tools/SKILL.md` - Step-by-step command creation - Option conversion: `/docs/option-conversion.md` - Converting to two-generic option pattern - Test prompts: `/servers/Azure.Mcp.Server/docs/e2eTestPrompts.md` - Example prompts for testing - Recorded tests: `/docs/recorded-tests.md` - Guide for converting live tests to recorded (playback) tests - Contributing guide: `/CONTRIBUTING.md` - Development workflow and standards - Code guidelines: `/.github/copilot-instructions.md` - Specific coding standards ## When Stuck - Ask clarifying questions about Azure service requirements or command patterns - Propose a short plan before implementing complex features - Reference existing commands in similar services as templates - Check `/.github/skills/add-azure-mcp-tools/SKILL.md` for implementation patterns - Use GitHub Copilot Chat with `"create [service] [resource] [operation] command using /skills/add-azure-mcp-tools as a reference"` - Check `/docs/option-conversion.md` for the two-generic option pattern ## PR Checklist - Format and type check: `dotnet format && dotnet build` - all green - Unit tests: Add comprehensive tests following existing patterns - Live test infrastructure: Include Bicep template and post-deployment script for Azure services - Live tests: Include live tests for all commands that interact with Azure resources - Recorded tests: All live tests **must** be recorded for playback (see `/docs/recorded-tests.md`) - Documentation: Update `/servers/Azure.Mcp.Server/docs/azmcp-commands.md` and add test prompts to `/servers/Azure.Mcp.Server/docs/e2eTestPrompts.md` - Tool validation (optional for Fabric MCP tools): Run `ToolDescriptionEvaluator` for command descriptions (target: top 3 ranking, ≥0.4 confidence) - Spelling check: `.\eng\common\spelling\Invoke-Cspell.ps1` - Changelog: Create changelog entry YAML file if the change is a new feature, bug fix, or breaking change. See `/docs/changelog-entries.md` for instructions. Always use the `-ChangelogPath` parameter (e.g., `/servers/Azure.Mcp.Server/CHANGELOG.md` or `/servers/Fabric.Mcp.Server/CHANGELOG.md`). - One tool per PR: Submit single toolsets for faster review cycles ## Architecture and Project Structure ### Repository Organization ``` ├── core/ # Core libraries and shared components │ ├── Azure.Mcp.Core/ # Azure MCP core library │ ├── Microsoft.Mcp.Core/ # Base MCP protocol implementation │ └── Fabric.Mcp.Core/ # Fabric-specific core (extends Azure.Mcp.Core) ├── servers/ # Individual MCP servers │ ├── Azure.Mcp.Server/ # Azure MCP server implementation │ ├── Fabric.Mcp.Server/ # Microsoft Fabric MCP server │ └── Template.Mcp.Server/ # Template for new MCP servers ├── tools/ # Service-specific toolset implementations │ ├── Azure.Mcp.Tools.Storage/ # Azure Storage operations │ ├── Azure.Mcp.Tools.KeyVault/ # Azure Key Vault operations │ ├── Azure.Mcp.Tools.Sql/ # Azure SQL operations │ └── [60+ other Azure services] # Each Azure service has its own toolset ├── eng/ # Engineering system and build infrastructure │ ├── scripts/ # Build, test, and deployment scripts │ ├── pipelines/ # Azure DevOps pipeline definitions │ └── tools/ # Development and validation tools └── docs/ # Documentation and implementation guides ``` ### Toolset Architecture Pattern Each Azure service follows a consistent pattern: ``` Azure.Mcp.Tools.{Service}/ ├── src/ │ ├── Commands/ # Command implementations │ │ └── {Resource}/ # Resource-specific commands │ ├── Services/ # Service layer implementations │ ├── Options/ # Command option definitions │ ├── Models/ # Data models and DTOs │ └── {Service}Setup.cs # Service registration and configuration └── tests/ ├── Azure.Mcp.Tools.{Service}.Tests/ # Unit tests (no Azure resources) and Integration tests (requires Azure) ├── test-resources.bicep # Test infrastructure template └── test-resources-post.ps1 # Post-deployment setup script ``` ### Command Naming Convention Commands follow the pattern: `azmcp ` ```bash # Examples azmcp storage account get # Get storage accounts azmcp sql database show # Show SQL database details azmcp keyvault secret get # Get Key Vault secret azmcp resourcegroup list # List resource groups ``` ## Build Commands and Development Workflow ### Core Build Commands ```powershell # Basic build (fastest for development) dotnet build # Full verification build (recommended before PR) ./eng/scripts/Build-Local.ps1 -VerifyNpx # AOT-compatible build (tests native compilation) ./eng/scripts/Build-Local.ps1 -BuildNative # Build with debugging symbols ./eng/scripts/Build-Local.ps1 # Docker image build ./eng/scripts/Build-Docker.ps1 -ServerName "Azure.Mcp.Server" ``` ### Testing Commands ```powershell # Unit tests only (no Azure resources required) ./eng/scripts/Test-Code.ps1 # Specific toolset unit tests ./eng/scripts/Test-Code.ps1 -Paths Storage, KeyVault # Deploy test infrastructure for live tests eng/common/TestResources/New-TestResources.ps1 -TestResourcesDirectory tools/Azure.Mcp.Tools.Storage # Live tests (requires Azure authentication and resources) ./eng/scripts/Test-Code.ps1 -TestType Live -Paths Storage # Run tests from specific directory pushd 'tools/Azure.Mcp.Tools.Storage/tests/Azure.Mcp.Tools.Storage.Tests' dotnet test --filter "FullyQualifiedName~StorageAccountGetCommandTests" popd ``` ### Code Quality and Validation ```powershell # Format code and remove unused using statements dotnet format # Format specific toolset dotnet format --include="tools/Azure.Mcp.Tools.Storage/**/*.cs" # Spelling check .\eng\common\spelling\Invoke-Cspell.ps1 # AOT compatibility analysis ./eng/scripts/Analyze-AOT-Compact.ps1 # Tool description quality validation (optional for Fabric MCP tools) pushd 'eng/tools/ToolDescriptionEvaluator/src' dotnet run -- --validate --tool-description "Your command description" --prompt "user query" --test-single-tool 'your-tool-name' popd ``` ## Testing Strategy and Patterns ### Unit Testing Requirements All commands must include comprehensive unit tests: ```csharp // Required test patterns for every command [Fact] public void Constructor_InitializesCommandCorrectly() [Theory] public async Task ExecuteAsync_ValidatesInputCorrectly(string args, bool shouldSucceed) [Fact] public async Task ExecuteAsync_DeserializationValidation() [Fact] public async Task ExecuteAsync_HandlesServiceErrors() [Fact] public void BindOptions_BindsOptionsCorrectly() ``` Command unit tests should extend `SubscriptionCommandUnitTestsBase` for subscription commands or `CommandUnitTestsBase` for non-subscription commands. ### Live Testing Requirements Azure service commands require live tests to validate functionality against actual Azure resources. Live tests must be recorded for playback using `RecordedCommandTestsBase`. See `/docs/recorded-tests.md` for the full recording workflow, sanitizer configuration, and migration guide. Tests for tools marked `LocalRequired = true` need special handling in classes extending `RecordedCommandTestsBase`. Start each such test with `AssertLocalToolIsUnavailableInHttpMode(toolName)` and return early when it returns `true`; the helper verifies that the tool is unavailable in remote HTTP mode before local-only behavior is tested in other transports. ### Live Test Infrastructure Azure service commands require Bicep templates for test resources: ```powershell # Deploy test infrastructure eng/common/TestResources/New-TestResources.ps1 -TestResourcesDirectory tools/Azure.Mcp.Tools.{Toolset} # Required files for Azure service toolsets: # - tools/Azure.Mcp.Tools.{Toolset}/tests/test-resources.bicep # - tools/Azure.Mcp.Tools.{Toolset}/tests/test-resources-post.ps1 ``` ### Authentication for Live Tests ```powershell # Azure authentication setup Connect-AzAccount az login # Test resource deployment with proper RBAC eng/common/TestResources/New-TestResources.ps1 -TestResourcesDirectory tools/Azure.Mcp.Tools.Storage ``` ### Testing with vally When testing the MCP server with vally, assume that the user has already been authenticated. Assume that `az login` has been called. DO NOT call `subscription_list`. ## Code Style and Standards ### C# Coding Standards - **Always use primary constructors** for dependency injection - **Always use `System.Text.Json`** over Newtonsoft.Json - **Make all command classes sealed** unless designed for inheritance - **Always make members static** when possible for AOT compatibility - **Put new classes and interfaces in separate files** - **Always run `dotnet build`** after making changes - **All generated code must be AOT-safe** ### File and Class Naming Patterns ```csharp // Command naming: {Resource}{Operation}Command public sealed class StorageAccountGetCommand // ✅ Correct public sealed class GetStorageAccountCommand // ❌ Wrong order // Options naming: {Resource}{Operation}Options public class StorageAccountGetOptions // ✅ Correct // Test naming: {Command}Tests public class StorageAccountGetCommandTests // ✅ Correct ``` ### Option Handling Pattern (Two-Generic) ```csharp // Options are flat POCOs with [Option] attributes — no RegisterOptions/BindOptions needed public class AccountGetOptions : ISubscriptionOption { [Option(Description = "The name of the Azure Storage account.")] public string? Account { get; set; } [Option(Description = OptionDescriptions.Subscription)] public string? Subscription { get; set; } [Option(Description = OptionDescriptions.Tenant)] public string? Tenant { get; set; } } // Commands use two-generic base: SubscriptionCommand public sealed class AccountGetCommand( ILogger logger, IStorageService storageService, ISubscriptionResolver subscriptionResolver) : SubscriptionCommand(subscriptionResolver) { // ExecuteAsync receives pre-bound, pre-validated options public override async Task ExecuteAsync( CommandContext context, AccountGetOptions options, CancellationToken cancellationToken) { // options are already bound — just use them directly } internal record AccountGetResult(List Accounts); } ``` > See `/docs/option-conversion.md` for the full conversion guide from one-generic to two-generic pattern. ### Parameter Naming Standards - **Use `subscription`** (never `subscriptionId`) - supports both IDs and names - **Use `resourceGroup`** (not `resourceGroupName`) - **Use singular nouns** for resource names (e.g., `server`, not `serverName`) - **Remove unnecessary "-name" suffixes** (e.g., `--account` vs `--account-name`) ## Error Handling Patterns ### Standard Error Response Format ```csharp // Override error handling for service-specific context protected override string GetErrorMessage(Exception ex) => ex switch { Azure.RequestFailedException reqEx when reqEx.Status == 404 => "Resource not found. Verify the resource exists and you have access.", Azure.RequestFailedException reqEx when reqEx.Status == 403 => $"Authorization failed accessing the resource. Details: {reqEx.Message}", Azure.Identity.AuthenticationFailedException => "Authentication failed. Please run 'az login' to sign in.", _ => base.GetErrorMessage(ex) }; protected override int GetStatusCode(Exception ex) => ex switch { Azure.RequestFailedException reqEx => reqEx.Status, Azure.Identity.AuthenticationFailedException => 401, ValidationException => 400, _ => base.GetStatusCode(ex) }; ``` ### Exception Handling in Commands ```csharp try { // Command execution logic var results = await service.GetResourcesAsync(options.Subscription!, cancellationToken); context.Response.Results = ResponseResult.Create(new(results ?? []), ServiceJsonContext.Default.CommandResult); } catch (Exception ex) { _logger.LogError(ex, "Error in {Operation}. Subscription: {Subscription}", Name, options.Subscription); HandleException(context, ex); // Always call base handler } ``` **DO NOT** log `{@Options}` as this may log sensitive information. Only log parameters that are known to be safe. ## Service Implementation Patterns ### Base Service Classes Choose the appropriate base class based on operations: **For Azure Resource Read Operations (recommended):** ```csharp public class StorageService(IAzureService azureService) : BaseAzureResourceService(azureService), IStorageService { public async Task> ListAccountsAsync( string subscription, string? resourceGroup, CancellationToken cancellationToken = default) { return await ExecuteResourceQueryAsync( "Microsoft.Storage/storageAccounts", resourceGroup, subscription, null, ConvertToStorageAccountModel, cancellationToken: cancellationToken); } } ``` **For Azure Resource Write Operations:** ```csharp public class StorageService(IAzureService azureService) : BaseAzureService(azureService), IStorageService { public async Task CreateStorageAccount( string account, string resourceGroup, string location, string subscription, string? sku = null, string? accessTier = null, bool? enableHierarchicalNamespace = null, string? tenant = null, CancellationToken cancellationToken = default) { var subscriptionResource = await AzureService.GetSubscription(subscription, tenant, cancellationToken: cancellationToken); // Use subscriptionResource for write operations } } ``` ### JSON Serialization Context (AOT Requirement) ```csharp // All response models must be registered for AOT compatibility [JsonSerializable(typeof(StorageAccountGetCommand.StorageAccountListCommandResult))] [JsonSerializable(typeof(StorageAccount))] [JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)] internal partial class StorageJsonContext : JsonSerializerContext; // Usage in commands context.Response.Results = ResponseResult.Create(new(results), StorageJsonContext.Default.StorageAccountGetCommandResult); ``` ### Long-running operations Long-running operations (at this time) don't offer the ability to configure polling intervals, and even if they were able to there is a limit on how small of a polling interval can be used. Due to this, to prevent long-running operations with a significant number of polls from wasting CPU time waiting during testing, all long-running operations should use a two call pattern. The first call is the service method starting the polling operation, that should pass `WaitUntil.Started` to simply begin the operation. Then waiting for completion should call `BaseAzureService.WaitForLroCompletionAsync` to wait for completion in a way that testing can ignore the polling interval to prevent CPU wait loops that aren't necessary when playback testing. ```csharp var lroOperation = Service.LroAsync(WaitUntil.Started, cancellationToken); await WaitForLroCompletionAsync(lroOperation, cancellationToken); ``` ## Adding New Commands and Services ### Development Process 1. **Create issue**: "Add command: azmcp [service] [resource] [operation]" 2. **Use Copilot for generation**: Execute in Copilot Chat: `"create [service] [resource] [operation]" command using "/skills add-azure-mcp-tools" as a reference` 3. **Follow implementation guidelines** in `/.github/skills/add-azure-mcp-tools/SKILL.md` and **two-generic pattern** in `/docs/option-conversion.md` 4. **Create live test infrastructure** (if Azure service): Bicep template and post-deployment script 5. **Submit one tool per pull request** for faster review cycles ### Required Files for New Commands ``` tools/Azure.Mcp.Tools.{Service}/ ├── src/ │ ├── Options/{Resource}/{Operation}Options.cs # Flat POCO with [Option] attributes │ ├── Commands/{Resource}/{Resource}{Operation}Command.cs # Two-generic command │ ├── Services/I{Service}Service.cs # Service interface │ ├── Services/{Service}Service.cs # Service implementation │ └── Commands/{Service}JsonContext.cs # JSON serialization context └── tests/ ├── Azure.Mcp.Tools.{Service}.Tests/{Resource}/{Resource}{Operation}CommandTests.cs # Unit tests (no Azure resources) ├── Azure.Mcp.Tools.{Service}.Tests/{Service}CommandTests.cs # Integration tests (requires Azure) ├── test-resources.bicep # Test infrastructure (Azure services only) └── test-resources-post.ps1 # Post-deployment script (Azure services only) ``` ### Tool Description Quality Validation ```powershell # Validate command descriptions for AI agent compatibility pushd 'eng/tools/ToolDescriptionEvaluator/src' # Single prompt validation dotnet run -- --validate --tool-description "Get storage accounts in a subscription" --prompt "show me my storage accounts" # Multiple prompt validation dotnet run -- --validate \ --tool-description "Get storage accounts in a subscription" \ --prompt "show storage accounts" \ --prompt "list my storage" \ --prompt "what storage do I have" # Custom files validation dotnet run -- --tools-file my-tools.json --prompts-file my-prompts.md popd # Target: Top 3 ranking and confidence score ≥ 0.4 (optional for Fabric MCP tools) ``` ## Local Development and Testing ### Running Azure MCP Server Locally mcp.json configuration for local development: ```json { "servers": { "azure-mcp-server": { "type": "stdio", "command": "C:/code/mcp/servers/Azure.Mcp.Server/bin/Debug/net10.0/azmcp.exe", "args": ["server", "start"] } } } ``` ### Server Mode Configurations **Namespace filtering** (specific services only): ```json "args": ["server", "start", "--namespace", "storage", "--namespace", "keyvault"] ``` **Namespace proxy mode** (groups tools, helpful for VS Code 128-tool limit): ```json "args": ["server", "start", "--mode", "namespace"] ``` **Single tool mode** (single "azure" tool with internal routing): ```json "args": ["server", "start", "--mode", "single"] ``` **Combined mode** (filter + proxy): ```json "args": ["server", "start", "--namespace", "storage", "--mode", "namespace"] ``` ### Docker Development ```powershell # Build local Docker image ./eng/scripts/Build-Docker.ps1 -ServerName "Azure.Mcp.Server" # Use in mcp.json { "servers": { "Azure MCP Server": { "command": "docker", "args": ["run", "-i", "--rm", "--env-file", "/path/to/.env", "azure-sdk/azure-mcp:"] } } } ``` ## Performance and Compatibility ### AOT (Ahead-of-Time) Compilation All new toolsets must be AOT-compatible or excluded from native builds: ```powershell # Test AOT compatibility ./eng/scripts/Build-Local.ps1 -BuildNative # If AOT fails (common for new Azure services), exclude toolset: # 1. Move setup call in Program.cs under #if !BUILD_NATIVE # 2. Add ProjectReference-Remove condition in Azure.Mcp.Server.csproj ``` ### Caching and Performance - Use `ICacheService` for expensive Azure operations - Implement `BaseAzureResourceService` for efficient Resource Graph queries ## Remote MCP Server Architecture Azure MCP Server supports **stdio** (local) and **HTTP** (remote) transports with different authentication models. ### Key Differences: Stdio vs Remote HTTP | Aspect | Stdio Mode | Remote HTTP Mode | |--------|-----------|------------------| | **Concurrency** | Single user | Multiple concurrent users | | **State Management** | Can use instance fields | Must be stateless | | **Deployment** | Local binaries | Cloud hosting (App Service, AKS) | | **Configuration** | Simple (no auth) | Requires Entra ID app registration | ### Authentication Strategies **On-Behalf-Of (OBO) Flow:** - Per-user authorization with audit trails - User's RBAC permissions enforced - Requires API permissions and admin consent - Command: `--run-as-remote-http-service --outgoing-auth-strategy UseOnBehalfOf` **Hosting Environment Identity:** - Service-level permissions using Managed Identity - Simpler configuration, no token exchange overhead - All users share server's permissions - Command: `--run-as-remote-http-service --outgoing-auth-strategy UseHostingEnvironmentIdentity` ### Command Implementation for Remote Mode **Critical Requirements:** - Write transport-agnostic commands (work in both stdio and HTTP modes) - Use `IAzureTokenCredentialProvider` for all Azure authentication - Keep commands stateless and thread-safe (no instance field state) - Test with different RBAC permissions for OBO scenarios - Provide context-aware error messages for remote scenarios **Key Patterns:** ```csharp // ✅ Correct: Authentication provider handles both modes var credential = await GetCredentialAsync(tenant, CancellationToken.None); var armClient = new ArmClient(credential); // ❌ Wrong: Don't check transport type or access HttpContext if (Environment.GetEnvironmentVariable("ASPNETCORE_URLS") != null) { } var httpContext = _httpContextAccessor.HttpContext; ``` ### Security Best Practices 1. Always use HTTPS in production 2. Implement least privilege RBAC 3. Use OBO for multi-tenant scenarios (preserves user identity) 4. Secure configuration secrets with Azure Key Vault 5. Enable Application Insights for monitoring 6. Validate token claims (audience, issuer, scopes) 7. Use Managed Identity when possible ## External MCP Server Integration The Azure MCP Server can proxy to external MCP servers via `registry.json`: ```json // core/Azure.Mcp.Core/src/Areas/Server/Resources/registry.json { "servers": { "documentation": { "url": "https://learn.microsoft.com/api/mcp", "description": "Search official Microsoft/Azure documentation" }, "github-server": { "type": "stdio", "command": "npx", "args": ["-y", "@modelcontextprotocol/server-github@latest"], "description": "GitHub repository operations" } } } ``` ## Documentation and Compliance ### Required Documentation Updates When adding new commands: 1. **Update `/servers/Azure.Mcp.Server/docs/azmcp-commands.md`** with new command details 2. **Add test prompts to `/servers/Azure.Mcp.Server/docs/e2eTestPrompts.md`** (maintain alphabetical order) 3. **Update toolset README.md** with new functionality 4. **Create changelog entry** if user-facing or critical change. See `docs/changelog-entries.md` for instructions. Always use the `-ChangelogPath` parameter (e.g., `servers/Azure.Mcp.Server/CHANGELOG.md` or `servers/Fabric.Mcp.Server/CHANGELOG.md`). 5. **Add CODEOWNERS entry** for new toolset ### Spelling and Content Validation ```powershell # Check spelling across codebase .\eng\common\spelling\Invoke-Cspell.ps1 # Add project-specific terms to that project's cspell.yaml. # Add cross-cutting terms to .vscode/cspell.json. ``` ## Git Workflow and Automation ### Git Hooks for Quality ```powershell # Install pre-push hook (runs dotnet format automatically) ./eng/scripts/Install-GitHooks.ps1 # Remove git hooks ./eng/scripts/Remove-GitHooks.ps1 ``` ### Git Worktree Safety When working in a linked git worktree (multiple worktrees sharing one repository common git directory), do **not** use `git stash`. Git stores stash entries in the shared `refs/stash`, so stash operations from concurrent worktrees share the same stack and can apply or drop the wrong changes. To compare against a baseline, use `git diff ` (e.g., `git diff main -- `) or create a throwaway worktree of the base branch with `git worktree add ` (e.g., `git worktree add ../mcp-main main`). When committing, stage intended changes explicitly (`git add -p` or specific paths) rather than `git add -A`. ### Pull Request Guidelines - **Run all tests**: `./eng/scripts/Test-Code.ps1` - **Format code**: `dotnet format` - **Check spelling**: `.\eng\common\spelling\Invoke-Cspell.ps1` - **Validate tool descriptions**: Use ToolDescriptionEvaluator (optional for Fabric MCP tools) - **Follow contribution guidelines**: See `CONTRIBUTING.md` - **One tool per PR**: Submit single toolsets for faster review ## Advanced Configuration ### External Tool Integration The server supports integration with external MCP servers through registry configuration, enabling aggregation of tools from multiple sources into a unified interface. ### Namespace-Based Tool Organization Commands are organized by Azure service namespace, allowing for fine-grained control over exposed functionality and helping manage VS Code's 128-tool display limit. ### Telemetry and Monitoring The server includes comprehensive telemetry integration with proper tag propagation for monitoring tool usage and performance across different deployment scenarios. --- This documentation provides AI agents with comprehensive guidance for working effectively with the Microsoft MCP codebase. For additional details, see `/.github/skills/add-azure-mcp-tools/SKILL.md` for implementation specifics and `CONTRIBUTING.md` for contribution workflows.