Content
# ARM MCP Server
A practical [Model Context Protocol](https://modelcontextprotocol.io) server that exposes Azure Resource Manager (ARM) APIs to MCP clients (Copilot, Claude Desktop, MCP Inspector, custom agents).
This repo is organized as a small Python package:
- `server.py` is the thin entrypoint.
- `arm_mcp/core.py` contains auth, HTTP, paging, LRO polling, api-version inference, and schema helpers.
- `arm_mcp/tools.py` registers the MCP tools.
## Why this design
ARM is a uniform control plane API (`HTTP verb + ARM path + api-version + optional JSON body` against `https://management.azure.com`).
Instead of hand-coding service-specific clients, this server exposes:
- generic execution tools,
- discovery tools to find valid resource types/versions,
- a planning tool to shape request bodies.
That gives agents a reliable discover -> plan -> execute workflow while keeping the server minimal.
## Exposed tools
### Execution (passthrough)
| Tool | Verb | Purpose |
|------|------|---------|
| `arm_get` | GET | Read any ARM resource or collection (`all_pages=true` follows `nextLink`) |
| `arm_put` | PUT | Create or fully replace a resource (polls async ARM operations by default) |
| `arm_patch` | PATCH | Partial update |
| `arm_delete` | DELETE | Delete a resource (`confirm=true` guard) |
| `arm_action` | POST | Invoke ARM actions (for example `listKeys`, `restart`, `start`, `stop`) |
### Discovery (what exists)
| Tool | Purpose |
|------|---------|
| `arm_list_subscriptions` | List accessible subscriptions |
| `arm_list_locations` | List Azure regions for a subscription |
| `arm_list_providers` | List provider namespaces, or provider resource types + api-versions |
| `arm_search_resource_types` | Keyword search across provider resource types (`detail=names|versions|full`) |
### Planning (how to call it)
| Tool | Purpose |
|------|---------|
| `arm_get_schema` | Return required request-body fields, summarized properties, and an example body for a resource type + API version |
### Local workflow discovery
| Tool | Purpose |
|------|---------|
| `get_skill` | Match a user intent to markdown workflow docs in a local `skills/` folder using semantic text matching |
All tools return a structured payload of the form:
`{"ok": bool, "status": int, "data": ..., "error"?: {...}}`
The tools also include MCP annotations (`readOnlyHint`, `destructiveHint`, `idempotentHint`).
## api-version behavior
For passthrough tools (`arm_get`, `arm_put`, `arm_patch`, `arm_delete`, `arm_action`), `api_version` can be omitted.
When omitted, the server attempts to infer the resource type from the ARM path and resolves the provider `defaultApiVersion`.
When defaulting succeeds, responses include:
- `api_version_used`
- `note`
If inference fails, the error points you to `arm_search_resource_types` or `arm_list_providers`.
## Typical flow
```text
arm_search_resource_types(query="storage", detail="versions")
-> pick resourceType + apiVersion
arm_get_schema(resource_type="Microsoft.Storage/storageAccounts", api_version="2024-01-01")
-> shape body
arm_put(path=".../storageAccounts/<name>", body={...})
```
## Prerequisites
1. Python 3.10+
2. Azure CLI authenticated with `az login`
3. Dependencies installed
```bash
python3 -m pip install -r requirements.txt
```
Authentication uses `azure-identity` `DefaultAzureCredential`, which reuses local Azure auth context (including Azure CLI login). No secrets are stored by this server.
## Repository layout
```text
arm-mcp-server/
├── README.md
├── requirements.txt
├── server.py
├── arm_mcp/
│ ├── __init__.py
│ ├── core.py
│ └── tools.py
├── docs/
│ └── ARM_MCP_TOOL_IMPLEMENTATION_GUIDE.md
├── eval/
│ ├── requirements.txt
│ ├── run_eval.py
│ ├── scenarios.json
│ └── results/
├── schema_cache/
│ └── <provider>_<api-version>.json
└── tests/
├── test_client.py
├── test_enhancements.py
└── test_all_tools.py
```
## Run and validate locally
Install dependencies:
```bash
python3 -m pip install -r requirements.txt
```
Quick smoke test (read-oriented discovery calls):
```bash
python3 tests/test_client.py
```
Enhancement test (schema/search/paging/annotations/api-version defaulting):
```bash
python3 tests/test_enhancements.py
```
Comprehensive walkthrough script:
```bash
python3 tests/test_all_tools.py
```
Interactive exploration with MCP Inspector:
```bash
npx @modelcontextprotocol/inspector python3 server.py
```
## MCP client configuration
Example MCP config entry:
```json
{
"mcpServers": {
"arm": {
"command": "python3",
"args": ["/Users/stevenbucher/Testing/arm-mcp-server/server.py"]
}
}
}
```
## Example tool calls
Read a storage account:
```json
{
"tool": "arm_get",
"arguments": {
"path": "/subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.Storage/storageAccounts/<name>",
"api_version": "2023-01-01"
}
}
```
Create a storage account:
```json
{
"tool": "arm_put",
"arguments": {
"path": "/subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.Storage/storageAccounts/<name>",
"api_version": "2023-01-01",
"body": {
"location": "eastus",
"sku": { "name": "Standard_LRS" },
"kind": "StorageV2",
"properties": {
"accessTier": "Hot",
"minimumTlsVersion": "TLS1_2"
}
}
}
}
```
Invoke an action (`listKeys`):
```json
{
"tool": "arm_action",
"arguments": {
"path": "/subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.Storage/storageAccounts/<name>/listKeys",
"api_version": "2023-01-01"
}
}
```
Discover valid versions first:
```json
{
"tool": "arm_list_providers",
"arguments": {
"subscription_id": "<sub>",
"namespace": "Microsoft.Storage"
}
}
```
Match a user intent to local workflow skills:
```json
{
"tool": "get_skill",
"arguments": {
"intent": "I need a repeatable workflow for creating an expense report from receipts",
"max_results": 3
}
}
```
## Evaluation harness
`eval/run_eval.py` runs scenario prompts against a model through GitHub Copilot SDK with only this ARM MCP server attached.
Captured per scenario:
- tool sequence and arguments,
- token usage and cost,
- latency,
- tool failures,
- blocked attempts to call non-ARM tools,
- final model answer.
Setup:
```bash
python3 -m pip install -r eval/requirements.txt
python3 -m copilot download-runtime
az login
```
Run:
```bash
python3 eval/run_eval.py
python3 eval/run_eval.py --model gpt-5
python3 eval/run_eval.py --only S001,S002
python3 eval/run_eval.py --subscription-id <sub-id> --resource-group <rg-name>
```
Notes:
- Default model is `claude-sonnet-4.5`.
- Default scenarios file is `eval/scenarios.json`.
- CSV input is still supported via `--scenarios path.csv` with `id`, `prompt`, and `expected_tools` (or legacy `expect_tool`).
Outputs:
- `eval/results/latest.json`
- `eval/results/report_<timestamp>.md`
## Operational notes
- Long-running operations: `arm_put`, `arm_patch`, and `arm_delete` poll ARM async operation endpoints by default (up to ~5 minutes). Use `wait=false` to return immediately.
- Secret-bearing actions: `arm_action` can return sensitive values (for example storage keys). Treat tool output accordingly.
- Schema source and cache: `arm_get_schema` reads from `https://schema.management.azure.com/schemas` and caches files under `schema_cache/`.
- Request body shape: `apiVersion`, `name`, and `type` are URL/template fields and are intentionally excluded from `arm_get_schema` body examples.
## Implementation docs
- `docs/ARM_MCP_TOOL_IMPLEMENTATION_GUIDE.md` contains an in-depth implementation walkthrough for every exposed MCP tool, including internal call paths, behavior details, and edge cases.