Skip to main content
Glama
vamsitejeswar

azure-billing-mcp-server

Azure Billing MCP Server

An MCP (Model Context Protocol) server that provides read-only access to Azure Cost Management and Billing APIs. It runs on Google Cloud Run and integrates with Gemini Enterprise as a custom AI connector, allowing users to query live Azure billing data through natural language.


Table of Contents

  1. Overview

  2. Architecture

  3. Project Structure

  4. MCP Tools

  5. Local Development

  6. Deployment

  7. Environment Variables

  8. Network Requirements

  9. Security

  10. Subscription Type Requirement

  11. Gemini Enterprise Configuration

  12. Troubleshooting

  13. Maintenance

  14. Known Limitations


Related MCP server: Azure FinOps Elite

Overview

What It Does

This server exposes 11 read-only MCP tools that allow Gemini Enterprise (or any MCP-compatible AI client) to query live Azure billing data — cost breakdowns by service, resource group, or individual resource; daily spend trends; invoices; budgets; and detailed usage records.

Key Features

  • 11 read-only tools covering Azure Cost Management and Billing APIs

  • Deployed on Google Cloud Run — serverless, auto-scaling, no infrastructure to manage

  • OAuth 2.0 via Microsoft Entra ID — users log in with their corporate Microsoft account

  • Audit logging — every request logs the caller's bearer token for traceability

  • Server-side service principal — all Azure calls use a fixed service account with least-privilege read-only access

Who Uses It

End users interact through Gemini Enterprise's AI chat interface. They ask natural-language questions about Azure spending (e.g. "What were our top 5 most expensive resources last month?"). The AI invokes the appropriate MCP tool and returns structured, readable data.


Architecture

User (Gemini Enterprise chat)
       │
       ▼
Gemini Enterprise ──► Microsoft Entra ID OAuth
       │              (login.microsoftonline.com)
       │              Issues token scoped to: api://<client-id>/mcp.access
       ▼
Cloud Run MCP Server (IAM-gated: --no-allow-unauthenticated)
  ┌─────────────────────────────────────────────┐
  │  BearerTokenMiddleware                       │
  │  Captures bearer token for AUDIT LOG ONLY   │
  │  (token is NOT forwarded to Azure APIs)      │
  ├─────────────────────────────────────────────┤
  │  FastMCP → 11 MCP Tools                     │
  ├─────────────────────────────────────────────┤
  │  AzureCostClient                            │
  │  DefaultAzureCredential → service principal │
  └─────────────────────────────────────────────┘
       │
       ├──► Azure Cost Management API  (cost queries, usage)
       └──► Azure Billing API          (invoices, periods, budgets)

Two Separate Azure Identities

The system uses two completely separate Azure identities. This is intentional and important:

Identity

Purpose

Credentials used

Billing service principal (azure-billing-mcp-reader)

Queries Azure Cost Management + Billing APIs

AZURE_TENANT_ID, AZURE_CLIENT_ID, AZURE_CLIENT_SECRET in Cloud Run

Entra ID OAuth app (gemini-enterprise-mcp)

OAuth login screen for Gemini Enterprise users

Configured in Gemini Enterprise's data store form

The bearer token that GE forwards with every request is scoped to api://<client-id>/mcp.access — this is the correct audience for this server, but the wrong audience for management.azure.com. It cannot be forwarded to Azure. All Azure API calls always use the server's own service principal credentials, regardless of who the caller is.

Request Flow

  1. User sends a billing question in Gemini Enterprise

  2. GE authenticates with Entra ID and receives an OAuth token

  3. GE sends the MCP request to Cloud Run with Authorization: Bearer <token>

  4. Cloud Run IAM verifies the caller is GE's own service account (gcp-sa-discoveryengine)

  5. BearerTokenMiddleware captures the token and logs its presence (audit only)

  6. The MCP tool calls AzureCostClient, which authenticates to Azure using DefaultAzureCredential and the server's service principal

  7. Azure responds; the server returns structured JSON to GE

For detailed setup instructions, see SETUP.md.


Project Structure

azure-billing-mcp-server/
├── README.md                   # This file — main entry point
├── SETUP.md                    # Step-by-step Azure + GCP + Gemini Enterprise setup guide
│
├── docs/
│   ├── ARCHITECTURE.md         # Detailed architecture and component documentation
│   ├── CONFIGURATION.md        # All environment variables and configuration reference
│   ├── TROUBLESHOOTING.md      # Common errors, symptoms, and fixes
│   └── MAINTENANCE.md          # Ongoing maintenance procedures
│
├── server.py                   # All application code (MCP server, Azure client, 11 tools)
├── requirements.txt            # Python dependencies
├── Dockerfile                  # Container image definition
├── deploy_cloudrun.sh          # One-command deployment to Google Cloud Run
├── .env.example                # Local development environment variable template
├── pyrightconfig.json          # Pyright/Pylance type-checker config (development only)
└── Azure_Setup_Guide.docx      # Azure setup reference document

Key Files

File

Purpose

server.py

All application logic. Contains AzureCostClient (Azure API wrapper), BearerTokenMiddleware (audit logging), and all 11 MCP tool definitions. Single-file by design — no package structure needed for a service this size.

deploy_cloudrun.sh

Builds the Docker image via Cloud Build, deploys to Cloud Run, grants IAM to Gemini Enterprise's service account. Fill in the variables at the top before running.

Dockerfile

Uses python:3.12-slim. Installs dependencies from requirements.txt then runs server.py.

SETUP.md

Complete step-by-step setup guide covering Azure Portal configuration, GCP Secret Manager, Cloud Run deployment, and Gemini Enterprise connection. Intended for engineers doing the initial setup.

.env.example

Template for local development credentials. Copy to .env and fill in values. Not used on Cloud Run.


MCP Tools

All tools are read-only (readOnlyHint=True). The server never writes to Azure.

Cost Management Tools

Tool

Parameters

Description

list_subscriptions

—

List all Azure subscriptions the service principal can access. Call this first if the user hasn't specified a subscription.

query_costs

subscription_id, resource_group?, timeframe, granularity, group_by?, start_date?, end_date?, cost_type

General-purpose cost query. Full control over grouping (e.g. ["ServiceName"], ["ResourceId"]) and time range.

get_cost_by_service

subscription_id, resource_group?, timeframe, start_date?, end_date?

Cost breakdown by Azure service (VM, Storage, Functions, etc.), sorted highest first.

get_cost_by_resource_group

subscription_id, timeframe, start_date?, end_date?

Cost breakdown by resource group, sorted highest first.

get_daily_cost_trend

subscription_id, resource_group?, timeframe, start_date?, end_date?

Day-by-day cost time series sorted by date.

get_top_resources_by_cost

subscription_id, resource_group?, timeframe, start_date?, end_date?, top_n

Top N most expensive individual resources (default: 10).

Billing API Tools

Tool

Parameters

Description

get_billing_accounts

—

List billing accounts accessible to the service principal. Call this first for invoice queries.

get_billing_periods

subscription_id, top?

List recent billing periods, most recent first (default: 12).

get_invoices

billing_account_name, top?

List invoices for a billing account (default: 12).

get_usage_details

subscription_id, start_date, end_date, top?

Detailed line-item usage records for a date range (default: 100 records).

get_budgets

subscription_id

Budget limits and current spend vs limit for a subscription.

Valid timeframe values: MonthToDate, BillingMonthToDate, TheLastMonth, TheLastBillingMonth, WeekToDate, Custom

When timeframe="Custom", both start_date and end_date are required in YYYY-MM-DD format.


Local Development

# 1. Create and activate a virtual environment
python -m venv venv
source venv/bin/activate        # Windows: venv\Scripts\activate

# 2. Install dependencies
pip install -r requirements.txt

# 3. Configure credentials
cp .env.example .env
# Edit .env — fill in AZURE_TENANT_ID, AZURE_CLIENT_ID, AZURE_CLIENT_SECRET

# 4. Start the server
python server.py
# Server starts at http://0.0.0.0:8080/mcp

Alternative: Azure CLI authentication (no service principal needed locally)

az login
python server.py    # DefaultAzureCredential picks up az login credentials automatically

The MCP endpoint is http://localhost:8080/mcp. You can connect any MCP-compatible client to this URL for local testing.


Deployment

See SETUP.md for complete step-by-step instructions. The high-level sequence is:

Prerequisites

  • gcloud CLI installed and authenticated (gcloud auth login)

  • GCP project selected (gcloud config set project <project-id>)

  • Required GCP APIs enabled:

    gcloud services enable run.googleapis.com secretmanager.googleapis.com discoveryengine.googleapis.com

Steps

  1. Azure — Create the billing service principal (azure-billing-mcp-reader), assign Cost Management Reader and Billing Reader roles (SETUP.md Steps 1–2)

  2. Azure — Create the OAuth app registration (gemini-enterprise-mcp) with the custom mcp.access scope (SETUP.md Step 3)

  3. GCP — Store AZURE_CLIENT_SECRET in GCP Secret Manager as Azure_secret_value (SETUP.md Step 4)

  4. GCP — Fill in deploy_cloudrun.sh variables and run bash deploy_cloudrun.sh (SETUP.md Step 5)

  5. Gemini Enterprise — Create a Custom MCP data store pointing to the Cloud Run URL (SETUP.md Step 6)

Deploy Script Variables

Edit the top section of deploy_cloudrun.sh:

GCP_PROJECT="<your-gcp-project-id>"
GCP_REGION="asia-south1"                    # change if needed
SERVICE_NAME="<your-service-name>"
SECRET_NAME="Azure_secret_value"            # must match the secret in GCP Secret Manager
COST_MGMT_TENANT_ID="<your-azure-tenant-id>"
COST_MGMT_CLIENT_ID="<your-azure-client-id>"

Then run:

bash deploy_cloudrun.sh

The script outputs the MCP Server URL on completion. Save this URL — it is needed for Gemini Enterprise configuration.

Verifying Deployment

After deployment:

  1. Check Cloud Run console — service status should be Active

  2. In Gemini Enterprise, click Reload custom actions — all 11 tools should appear with a green checkmark


Environment Variables

See docs/CONFIGURATION.md for full reference.

Variable

Required

Default

Purpose

Where to get it

AZURE_TENANT_ID

Yes

—

Azure tenant ID for the billing service principal

Entra ID → App Registrations → Overview → Directory (tenant) ID

AZURE_CLIENT_ID

Yes

—

Application (client) ID of the billing service principal

Entra ID → App Registrations → Overview → Application (client) ID

AZURE_CLIENT_SECRET

Yes

—

Client secret for the billing service principal

Created in SETUP.md Step 1.3. In production, injected from GCP Secret Manager.

PORT

No

8080

HTTP port the server listens on

N/A

LOG_LEVEL

No

INFO

Logging verbosity (DEBUG, INFO, WARNING, ERROR)

N/A

On Cloud Run: AZURE_TENANT_ID and AZURE_CLIENT_ID are passed as --set-env-vars; AZURE_CLIENT_SECRET is injected via --set-secrets from GCP Secret Manager. The .env file is for local development only.


Network Requirements

The server makes outbound HTTPS (port 443) calls to these endpoints:

Endpoint

Purpose

login.microsoftonline.com

Azure token acquisition via DefaultAzureCredential

management.azure.com

Azure Cost Management API and Billing API

Inbound: Cloud Run handles TLS termination. The container listens on port 8080 internally. Access is restricted by Cloud Run IAM (--no-allow-unauthenticated) — no additional firewall rules are required.

No VPC peering, IP allowlisting, custom DNS, or proxy configuration is required for a standard Cloud Run deployment.


Security

Access Control Model

  • Cloud Run is deployed without public access (--no-allow-unauthenticated)

  • Only Gemini Enterprise's own Google service account (service-<project-number>@gcp-sa-discoveryengine.iam.gserviceaccount.com) is granted roles/run.invoker

  • The server does not perform JWT validation on incoming bearer tokens — token validation is Cloud Run IAM's responsibility

  • All Azure API calls use the server's own service principal credentials — callers cannot escalate or override which Azure identity is used

Secrets Management

Secret

Production location

Notes

AZURE_CLIENT_SECRET

GCP Secret Manager (Azure_secret_value)

Injected at deploy time via --set-secrets. Never stored in environment variables directly or in code.

Entra ID OAuth app secret (gemini-enterprise-mcp)

Gemini Enterprise data store configuration

Used only by GE for the login flow — this server never sees it.

Required Azure Roles

The billing service principal requires:

Role

Scope

Required for

Cost Management Reader

Subscription

All cost query and usage tools

Billing Reader

Subscription

get_billing_periods, get_invoices, get_billing_accounts

The service principal has no write permissions anywhere.


Subscription Type Requirement

Azure Cost Management and Billing APIs only work with paid commercial subscriptions:

Works

Does NOT work

Pay-As-You-Go

Free Trial

Enterprise Agreement (EA)

Visual Studio / Dev/Test

Microsoft Customer Agreement (MCA)

CSP / Sponsored

To upgrade: Azure Portal → Subscriptions → select subscription → Upgrade.


Gemini Enterprise Configuration

After deploying to Cloud Run, configure Gemini Enterprise:

Data Store Settings

In Gemini Enterprise Console → Data Stores → Create Data Store → Custom MCP Server:

Field

Value

MCP Server URL

https://<cloud-run-url>/mcp (printed by deploy script)

Authorization URL

https://login.microsoftonline.com/<TENANT_ID>/oauth2/v2.0/authorize

Token URL

https://login.microsoftonline.com/<TENANT_ID>/oauth2/v2.0/token

Client ID

Application (client) ID of the gemini-enterprise-mcp app (Step 3 of SETUP.md)

Client Secret

Secret from gemini-enterprise-mcp app (Step 3.3) — NOT the billing service principal secret

Scopes

api://<client-id-of-gemini-enterprise-mcp>/mcp.access offline_access

Enable PKCE

Leave unchecked

Required Redirect URIs (add in Entra ID App Registration → Authentication)

https://vertexaisearch.cloud.google.com/console/oauth/default_oauth.html
https://vertexaisearch.cloud.google.com/oauth-redirect

MCP Agent Instructions

Paste this into the Gemini Enterprise agent instructions field:

You have access to Azure Cost Management and Billing tools for querying actual
Azure spend, invoices, budgets, and usage. All tools are read-only.
If the user doesn't specify a subscription, call list_subscriptions first.
For invoice queries, call get_billing_accounts first.
Default timeframe is "MonthToDate" unless the user asks for a different period.

Troubleshooting

See docs/TROUBLESHOOTING.md for the full guide.

Symptom

Likely Cause

Fix

Failed to obtain refresh token in GE

Wrong client secret in GE form

Use the gemini-enterprise-mcp app secret (Step 3.3 of SETUP.md), not the billing service principal secret

Failed to load actions in GE

Cloud Run not starting

Check GCP Secret Manager IAM — the compute service account must have secretmanager.secretAccessor

doesn't have valid WebDirect/AIRS offer type

Free Trial Azure subscription

Upgrade to Pay-As-You-Go

AuthorizationFailed on billing tools

Missing Billing Reader role

Assign Billing Reader to the service principal in Azure Portal (SETUP.md Step 2.2)

Tools return empty []

Service principal lacks subscription access

Assign roles using the service principal's Object ID (from Enterprise Applications), not the Application ID

403 Forbidden on Cloud Run

GE service account missing invoker role

Re-run the IAM grant step in deploy_cloudrun.sh

Invalid tenant ID on startup

Placeholder values left in deploy script

Fill in real COST_MGMT_TENANT_ID and COST_MGMT_CLIENT_ID in deploy_cloudrun.sh

Permission denied on secret

Cloud Run cannot read Azure_secret_value

Run the gcloud secrets add-iam-policy-binding command from SETUP.md Step 4.1


Maintenance

See docs/MAINTENANCE.md for full procedures.

Deploying a new version:

bash deploy_cloudrun.sh    # rebuild image and redeploy

Rotating the Azure client secret:

  1. Create a new secret in Azure Portal (Entra ID → App Registrations → azure-billing-mcp-reader → Certificates & Secrets)

  2. Update the value in GCP Secret Manager: Azure_secret_value

  3. Redeploy: bash deploy_cloudrun.sh

Viewing logs:

gcloud logging read "resource.type=cloud_run_revision AND resource.labels.service_name=azure-billing-mcp" \
  --project <your-gcp-project-id> --limit 100 --format "table(timestamp, textPayload)"

Known Limitations

  • Subscription type: Cost Management APIs do not work on Free Trial, Visual Studio, CSP, or sponsored subscriptions.

  • Pagination: get_usage_details defaults to 100 records (top parameter). Large date ranges may require multiple calls or a higher top value.

  • No per-user Azure RBAC: All users share the service principal's access level. The bearer token is captured for audit logging only — there is no per-caller Azure identity switching.

  • Secret expiry: The AZURE_CLIENT_SECRET has a finite validity (default: 12 months). When it expires, the server will stop authenticating to Azure. Set a calendar reminder to rotate it before expiry.

  • Single region: The deploy script defaults to asia-south1. Multi-region deployments require manual configuration.

  • Read-only: No write operations are supported. The server cannot create budgets, modify resources, or take any action in Azure.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Production-grade MCP server for enterprise Azure cost optimization, enabling spend anomaly detection, multi-tenant auditing, budget validation, and compliance-aware recommendations.
    1
    MIT
  • F
    license
    B
    quality
    D
    maintenance
    An MCP server that gives Claude live access to Azure pricing and cost data — retail prices, VM comparisons, reservation analysis, architecture estimates, and actual subscription spend.
    9
    -