{
  "markdown": "# Azure FinOps MCP Server\n\n<!-- mcp-name: io.github.raviteja-pegata/azure-finops-mcp -->\n<!-- google-site-verification: BG8dYih08oraoJjFa-qBpVZE5W4tuJ0A4qTylUWzjrI -->\n\nAn MCP server that gives LLM clients (Claude Desktop, Claude Code, VS Code, Cursor)\nconversational access to Azure cost analysis, budget tracking, forecasting, and resource\noptimization — across multiple subscriptions.\n\n## Tools\n\n### Discovery\n| Tool | Purpose |\n|---|---|\n| `list_subscriptions` | List allowed subscriptions with friendly names |\n\n### Cost Analysis\n| Tool | Purpose |\n|---|---|\n| `get_cost_summary` | Total cost for a date range (single sub) |\n| `get_cost_by_dimension` | Cost breakdown by service / RG / location / meter |\n| `get_cost_by_tag` | Cost grouped by tag value (showback/chargeback) |\n| `get_month_to_date_cost` | Current-month spend (single sub) |\n| `get_portfolio_month_to_date_cost` | Current-month spend across ALL subs |\n\n### Budgets\n| Tool | Purpose |\n|---|---|\n| `get_budget_status` | Budget consumption for a single sub |\n| `get_portfolio_budget_status` | Budget status across ALL subs |\n\n### Optimization\n| Tool | Purpose |\n|---|---|\n| `find_idle_resources` | Unattached disks, stranded IPs/NICs, stopped VMs |\n| `find_idle_resources_portfolio` | Idle resources across ALL subs |\n| `get_advisor_recommendations` | Azure Advisor cost recs with annual savings |\n| `get_vm_utilization` | CPU stats to validate rightsizing |\n\n### Forecasting\n| Tool | Purpose |\n|---|---|\n| `forecast_month_end_spend` | Predicted month-end cost (single sub) |\n| `forecast_portfolio_month_end_spend` | Predicted month-end cost across ALL subs |\n\n## Quick Install\n\n```bash\npip install azure-finops-mcp\n```\n\nThen add it to your MCP client config using the installed command — no cloning needed:\n\n```json\n{\n  \"mcpServers\": {\n    \"azure-finops\": {\n      \"command\": \"azure-finops-mcp\",\n      \"env\": {\n        \"AZURE_ALLOWED_SUBSCRIPTIONS\": \"sub-id-1,sub-id-2\",\n        \"AZURE_DEFAULT_SUBSCRIPTION\": \"sub-id-1\"\n      }\n    }\n  }\n}\n```\n\nSee the [Client Configuration](#client-configuration) section below for per-client config file locations.\n\n---\n\n## Prerequisites\n\n- Python 3.11+\n- Azure CLI installed and logged in (`az login`)\n\n## Azure RBAC Permissions\n\nThe identity running this server (your user, a service principal, or a managed identity)\nneeds three roles assigned on **each subscription** you want to query:\n\n| Role | Purpose |\n|---|---|\n| **Cost Management Reader** | Cost analysis, forecasting, budget queries |\n| **Reader** | Resource inventory via Resource Graph |\n| **Monitoring Reader** | VM utilization metrics via Azure Monitor |\n\n### Assign via Azure CLI\n\n```bash\nSUBSCRIPTION_ID=\"<your-subscription-id>\"\nPRINCIPAL_ID=\"<object-id-of-user-sp-or-managed-identity>\"\n\nfor ROLE in \"Cost Management Reader\" \"Reader\" \"Monitoring Reader\"; do\n  az role assignment create \\\n    --assignee \"$PRINCIPAL_ID\" \\\n    --role \"$ROLE\" \\\n    --scope \"/subscriptions/$SUBSCRIPTION_ID\"\ndone\n```\n\nRepeat for each subscription listed in `AZURE_ALLOWED_SUBSCRIPTIONS`.\n\n### Local development (your own user)\n\n```bash\naz login\naz account set --subscription \"<your-subscription-id>\"\n\n# Check your object ID\naz ad signed-in-user show --query id -o tsv\n```\n\nYour user already has these roles if you're a subscription Owner or Contributor.\nIf not, ask your Azure admin to assign them.\n\n### Managed Identity (Container Apps deployment)\n\nAfter deploying with `deploy.sh`, the script automatically assigns these three roles\nto the Container App's system-assigned managed identity on each allowed subscription.\nNo credentials or secrets are needed — `DefaultAzureCredential` picks up the\nmanaged identity automatically at runtime.\n\n## Install\n\n```bash\ngit clone <your-repo-url> azure-finops-mcp\ncd azure-finops-mcp\n\npython3 -m venv .venv\nsource .venv/bin/activate      # Windows: .venv\\Scripts\\activate\npip install -e .\n\ncp .env.example .env\n# Edit .env: set your subscription IDs\n```\n\n## Configure .env\n\n```bash\n# Required: comma-separated subscription IDs the server may query\nAZURE_ALLOWED_SUBSCRIPTIONS=sub-id-1,sub-id-2,sub-id-3\n\n# Required: default subscription (must be in the list above)\nAZURE_DEFAULT_SUBSCRIPTION=sub-id-1\n```\n\n## Test with MCP Inspector\n\nThe Inspector is a web UI that lets you call tools interactively and see\nraw JSON-RPC messages. Always test here before connecting to Claude Desktop.\n\n```bash\n# Use the venv's python3 explicitly — the Inspector launches a subprocess\n# and needs the binary that has mcp + azure SDKs installed.\nnpx @modelcontextprotocol/inspector $(which python3) -m azure_finops_mcp.server\n```\n\nIn the Inspector UI:\n1. Verify Transport Type is **STDIO**\n2. Click **Connect** — should succeed and show \"azure-finops\" as the server name\n3. Navigate to Tools, click List Tools — you should see all 15 tools\n4. Try `list_subscriptions` first (no arguments needed)\n5. Try `get_month_to_date_cost` (no arguments needed — uses default sub)\n\n## Client Configuration\n\nFind the absolute path to your venv's Python first — you'll need it in every config below:\n\n```bash\n# With your venv activated:\nwhich python3\n# e.g. /Users/yourname/azure-finops-mcp/.venv/bin/python3\n```\n\n---\n\n### Claude Desktop\n\nEdit `claude_desktop_config.json`:\n\n| OS | Path |\n|---|---|\n| macOS | `~/Library/Application Support/Claude/claude_desktop_config.json` |\n| Windows | `%APPDATA%\\Claude\\claude_desktop_config.json` |\n| Linux | `~/.config/Claude/claude_desktop_config.json` |\n\n```json\n{\n  \"mcpServers\": {\n    \"azure-finops\": {\n      \"command\": \"/absolute/path/to/.venv/bin/python3\",\n      \"args\": [\"-m\", \"azure_finops_mcp.server\"],\n      \"env\": {\n        \"AZURE_ALLOWED_SUBSCRIPTIONS\": \"sub-1,sub-2,sub-3\",\n        \"AZURE_DEFAULT_SUBSCRIPTION\": \"sub-1\",\n        \"FINOPS_CACHE_TTL_SECONDS\": \"900\"\n      }\n    }\n  }\n}\n```\n\nRestart Claude Desktop. A tool icon in the chat input confirms the server connected.\n\n---\n\n### VS Code (GitHub Copilot / Agent mode)\n\nCreate `.vscode/mcp.json` in your workspace (or add to user `settings.json` under `\"mcp\"`):\n\n```json\n{\n  \"servers\": {\n    \"azure-finops\": {\n      \"type\": \"stdio\",\n      \"command\": \"/absolute/path/to/.venv/bin/python3\",\n      \"args\": [\"-m\", \"azure_finops_mcp.server\"],\n      \"env\": {\n        \"AZURE_ALLOWED_SUBSCRIPTIONS\": \"sub-1,sub-2,sub-3\",\n        \"AZURE_DEFAULT_SUBSCRIPTION\": \"sub-1\",\n        \"FINOPS_CACHE_TTL_SECONDS\": \"900\"\n      }\n    }\n  }\n}\n```\n\nRequires VS Code 1.99+ with the **GitHub Copilot** extension. Open the Chat panel,\nswitch to **Agent** mode, and the `azure-finops` tools will appear automatically.\n\n---\n\n### Cursor\n\nCreate or edit `~/.cursor/mcp.json`:\n\n```json\n{\n  \"mcpServers\": {\n    \"azure-finops\": {\n      \"command\": \"/absolute/path/to/.venv/bin/python3\",\n      \"args\": [\"-m\", \"azure_finops_mcp.server\"],\n      \"env\": {\n        \"AZURE_ALLOWED_SUBSCRIPTIONS\": \"sub-1,sub-2,sub-3\",\n        \"AZURE_DEFAULT_SUBSCRIPTION\": \"sub-1\",\n        \"FINOPS_CACHE_TTL_SECONDS\": \"900\"\n      }\n    }\n  }\n}\n```\n\nOr add it via **Cursor Settings → MCP → Add new global MCP server**.\nRestart Cursor. The tools appear in Cursor's Agent/Composer panel.\n\n---\n\n### Claude Code (CLI)\n\n```bash\nclaude mcp add azure-finops \\\n  /absolute/path/to/.venv/bin/python3 \\\n  -m azure_finops_mcp.server \\\n  -e AZURE_ALLOWED_SUBSCRIPTIONS=sub-1,sub-2,sub-3 \\\n  -e AZURE_DEFAULT_SUBSCRIPTION=sub-1\n```\n\n---\n\n### Remote HTTP (after deploying to Azure Container Apps)\n\nAll clients support connecting to the deployed server over HTTP — no local Python needed:\n\n**Claude Desktop / Cursor** — add to the same config files above:\n```json\n{\n  \"mcpServers\": {\n    \"azure-finops\": {\n      \"type\": \"http\",\n      \"url\": \"https://<your-container-app-fqdn>/mcp\"\n    }\n  }\n}\n```\n\n**VS Code** — in `.vscode/mcp.json`:\n```json\n{\n  \"servers\": {\n    \"azure-finops\": {\n      \"type\": \"http\",\n      \"url\": \"https://<your-container-app-fqdn>/mcp\"\n    }\n  }\n}\n```\n\n**Claude Web** — Settings → Integrations → Add → `https://<your-container-app-fqdn>/mcp`\n\n## Example Prompts\n\nTry these once connected:\n\n- \"What are our allowed subscriptions?\"\n- \"What's our total month-to-date spend across all subscriptions?\"\n- \"Which 10 services cost the most on our prod subscription last month?\"\n- \"Break down last quarter's spend by the `costcenter` tag.\"\n- \"Are any budgets close to breaching?\"\n- \"Show me idle resources across all our subscriptions.\"\n- \"What does Azure Advisor recommend for cost savings?\"\n- \"Is VM `my-analytics-vm` actually being used? Check its CPU over 14 days.\"\n- \"Compare our forecast for this month against our budgets.\"\n\n## Architecture\n\n```\nClaude Desktop ◄─┐\nVS Code        ◄─┤\nCursor         ◄─┼──stdio / HTTP──► Azure FinOps MCP Server ◄──REST──► Azure APIs\nClaude Code    ◄─┤                        │\nClaude Web     ◄─┘                        ├── config.py          ← env + allowlist\n                                          ├── azure_clients.py   ← shared credential\n                                          ├── cache.py           ← TTL cache\n                                          ├── server.py          ← FastMCP + registration\n                                          └── tools/\n                                              ├── subscriptions  ← discovery\n                                              ├── cost           ← queries + portfolio\n                                              ├── budgets        ← budget status\n                                              ├── optimization   ← idle + advisor + metrics\n                                              └── forecast       ← predictions\n```\n\n### Key design decisions\n\n**Narrow tools over flexible tools.** The LLM picks among well-named tools\nfar better than it constructs complex query objects. 15 purpose-built tools\nbeats 3 configurable ones.\n\n**Subscription allowlist.** A frozenset loaded from env. Every tool calls\n`resolve_subscription()` which refuses any ID not in the list. Prevents the\nLLM from querying unauthorized subscriptions — important for prompt injection\ndefense.\n\n**Portfolio tools catch per-sub errors.** When querying 5+ subscriptions, one\nmight have different RBAC or be in a weird state. Portfolio tools (`get_portfolio_*`)\nwrap each sub in try/except so partial results are returned with errors listed\nseparately.\n\n**Cache on Cost Management only.** Cost queries are expensive and rate-limited\n(~30 req/min per tenant). Cost data updates hourly at best. Default 15-minute\nTTL trades almost nothing in freshness for significant rate-limit headroom.\nResource Graph and Advisor are fast and cheap — no caching needed.\n\n**Structured returns, not prose.** Tools return dicts with columns/rows/metadata.\nThe LLM narrates them naturally. This avoids encoding English into tool responses\n(which makes them brittle to prompt changes).\n\n## Deploying to Azure (Remote Mode)\n\nFor team-wide access, deploy as a remote HTTP server:\n\n1. **Transport swap** in `server.py`:\n   ```python\n   mcp.run(transport=\"streamable-http\", host=\"0.0.0.0\", port=8000)\n   ```\n\n2. **Dockerfile:**\n   ```dockerfile\n   FROM python:3.12-slim\n   WORKDIR /app\n   COPY . .\n   RUN pip install --no-cache-dir -e .\n   CMD [\"azure-finops-mcp\"]\n   ```\n\n3. **Deploy to Azure Container Apps** with a user-assigned managed identity.\n\n4. **Grant RBAC** to the managed identity (same 3 roles: Cost Management Reader,\n   Reader, Monitoring Reader) on each subscription.\n\n5. **Add auth** via APIM or Azure Front Door + Entra ID.\n   MCP supports OAuth for remote servers.\n\n6. `DefaultAzureCredential` picks up the managed identity automatically —\n   no code changes needed.\n\n## Troubleshooting\n\n| Problem | Fix |\n|---|---|\n| `DefaultAzureCredential` auth errors | Run `az login` and verify with `az account show` |\n| 429 throttling on Cost Management | Increase `FINOPS_CACHE_TTL_SECONDS` |\n| Empty budget list | Budgets must exist in the portal — the API doesn't create them |\n| `find_idle_resources` errors | You need `Reader` RBAC at subscription scope |\n| Inspector \"Connection Error\" | Use absolute path to venv's python3 in Command field |\n| `print()` breaks the server | Never use `print()` in MCP tools — it corrupts the stdio JSON stream. Use `logging` instead |\n",
  "bytes": 12133,
  "sha": "f7c935c4548e9ab434d4f99d1ba5562befee053256186b67f87380ca83f607b3",
  "repo_slug": "raviteja-pegata/azure-finops-mcp-server",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_raviteja_pegata_azure_finops_m_59652334/readme"
}