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:
- Creates a
FastMCP("azops-mcp")instance from themcpSDK - Imports all 14 tool modules from the
tools/package - Registers all 93 tools with
@mcp.tool()decorators - 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.
| Category | Fields |
|---|---|
| Logging | log_level, log_format |
| API | api_timeout, api_retry_attempts, api_retry_delay |
| Azure | azure_tenant_id, azure_client_id, azure_client_secret, azure_subscription_id, azure_default_location |
| Rate Limiting | rate_limit_enabled, rate_limit_requests_per_minute, rate_limit_burst_size |
| Security | secret_key, allowed_hosts |
tools/ — Azure SDK Integrations
The tools package is organized into 14 focused modules grouped by Azure service area:
| Module | Responsibility |
|---|---|
_clients.py | Shared auth & lazy SDK client factories |
subscription.py | Subscriptions, auth, tenants, locations |
resource_groups.py | Resource groups, tags, locks, activity log |
compute.py | VMs, VMSS, resource listing |
networking.py | VNets, subnets, peerings |
authorization.py | RBAC roles & assignments |
management_groups.py | Management group hierarchy |
app_configuration.py | App Configuration stores & key-values |
app_service.py | App Service plans & web apps |
container_registry.py | Azure Container Registry (ACR) |
active_directory.py | Azure AD / Entra ID |
webapp_deployment.py | Web App for Containers deployment |
docker.py | Local Docker container runtime |
monitoring.py | System 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
| Client | SDK Package | Used By |
|---|---|---|
ComputeManagementClient | azure-mgmt-compute | compute.py |
ResourceManagementClient | azure-mgmt-resource | resource_groups.py, compute.py |
StorageManagementClient | azure-mgmt-storage | compute.py |
SubscriptionClient | azure-mgmt-subscription | subscription.py |
NetworkManagementClient | azure-mgmt-network | networking.py |
ContainerRegistryManagementClient | azure-mgmt-containerregistry | container_registry.py |
GraphServiceClient | msgraph-sdk | active_directory.py |
Request Lifecycle
- AI client sends a JSON-RPC
tools/callmessage over stdio - FastMCP deserializes the request and dispatches to the matching
@mcp.tool()function server.pywrapper validates inputs and delegates to the appropriate tool module- The tool module lazily initializes the Azure SDK client via
_clients.py - Azure SDK makes a REST call to the Azure Resource Manager API
- 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 Exceptionin every tool ensures the server never crashes - Azure exceptions — caught and formatted via
format_error_message() - ImportError — caught separately to suggest
pip installcommands - 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 File | Covers |
|---|---|
test_subscription.py | Subscription, auth, account tools |
test_resource_groups.py | Resource groups, tags, locks, activity log |
test_compute.py | VMs, VMSS, storage, resources |
test_networking.py | VNets, subnets, peerings |
test_authorization.py | RBAC roles & assignments |
test_container_registry.py | ACR tools |
test_active_directory.py | Azure AD tools |
test_webapp_deployment.py | Web App for Containers |
test_docker.py | Docker container runtime |
test_monitoring.py | System metrics & health |
test_health.py | Health check & rate limiting |
test_config.py | Configuration management |
All tests use pytest with unittest.mock to mock Azure SDK calls.
pytest tests/ -v