MCP in practice: a real, running agent

Ask your cloud bill a question, get a plain-English answer

"Check my GCP cost and find any waste." An MCP server exposes two GCP tools, an LLM decides which ones to call, and the results come back as one synthesized report: unattached disks, idle VMs, and unused static IPs included.

01

GCP has no "get my spend" button

Unlike a single dashboard call, real cost-by-service data lives in a BigQuery billing export table, and cloud waste hides across three completely different APIs. FinOps work is mostly knowing where to look.

Unattached disks

Persistent disks left behind after a VM is deleted keep billing indefinitely, found via Compute Engine's Disk.users field.

Idle VMs

Running instances doing nothing. Confirming "idle" (not just "running") means cross-referencing Cloud Monitoring's 7-day average CPU.

Unused static IPs

A reserved external IP bills hourly the moment it's no longer attached to anything, easy to forget, easy to detect.

02

Two MCP tools, one server

server.py exposes both over stdio. Each tries the real GCP API first, then degrades to clearly labeled mock data, so a demo (or a misconfigured production run) still returns something useful instead of crashing.

model-controlled

get_gcp_monthly_cost_summary()

Queries the BigQuery billing export for the current month, grouped by service. Falls back to a realistic mock breakdown when credentials or the export table aren't configured.

BigQuery Billing Export
model-controlled

detect_idle_gcp_resources()

Scans Compute Engine for unattached disks and unused static IPs, and cross-checks running instances against Cloud Monitoring CPU data to flag idle VMs.

Compute Engine Cloud Monitoring
03

Inside one real run

This is an actual trace captured while testing the agent: python agent.py "Check my GCP cost and find any waste", running on Gemini's free tier. Click a stage to see what happened inside it.

+

Not a mockup: two real runs

Unedited terminal output from python agent.py, on Gemini's free tier, no GCP credentials configured. Both tools returned their labeled mock data, and the LLM still had to read it, pick the right numbers, and answer correctly.

$ python agent.py "Check my GCP cost and find any waste"

Terminal output: agent.py run for 'Check my GCP cost and find any waste', showing a full cost breakdown and idle-resource report

$ python agent.py "What's my biggest cost driver this month?"

Terminal output: agent.py run for 'What's my biggest cost driver this month?', showing the agent identifying the top spending service
04

Diagrams

The same system, two views: the request flow over time, and the architecture at rest.

Sequence: one request, two tool calls

agent.py orchestrates; server.py stays a dumb, stateless tool provider.

sequenceDiagram
    actor User
    participant Agent as agent.py (MCP client)
    participant LLM as LLM (Gemini or Claude)
    participant Server as server.py (MCP server)
    participant GCP as GCP APIs / mock fallback

    User->>Agent: "Check my GCP cost and find any waste"
    Agent->>Server: list_tools()
    Server-->>Agent: [get_gcp_monthly_cost_summary, detect_idle_gcp_resources]
    Agent->>LLM: prompt + tool schemas

    LLM-->>Agent: tool_use: get_gcp_monthly_cost_summary()
    Agent->>Server: call_tool(get_gcp_monthly_cost_summary)
    Server->>GCP: BigQuery billing export query
    GCP-->>Server: cost breakdown by service
    Server-->>Agent: tool result (JSON)
    Agent->>LLM: tool result

    LLM-->>Agent: tool_use: detect_idle_gcp_resources()
    Agent->>Server: call_tool(detect_idle_gcp_resources)
    Server->>GCP: Compute Engine + Cloud Monitoring scan
    GCP-->>Server: idle disks / VMs / static IPs
    Server-->>Agent: tool result (JSON)
    Agent->>LLM: tool result

    LLM-->>Agent: final synthesized answer
    Agent-->>User: cost summary + waste report
        

Architecture: at rest

Every real data source has a labeled mock fallback right behind it.

flowchart LR
    subgraph ClientApp["agent.py (MCP client)"]
        Loop["LLM Tool-Use Loop\n(Gemini or Claude)"]
    end

    ClientApp <-->|"JSON-RPC 2.0 over stdio"| Server["server.py (MCP server)"]

    Server --> Cost["get_gcp_monthly_cost_summary()"]
    Server --> Idle["detect_idle_gcp_resources()"]

    Cost <--> BQ[("BigQuery Billing Export")]
    Idle <--> CE[("Compute Engine + Cloud Monitoring")]

    BQ -. fallback .-> Mock1[("Mock cost data")]
    CE -. fallback .-> Mock2[("Mock idle-resource data")]