Project · gcp-mcp-standalone

Status Dashboard

A single-user, Grafana-styled status page for Bob's infra — health, GCP month-to-date cost, and recent activity across the stack — live at

type componentstatus activegcp · dashboard · cloudflare-access · security

status.bobsmcp.uk since 2026-07-11. It runs as a second, independent Docker Compose project on the existing broker VM (no new compute cost) and is gated by Cloudflare Access to Bob's email alone. Deliberately not wired through the MCP broker — a headless poller calls provider REST APIs directly and writes a sanitized snapshot the static page reads.

Key points

  • Two processes, no new ports. A Python poller (no listening port) writes snapshot.json; an nginx container serves the static site + snapshot on 127.0.0.1:8080 only. Public reach is via the existing outbound Cloudflare Tunnel — the deny-all firewall is unchanged.
  • Keyless identity. The poller impersonates a dedicated, read-only mcp-status-poller-sa (via mcp-broker-sa's metadata identity + the IAM Credentials API) — no downloadable key exists (the org enforces disableServiceAccountKeyCreation). The poller SA has zero compute write verbs, strictly weaker than the broker's own mcpBrokerOps.
  • Sources (one module each, own cadence): GCP compute/logging (5 min), GCP billing month-to-date (6 h, BigQuery export), Tailscale devices, GitHub + GitLab activity, Cloudflare health, Notion recent edits (5 min), Adzuna roles (30 min, free-tier rate limit).
  • Leakage guard is real, not cosmetic. Every source returns an allow-listed dict (e.g. Tailscale emits only name/os/online/last-seen — never IPs or node keys; billing is a single cost sum, no row detail or account IDs); snapshot.schema.json (additionalProperties: false throughout) + a test that injects a fake service_account_key and asserts rejection fail the build if any field ever leaks.
  • Frontend is inert + hardened. Vanilla HTML/CSS/JS, no build step; untrusted values rendered via textContent, links regex-guarded to http(s)://. nginx sends a strict CSP + nosniff + no-referrer + frame-ancestors 'none'.

Access & data path

Browser → Cloudflare Access (email OTP) → Cloudflare Tunnel → nginx 127.0.0.1:8080 → static page → fetches /data/snapshot.json. The browser never holds a credential; it only ever reads the pre-built, schema-validated snapshot. See gcp-mcp-standalone/network-access for how this sits alongside the broker's paths.

Details

Source: this repo (status-dashboard/poller/, status-dashboard/site/), deployed by ansible/status-dashboard.yml as its own compose project. Security reviewed and live-verified twice (docs/security-review-2026-07-10-status-dashboard.md; folded into gcp-mcp-standalone/security-review). Phase 2 (AWS/"lifebot" cost + EC2 panel) is deferred — the schema and frontend are already structured for it.

Sources

Compiled from wiki/projects/gcp-mcp-standalone/status-dashboard.md · git is the source of truth