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:
- Detects the anomaly statistically against a rolling per-sensor baseline.
- Looks up the property — address, square footage, tenant contact — through the MongoDB MCP server.
- Recalls precedent by embedding a description of the anomaly and running Atlas Vector Search over a library of past incidents.
- 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.
- 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 taggedtransport:"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'snpx/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-installedmongodb-mcp-serverinto 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
BrokenResourceErroron 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
/tmpenv 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
Log in or sign up for Devpost to join the conversation.