Az
azops-mcp

Architecture

How azops-mcp works under the hood.


High-Level Overview

┌──────────────────┐  stdio (JSON-RPC)  ┌───────────────────────────────────┐
│   AI Assistant   │ ◄────────────────► │           azops-mcp               │
│   (Cursor, etc.) │                    │                                   │
└──────────────────┘                    │   server.py (93 tools)            │
                                        │                                   │
                                        │   tools/                          │
                                        │   ├─ _clients.py (shared)         │
                                        │   ├─ subscription.py (auth)       │
                                        │   ├─ compute.py (VMs)             │
                                        │   ├─ networking.py (VNets)        │
                                        │   ├─ container_registry.py (ACR)  │
                                        │   ├─ active_directory.py (AAD)    │
                                        │   ├─ ... (8 more)                 │
                                        │                                   │
                                        │   config.py                       │
                                        │   utils/helpers.py                │
                                        └──────────────┬────────────────────┘
                                                       │
                                              Azure SDK REST calls
                                                       │
                                                       ▼
                                              ┌─────────────────┐
                                              │   Azure Cloud    │
                                              │   (ARM API)      │
                                              └─────────────────┘

azops-mcp is a single Python process started by the AI client as a subprocess. It communicates over stdio using the Model Context Protocol and calls Azure SDK operations using your local credentials or a configured Service Principal.


Tool Registration

All 93 tools are registered at module level using the @mcp.tool() decorator. Each tool is a thin async wrapper that validates inputs, delegates to the appropriate tool module, and catches exceptions:

1@mcp.tool()
2async def list_resource_groups() -> str:
3    """List all resource groups in the subscription."""
4    try:
5        return await resource_groups.list_resource_groups()
6    except Exception as e:
7        return f"Error: {e}"

The MCP tools/list response includes all 93 tools with their names, descriptions, and parameter schemas. The AI client uses this to decide which tool to call.


Module Breakdown

__main__.py — Entry Point

from .server import main

if __name__ == "__main__":
    main()

When you run python -m azops_mcp, this module imports and calls main() from server.py.

server.py — MCP Server & Tool Definitions

This is the core of the application. It:

  1. Creates a FastMCP("azops-mcp") instance from the mcp SDK
  2. Imports all 14 tool modules from the tools/ package
  3. Registers all 93 tools with @mcp.tool() decorators
  4. Handles lifecycle — main() starts the server on stdio transport with signal handlers

config.py — Configuration Management

A @dataclass called ServerConfig with fields loaded from environment variables via os.getenv() with sensible defaults.

CategoryFields
Logginglog_level, log_format
APIapi_timeout, api_retry_attempts, api_retry_delay
Azureazure_tenant_id, azure_client_id, azure_client_secret, azure_subscription_id, azure_default_location
Rate Limitingrate_limit_enabled, rate_limit_requests_per_minute, rate_limit_burst_size
Securitysecret_key, allowed_hosts

tools/ — Azure SDK Integrations

The tools package is organized into 14 focused modules grouped by Azure service area:

ModuleResponsibility
_clients.pyShared auth & lazy SDK client factories
subscription.pySubscriptions, auth, tenants, locations
resource_groups.pyResource groups, tags, locks, activity log
compute.pyVMs, VMSS, resource listing
networking.pyVNets, subnets, peerings
authorization.pyRBAC roles & assignments
management_groups.pyManagement group hierarchy
app_configuration.pyApp Configuration stores & key-values
app_service.pyApp Service plans & web apps
container_registry.pyAzure Container Registry (ACR)
active_directory.pyAzure AD / Entra ID
webapp_deployment.pyWeb App for Containers deployment
docker.pyLocal Docker container runtime
monitoring.pySystem metrics & health

_clients.py — Shared Authentication

This is the foundation module. Azure SDK clients are expensive to construct, so _clients.py uses module-level globals with lazy loading:

1_azure_credential = None
2_compute_client = None
3
4def _get_compute_client():
5    global _compute_client
6    if _compute_client is None:
7        _compute_client = ComputeManagementClient(
8            credential=_get_azure_credential(),
9            subscription_id=get_subscription_id(),
10        )
11    return _compute_client

Each client is created once on first use, then cached for the session.

Authentication Chain

def _get_azure_credential():
    # Priority:
    # 1. Service Principal (if fully configured)
    # 2. Azure CLI + Managed Identity (ChainedTokenCredential)

Azure Client Matrix

ClientSDK PackageUsed By
ComputeManagementClientazure-mgmt-computecompute.py
ResourceManagementClientazure-mgmt-resourceresource_groups.py, compute.py
StorageManagementClientazure-mgmt-storagecompute.py
SubscriptionClientazure-mgmt-subscriptionsubscription.py
NetworkManagementClientazure-mgmt-networknetworking.py
ContainerRegistryManagementClientazure-mgmt-containerregistrycontainer_registry.py
GraphServiceClientmsgraph-sdkactive_directory.py

Request Lifecycle

  1. AI client sends a JSON-RPC tools/call message over stdio
  2. FastMCP deserializes the request and dispatches to the matching @mcp.tool() function
  3. server.py wrapper validates inputs and delegates to the appropriate tool module
  4. The tool module lazily initializes the Azure SDK client via _clients.py
  5. Azure SDK makes a REST call to the Azure Resource Manager API
  6. Response flows back: SDK → tool module (formats as string) → server.py → FastMCP → stdio → AI client

Error Handling Strategy

Every tool follows defensive error handling:

  • Catch-all — top-level except Exception in every tool ensures the server never crashes
  • Azure exceptions — caught and formatted via format_error_message()
  • ImportError — caught separately to suggest pip install commands
  • Input validation — required parameters checked before any SDK call

Errors are returned as plain-text strings (not exceptions) so the AI can relay them to the user.


Transport

The server uses stdio transport exclusively. The AI client spawns uv run python -m azops_mcp as a child process and communicates via stdin/stdout using the MCP protocol. Stderr is used for logging.

mcp.run(transport="stdio")

Testing

Tests are organized into separate files by integration category:

Test FileCovers
test_subscription.pySubscription, auth, account tools
test_resource_groups.pyResource groups, tags, locks, activity log
test_compute.pyVMs, VMSS, storage, resources
test_networking.pyVNets, subnets, peerings
test_authorization.pyRBAC roles & assignments
test_container_registry.pyACR tools
test_active_directory.pyAzure AD tools
test_webapp_deployment.pyWeb App for Containers
test_docker.pyDocker container runtime
test_monitoring.pySystem metrics & health
test_health.pyHealth check & rate limiting
test_config.pyConfiguration management

All tests use pytest with unittest.mock to mock Azure SDK calls.

pytest tests/ -v