---
season: 3
season_name: "Season 3 — October 2026 (Autumn / Halloween Edition)"
day: 23
title: "Agent Topology: See What Breaks Before You Delete a Tool"
summary: "Query App Topology to map which agents are bound to an MCP server, which ones are calling it right now, and whether a change is safe to ship."
tags: ["Phase 5: App Topology", "Agent Registry", "Observability", "Blast Radius"]
canonical_url: "https://adventofagents.com/2026/10/23"
markdown_url: "https://adventofagents.com/2026/10/23.md"
video_url: "https://www.youtube.com/embed/CJlc5-MCyMA"
primary_video:
  title: "Day 23: Agent Topology & Blast-Radius Mapping — 5-Minute Google Cloud Kata"
  creator_name: "Dani Zamora & Nikhil Jethava"
  duration: "8:16"
  video_url: "https://www.youtube.com/embed/CJlc5-MCyMA"
---

# 🕸️ Day 23: Agent Topology: See What Breaks Before You Delete a Tool

> **Season 3 — October 2026 (Autumn / Halloween Edition)** · [Interactive Web View](https://adventofagents.com/2026/10/23?utm_source=adventofagents&utm_medium=markdown&utm_campaign=adventofagents_s3_2026&utm_content=day23) · [Raw Markdown](https://adventofagents.com/2026/10/23.md?utm_source=adventofagents&utm_medium=markdown&utm_campaign=adventofagents_s3_2026&utm_content=day23)

**Summary:** Query App Topology to map which agents are bound to an MCP server, which ones are calling it right now, and whether a change is safe to ship.

**Day 23 of Google's Advent of Agents — Season 3**

You want to retire an old MCP server, or ship a breaking change to one of its tools. Which agents are bound to it? Which ones are calling it right now, from which runtime, and with what error rate? In most organizations that answer is scattered across Agent Registry, Cloud Run, GKE, Cloud Trace and a dozen README files, so the change ships blind and the on-call engineer finds out at 2 AM.

**How It Works**

**App Topology** builds one graph of your Google Cloud estate: every discovered resource (Cloud Run services, GKE deployments, Reasoning Engines, Agent Registry agents, MCP servers, skills and bindings) is a node, and relationships such as `DEPENDS_ON` and `Observability/SENDS_TRAFFIC` are edges. You query it with a small JSON pattern: a `startingNode`, the edge label to follow (`direction: FROM` means "who points at me?"), and the node types you expect on the other side. The same pattern runs through `gcloud app-topology resources-graph generate`, the REST API, or the App Topology remote MCP server, so an agent can ask the question before it changes anything.

- **Static blast radius (who is bound to it)**: Start at the MCP server (`Base/MCPServer`, filtered by `Base/displayName`), follow `DEPENDS_ON` backwards to its Agent Registry `Binding` nodes, then one more hop to the agents behind them (`Base/Agent` covers Reasoning Engines, Cloud Run services and GKE deployments). This is the declared contract.
- **Live blast radius (who is calling it now)**: Follow `Observability/SENDS_TRAFFIC` edges into the same node. Each edge is built from Cloud Trace and carries `Observability/errorRate` and `Observability/p95Latency`, so you also catch shadow consumers that never registered a binding, and you see the health of every call path.
- **Try it yourself**: Run the `blast_radius.py` snippet from the "Code" tab. It runs both patterns and prints `SAFE` when the server has zero bound dependents and zero live callers, otherwise the list of owners to talk to first. Point Gemini CLI or Antigravity at the App Topology MCP server and ask the same question in plain English: "Which agents depend on MCP server X in project Y, and which of them are sending it traffic right now?"

**Resources:**

- [App Topology overview](https://g.dev/cloud/adventofagents-season3-docs-app-topology-overview)
- [Use the App Topology API: agentic resource and agent traffic patterns](https://g.dev/cloud/adventofagents-season3-docs-app-topology-api)
- [About queries: pattern syntax and limits](https://g.dev/cloud/adventofagents-season3-docs-app-topology-queries)
- [Use the App Topology remote MCP server](https://g.dev/cloud/adventofagents-season3-docs-app-topology-mcp)

## Code & Commands

### blast_radius.py

```python
"""Day 23: Blast radius of an MCP server = registered dependents + live callers."""
import json, os, subprocess, tempfile

PROJECT = os.environ.get("PROJECT_ID", "my-project")
TARGET = os.environ.get("MCP_SERVER", "k8s-mcp-server-deployment")  # Base/displayName of the MCP server

def node(alias, labels, prop=None):
    pattern = {"labelMatcherExpr": labels}
    if prop:
        pattern["propertyMatcherExpr"] = prop
    return {"alias": alias, "labelPropertiesPattern": pattern}

def hop(edge_label, graph):
    # direction FROM = follow edges that point AT the current node ("who depends on me?")
    return {"edge": {"direction": "FROM", "labelPropertiesPattern": {"labelMatcherExpr": edge_label}}, "graph": graph}

MCP = node("mcp", "Base/MCPServer", f'mcp.Base/displayName = "{TARGET}"')

# 1) Static blast radius: agent -DEPENDS_ON-> Agent Registry Binding -DEPENDS_ON-> this MCP server
DEPENDENTS = {"startingNode": MCP, "neighbors": [hop("DEPENDS_ON", {
    "startingNode": node("binding", "Base/agentregistry.googleapis.com/Binding"),
    "neighbors": [hop("DEPENDS_ON", {"startingNode": node("consumer", "Base/Agent")})]})]}

# 2) Live blast radius: who is really calling it right now (Cloud Trace, with error rate and p95 latency)
CALLERS = {"startingNode": MCP, "neighbors": [hop("Observability/SENDS_TRAFFIC", {
    "startingNode": node("caller", "Base/Agent")})]}

def query(pattern):
    with tempfile.NamedTemporaryFile("w", suffix=".json", delete=False) as f:
        json.dump(pattern, f)
    try:
        out = subprocess.run(["gcloud", "app-topology", "resources-graph", "generate", "--domains=SRE",
                              f"--project={PROJECT}", f"--pattern-file={f.name}", "--format=json"],
                             capture_output=True, text=True, check=True).stdout
        graph = json.loads(out or "[]")
        return {} if isinstance(graph, list) else graph  # gcloud prints [] when nothing matches
    finally:
        os.unlink(f.name)

def label(graph, name):
    n = next((n for n in graph.get("nodes", []) if n.get("name") == name), {})
    kind = n.get("context", {}).get("type", "?").replace("Base/", "")
    return f'{kind} "{n.get("properties", {}).get("Base/displayName") or name.rsplit("/", 1)[-1]}"'

if not query({"startingNode": MCP}).get("nodes"):
    raise SystemExit(f"No MCP server with displayName '{TARGET}' in {PROJECT} (run.sh step 1 lists the known ones)")
deps, live = query(DEPENDENTS), query(CALLERS)
by_dest = {}
for e in deps.get("edges", []):
    by_dest.setdefault(e["destinationNodeName"], []).append(e["sourceNodeName"])
mcp_ids = [n["name"] for n in deps.get("nodes", []) if "Base/MCPServer" in n.get("labels", [])]
consumers = set()
for m in mcp_ids:                        # MCP server <- Binding <- agent
    for b in by_dest.get(m, []):
        consumers.update(by_dest.get(b, []))

print(f"== Blast radius of MCP server '{TARGET}' in {PROJECT} ==")
print(f"Registered dependents (Agent Registry bindings): {len(consumers)}")
for c in sorted(consumers):
    print("  -", label(deps, c))
calls = live.get("edges", [])
print(f"Live callers in recent traces: {len(calls)}")
for e in calls:
    p = e.get("properties", {})
    print(f"  - {label(live, e['sourceNodeName'])}  errorRate={float(p.get('Observability/errorRate', 0)):.1%}"
          f"  p95={float(p.get('Observability/p95Latency', 0)):.0f} ms")
print("Verdict:", "SAFE: nothing depends on it" if not consumers and not calls else "STOP: coordinate with the owners above")
```

### run.sh

```bash
export CLOUDSDK_METRICS_ENVIRONMENT="advent-of-agents-s3-day23"

#!/usr/bin/env bash
set -euo pipefail
# Day 23: map the blast radius of one MCP server in under five minutes.
export PROJECT_ID="${PROJECT_ID:-my-project}"
# Needs roles/apptopology.viewer on the project.
gcloud services enable apptopology.googleapis.com --project="${PROJECT_ID}"

echo "=== 1. Which MCP servers does App Topology already know about? ==="
echo '{"startingNode":{"alias":"mcp","labelPropertiesPattern":{"labelMatcherExpr":"Base/MCPServer"}}}' > mcp_servers.json
gcloud app-topology resources-graph generate --domains=SRE --project="${PROJECT_ID}" \
  --pattern-file=mcp_servers.json --format=json \
  | python3 -c 'import json,sys; g=json.load(sys.stdin); g=g if isinstance(g,dict) else {}; [print(" ", n.get("context", {}).get("type", "?").replace("Base/",""), "|", n.get("properties", {}).get("Base/displayName", n.get("name", "?"))) for n in g.get("nodes", [])]'

echo "=== 2. Static + live blast radius for one of them ==="
export MCP_SERVER="${MCP_SERVER:-k8s-mcp-server-deployment}"   # paste a display name from step 1
python3 blast_radius.py
```

### settings.json (Gemini CLI MCP)

```json
{
  "mcpServers": {
    "app-topology": {
      "httpUrl": "https://apptopology.googleapis.com/mcp",
      "authProviderType": "google_credentials",
      "oauth": { "scopes": ["https://www.googleapis.com/auth/cloud-platform"] },
      "timeout": 30000,
      "headers": { "x-goog-user-project": "<YOUR_PROJECT_ID>" }
    }
  }
}
```

## Resources & Links

- **[App Topology overview](https://g.dev/cloud/adventofagents-season3-docs-app-topology-overview)** — One graph of discovered resources, agents, MCP servers, deployments, traffic and security data.
- **[Use the App Topology API](https://g.dev/cloud/adventofagents-season3-docs-app-topology-api)** — gcloud and REST walkthrough with the agentic resource and agent traffic query patterns.
- **[About App Topology queries](https://g.dev/cloud/adventofagents-season3-docs-app-topology-queries)** — Query structure, node and edge matching, directions and limits.
- **[Use the App Topology remote MCP server](https://g.dev/cloud/adventofagents-season3-docs-app-topology-mcp)** — Connect Gemini CLI, Antigravity or any MCP client and ask topology questions in plain English.
