Inspiration

What it does

How we built it

Challenges we ran into

Accomplishments that we're proud of# SmartUtilities Sentinel

An agent that watches utility sensors across a property portfolio, remembers every past incident, and takes action on its own — built on Google Cloud's Agent Development Kit with Gemini, grounded in MongoDB Atlas.


💡 Inspiration

Commercial property portfolios waste billions of dollars a year on utility anomalies nobody catches in time — slab leaks, runaway HVAC, equipment left running overnight, silent meter failures. The feedback loop is brutally slow: operators find out from a utility bill that lands weeks after the damage is done.

The data to catch these events early already exists — it's streaming off water, electric, and gas meters in real time. What's missing is something that watches all of it continuously, knows what a real problem looks like because it has seen one before, and acts without waiting for a human to read a dashboard.

I wanted to find out how far a single agent could go if you gave it three things: live meter data, a memory of past incidents it could search semantically, and real tools to act with. SmartUtilities Sentinel is that experiment.

🛠️ What it does

Sentinel monitors water, electricity, and gas across a portfolio of commercial and residential properties. When a reading goes abnormal, it doesn't just raise an alert — it runs an investigation and responds:

  1. Detects the anomaly statistically against a rolling per-sensor baseline.
  2. Looks up the property — address, square footage, tenant contact — through the MongoDB MCP server.
  3. Recalls precedent by embedding a description of the anomaly and running Atlas Vector Search over a library of past incidents.
  4. Acts — simulates utility shutoff for safety-critical events, notifies the tenant, opens a severity-driven work order linked to the matched incident, and records the resolved case back into the library so the next run is smarter.
  5. Benchmarks every property's consumption against the rest of the portfolio and against public EIA/EPA baselines.

Every reasoning step is captured in a run trace the dashboard renders, so an operator (or a judge) can replay the agent's tool calls, MCP reads, vector-search hits, and final summary for any incident.

A concrete run from the demo: a synthetic water-main leak with a z-score of 82 produces a 13-step agent run — two MongoDB MCP reads, a vector match at 0.93 cosine similarity to a prior slab leak, then shutoff → tenant SMS → critical work order → incident record, all written back to Atlas.

🧮 How it works — the math is load-bearing

The agent never eyeballs raw data. MongoDB does the quantitative work; Gemini interprets what MongoDB flags.

Anomaly detection. Each new reading $x$ is scored against a rolling baseline (mean $\mu$, standard deviation $\sigma$) computed over a 60-minute window per sensor with MongoDB's aggregation framework:

$$ z = \frac{x - \mu}{\sigma} $$

An event is raised only when it clears the threshold with enough samples in the window:

$$ |z| \ge 3 \quad \text{and} \quad n \ge 10 $$

A deterministic, time-bucketed event id collapses duplicate detections across replicas to one agent run per sensor per cooldown window, so a sustained leak doesn't spawn a hundred runs.

Semantic recall. The anomaly is embedded to a 768-dimensional vector with gemini-embedding-001 and matched against stored incident embeddings via Atlas Vector Search, ranked by cosine similarity:

$$ \text{sim}(\mathbf{q}, \mathbf{d}) = \frac{\mathbf{q} \cdot \mathbf{d}}{\lVert \mathbf{q} \rVert \, \lVert \mathbf{d} \rVert} $$

The top-$k$ hits give the agent precedent — what the incident was, how it was resolved, who acted — which it cites in its reasoning.

Portfolio benchmarking. Using $setWindowFields and $lookup, each property's daily consumption is normalized by floor area and ranked against the portfolio:

$$ \text{intensity} = \frac{\text{kWh}_{\text{daily}}}{\text{sqft}}, \qquad \text{percentile}_p = \frac{\operatorname{rank}(p)}{N} $$

Industry baselines (EIA CBECS / RECS, EPA WaterSense) turn a raw number into a judgment: is this property's usage actually a problem this week?

🏗️ How I built it

Agent. A google.adk.agents.LlmAgent running Gemini 2.5 Pro on Vertex AI, orchestrated by an ADK Runner with an InMemorySessionService, with explicit Gemini safety settings on every call. It exposes seven tools — three MongoDB reads routed through the MCP client, four custom action tools that write to Atlas.

MongoDB — five Atlas capabilities, all on the critical path.

  • MongoDB MCP server — the agent's read layer, spawned by the backend as a stdio subprocess (npx mongodb-mcp-server) on a per-call session lifecycle. Every read is tagged transport:"mcp" in the run trace, so the partner-MCP integration is visible end to end.
  • Atlas Vector Search — 768-d incident embeddings for precedent retrieval, queried through the same MCP server.
  • Time Series (sensor_readings) — water/electric/gas meter data.
  • Aggregation Framework ($setWindowFields) — rolling-baseline anomaly stats and portfolio benchmarks.
  • Change Streams — the agent's wake signal, firing on $|z| \ge 3$ deviations.

Google Cloud. Vertex AI (Gemini + embeddings); the Agent Development Kit (ADK) as the code-first path in the Agent Builder ecosystem; Cloud Run for the FastAPI backend and Next.js frontend (both scale-to-zero, idle cost ≈ \$0); Cloud Build + Artifact Registry for images; Secret Manager for the MongoDB connection string (injected via --set-secrets, never plaintext); a minimum-privilege runtime service account.

Frontend. A Next.js dashboard with live charts, an incident feed, the agent run-trace viewer, and an aggregation-driven benchmarks page with a cited methodology view.

🧱 Challenges I faced

  • Running an MCP server inside Cloud Run. Cloud Run's filesystem is read-only except /tmp. The MCP server's npx/Node spawn broken-pipe'd instantly until I redirected every state path into /tmp (HOME, npm_config_cache, MDB_MCP_LOG_PATH, XDG_*) and pre-installed mongodb-mcp-server into the image so it spawns without a registry fetch.
  • stdio MCP session lifecycle under anyio. Reusing one session across async tasks bled anyio cancel scopes. The fix: a fresh stdio session per MCP call, tolerating the BrokenResourceError on teardown (the server exits right after responding — the result is already in hand).
  • The MCP server's prompt-injection guard. Results come wrapped in <untrusted-user-data-UUID>…</untrusted-user-data-UUID> tags, and the surrounding prose names those tags too — so parsing has to try multiple matches, longest-first, to pull the real payload.
  • Migrating to a fresh GCP project mid-build after the original was suspended — re-auth gcloud + ADC, re-enable APIs, re-allowlist Atlas, redeploy. The MCP integration came back intact: transport:"mcp", zero fallbacks.

📚 What I learned

  • Putting MongoDB at the center is what makes the agent useful. The statistics live in $setWindowFields, the retrieval lives in $vectorSearch, and the LLM never detects anomalies from raw data — it interprets what Mongo flags. That separation kept the demos fast and the agent traces readable.
  • A real stdio MCP integration in a managed container is an architectural artifact, not a wrapper. The hard, valuable part is the detail work — the /tmp env redirects, the per-call session, the broken-pipe tolerance, the guard-tag parsing. That is the integration.
  • Determinism matters in agent plumbing. A time-bucketed, content-derived event id is what stops a sustained anomaly — or multiple Cloud Run replicas — from stampeding the agent with duplicate runs. Idempotency is a feature, not an afterthought.
  • Secret Manager + explicit safety settings are cheap, high-signal ways to show the agent ships like a service, not a script.

🚀 What's next

  • Tighten the Atlas IP allowlist after judging.
  • Wire real notification providers (SendGrid, Twilio) end to end.
  • Add more partner-MCP integrations (e.g., observability) as additional read-brains for the agent.

Live app: https://sentinel-frontend-txihkrceaq-uc.a.run.app Built with: `

What we learned

-Utility data is years old

What's next for SmartUtilities Sentinel

-Real estate buy in

Built With

  • artifact-registry
  • atlas-vector-search
  • cloud-build
  • cloud-run
  • fastapi
  • gemini
  • google-adk
  • google-cloud
  • model-context-protocol
  • mongodb
  • mongodb-atlas
  • mongodb-mcp-server
  • nextjs
  • python
  • secret-manager
  • tailwindcss
  • typescript
  • vertex-ai
Share this project:

Updates