Agent Gateway egress from Agent Runtime to VPC network

1. Introduction

This Codelab explores Agent Gateway egress governance for AI agents accessing remote endpoints through a private VPC network. Agent Gateway operating in egress (agent-to-anywhere) mode supports private connectivity to a VPC network using Private Service Connect (PSC) interfaces. In this scenario, Agent Runtime uses Agent Gateway to access a remote streamable HTTP MCP server hosted on Cloud Run by using a Private Service Connect (PSC) endpoint for Google APIs in the VPC network.

AI apps, from standalone chatbots to autonomous systems with multi-agent workflows, can dynamically invoke external tools. Providing agents with controlled access for querying databases, fetching web content, and executing actions is essential for safe and productive agents. However, in enterprise environments, securing agent tool execution is a challenge. If an agent has direct network access, prompt injection attacks or model hallucinations can cause the agent to exfiltrate sensitive data or run destructive commands.

To manage autonmous agents at scale, Agent Gateway provides a centralized, zero-trust enforcement point directly integrated into the Agent Platform architecture. Instead of relying on custom implementations unique to each application or agent code, Agent Gateway provides network routing, agent governance, and runtime security enforced at the platform level.

When Agent Gateway operates in egress (agent-to-anywhere) mode, it proxies all outbound requests from agents configured to use the gateway. Each agent workload request is authenticated using the unique Agent Identity and authorized using Identity-Aware Proxy (IAP) policies. MCP tool calls are dynamically decoded and inspected for policy enforcement. Security admins can centrally enforce fine-grained permissions to ensure agents can only invoke approved endpoints and methods.

What you build

  • Agent Gateway in egress (agent-to-anywhere) mode
  • Identity-Aware Proxy (IAP) authorization extension
  • Agent Runtime ADK agent with agent identity
  • Cloud Run streamable HTTP MCP server
  • Agent Registry with Google API and custom MCP endpoints
  • Authorization policies with IAM access control
  • VPC network resources, Cloud DNS zone, and PSC endpoint for Google APIs
  • PSC network attachment for Agent Gateway egress
  • Cloud Next Generation Firewall (NGFW) firewall policy rules

figure1

Fig 1. Codelab architecture

What you learn

  • How to deploy Agent Gateway in a structured configuration sequence
  • How to configure Agent Gateway with private VPC network connectivity
  • How to transition authorization policies from a dry-run to enforced mode
  • How to register Google API endpoints and MCP servers in the Agent Registry
  • How to audit Agent Gateway logs to validate egress governance
  • How to validate Agent Gateway egress traffic using Cloud NGFW logs

What you need

  • A Google Cloud project with billing enabled
  • IAM permissions to provision networking services, BigQuery datasets, and Agent Platform resources
  • A POSIX-compatible shell (bash or zsh) with Google Cloud CLI (gcloud and bq components) installed
  • Command-line tools: git, curl, jq (JSON processor), Python 3, and uv (Python package manager)

2. Concepts

Deployment sequence

This codelab follows a structured configuration sequence so agents have access to the necessary resources on initialization and connectivity can be confirmed before applying enforced access policies.

This Codelab uses the following deployment sequence:

  1. Start in DRY_RUN mode: Ensure that agent requests are successfully arriving at the gateway and passing through (audit-only mode).
  2. Register all services: Register all agentic components in Agent Registry and confirm agent tool access. Use Cloud Logging and the Agent Gateway observability dashboard to view queries to Agent Registry.
  3. Create authorization policies: Create and apply authorization policies to allow egress traffic from agents to MCP servers and endpoints.
  4. Switch to ENFORCED mode: Update authorization extension policy to enforce policies and block unauthorized egress traffic.

figure2

Fig 2. Deployment sequence

Egress routing topology

Agent Gateway egress (agent-to-anywhere) acts as a centralized, zero-trust outbound proxy for agentic workloads. When an agent makes a request to an external tool or API, the outbound request is intercepted by Agent Gateway and evaluated against governance policies before routing to its destination. In a Google Cloud Agent Platform architecture, Agent Gateway provides data plane connectivity to:

  • Private networks (VPC): Outbound traffic targeting internal enterprise APIs, microservices, databases hosted within private VPCs, and on-premises or cross-cloud networks reachable through hybrid connectivity.
  • External networks (Internet): Outbound traffic targeting third-party web services, SaaS APIs, or public endpoints.
  • AI services & Agent Platform: Outbound traffic targeting managed Google Cloud APIs, foundation models, Google MCP endpoints (such as BigQuery), and platform governance services (Agent Registry and IAP policies).

figure3

Fig 3. Agent Gateway egress routing topology

The focus of this Codelab is outbound traffic from a custom agent on Agent Runtime targeting a PSC for Google APIs endpoint deployed in a VPC network. This architecture enables private access to the MCP server hosted on Cloud Run by using the PSC network attachment for Agent Gateway egress.

VPC connectivity

Agent Gateway egress (agent-to-anywhere) is able to connect to a VPC network by specifying the networkConfig: egress: networkAttachment: [URI] in the deployment YAML configuration file. This setting configures routing rules on Agent Gateway to send traffic that resolves to private IP addresses through the PSC network attachment into the VPC network.

figure4

Fig 4. Agent Gateway VPC connectivity

Agent Gateway is able to resolve private DNS hostnames using Cloud DNS peering to a specified target project and VPC network. Any private zones listed in the dnsPeeringConfig: domains: [NAME] configuration will use Cloud DNS records in the target project. Any other DNS queries will use the default VPC Cloud DNS public resolver.

This concludes the concepts portion... next on to the Setup section.

3. Setup

Required IAM roles

The following roles are required to create the resources in this Codelab:

Category

Required IAM role (ID)

Description

API management

roles/serviceusage.serviceUsageAdmin

Enable Google Cloud API services

Networking & gateway

roles/networkservices.admin

Provision Agent Gateway

Service extensions

roles/serviceextensions.admin

Configure routing extensions

Network security

roles/networksecurity.admin

Deploy authorization policies

Agent Registry

roles/agentregistry.admin

Catalog allowed hosts

Vertex AI & agents

roles/aiplatform.admin

Deploy Agent Runtime workloads

Egress policies

roles/iap.admin

Apply roles/iap.egressor policies

Cloud Storage

roles/storage.admin

Manage deployment staging buckets

Logs & auditing

roles/logging.viewer

Inspect traces and audit logs

Cloud Run Developer

roles/run.developer

Deploy Cloud Run services

Artifact Registry Reader

roles/artifactregistry.reader

Pull container images

Compute Network Admin

roles/compute.networkAdmin

Manage VPC networking resources

DNS Administrator

roles/dns.admin

Manage Cloud DNS zones and records

Alternatively, use a broad basic role like roles/admin or legacy role roles/owner.

Access your project

This Codelab uses a single Google Cloud project. Configuration steps use gcloud CLI and Linux shell commands.

Start by accessing your Google Cloud project command line:

Set your Project ID

gcloud config set project SET_YOUR_PROJECT_ID_HERE

Authenticate session

# login to gcloud cli
gcloud auth login
# login for gcloud api
gcloud auth application-default login

Set shell environment variables

# set custom var for slug (eg, "foo") and region preference
export SLUG="foo"
export REGION="us-central1"
export MREGION="us"
echo ${SLUG}
echo ${REGION}
echo ${MREGION}
# create project vars (automatic)
export PROJ_ID=$(gcloud config list --format="value(core.project)")
export PROJ_NO=$(gcloud projects describe ${PROJ_ID} --format="value(projectNumber)")
export ORG_ID=$(gcloud projects get-ancestors ${PROJ_ID} --format="value(id)" | tail -n 1)
export USER_IDENTITY=$(gcloud config get-value account)
echo ${PROJ_ID}
echo ${PROJ_NO}
echo ${ORG_ID}
echo ${USER_IDENTITY}
# create resource vars (automatic)
export AGW_NAME="agw-${SLUG}-${REGION}-ata"
export AGW_URI="projects/${PROJ_ID}/locations/${REGION}/agentGateways/${AGW_NAME}"
export RE_AGENT_NAME="agent-weather"
export RE_AGENT_ID_SET="principalSet://agents.global.org-${ORG_ID}.system.id.goog/attribute.platformContainer/aiplatform/projects/${PROJ_NO}"
export STAGING_BUCKET="agent-staging-${PROJ_NO}"
export MCP_NAME="mcp-weather"
export MCP_DISPLAY_NAME="${MCP_NAME}-${PROJ_NO}.${REGION}.run.app"
export MCP_URL="https://${MCP_DISPLAY_NAME}/mcp"
echo ${AGW_NAME}
echo ${AGW_URI}
echo ${RE_AGENT_NAME}
echo ${RE_AGENT_ID_SET}
echo ${STAGING_BUCKET}
echo ${MCP_NAME}
echo ${MCP_DISPLAY_NAME}
echo ${MCP_URL}
# create local dir for config files
mkdir -p cfg

If running a self-managed install of the Google Cloud SDK (ie, outside of Cloud Shell), update the components to the latest version.

# update gcloud cli
gcloud components update

Enable API services

# enable google apis (agent platform bundle, part 1)
gcloud services enable \
  agentregistry.googleapis.com \
  aiplatform.googleapis.com \
  apphub.googleapis.com \
  apptopology.googleapis.com \
  cloudapiregistry.googleapis.com \
  cloudtrace.googleapis.com \
  compute.googleapis.com \
  dataform.googleapis.com \
  iam.googleapis.com \
  iamconnectors.googleapis.com \
  iap.googleapis.com \
  logging.googleapis.com \
  modelarmor.googleapis.com \
  monitoring.googleapis.com \
  networksecurity.googleapis.com \
  networkservices.googleapis.com \
  notebooks.googleapis.com \
  observability.googleapis.com
# enable google apis (agent platform bundle, part 2)
gcloud services enable \
  securitycenter.googleapis.com \
  saasservicemgmt.googleapis.com \
  storage.googleapis.com \
  telemetry.googleapis.com \
  texttospeech.googleapis.com
# enable google apis (all the rest)
gcloud services enable \
  run.googleapis.com \
  artifactregistry.googleapis.com \
  cloudbuild.googleapis.com \
  dns.googleapis.com

This concludes the setup portion... next on to the Network section.

4. Network

The VPC network is deployed using custom mode to create regular subnets supporting the PSC network attachment for Agent Gateway and Cloud Run direct VPC egress.

The PSC endpoint for Google APIs is deployed using a single /32global internal IPv4 address to support private internal access to Cloud Run. This IP address is not assigned from any regional subnet.

Create networks

Create a global VPC network.

# create vpc network
gcloud compute networks create vnet-${SLUG} --subnet-mode=custom

Create subnets for the Agent Gateway PSC network attachment and Cloud Run direct VPC egress.

# create subnet for agent gateway psc na
gcloud compute networks subnets create subnet-${REGION}-agw \
  --network=vnet-${SLUG} \
  --range=192.168.10.0/28 \
  --region=${REGION} \
  --enable-private-ip-google-access

# create subnet for cloud run dvpc egress
gcloud compute networks subnets create subnet-${REGION}-crun \
  --network=vnet-${SLUG} \
  --range=10.10.10.0/24 \
  --region=${REGION} \
  --enable-private-ip-google-access

Create firewall rules

Create a firewall policy and rule to allow all egress traffic with logging enabled. This will be used to monitor traffic egressing from Agent Gateway to the VPC network.

# create fw policy
gcloud compute network-firewall-policies create fw-policy-${SLUG} --global
# create fw policy rule
gcloud compute network-firewall-policies rules create 1001 \
  --description="allow all out and log" \
  --firewall-policy=fw-policy-${SLUG} \
  --global-firewall-policy \
  --action=allow \
  --direction=EGRESS \
  --layer4-configs=all \
  --dest-ip-ranges=0.0.0.0/0 \
  --enable-logging
# associate fw policy to vpc network
gcloud compute network-firewall-policies associations create \
  --name=fw-policy-bind-${SLUG} \
  --firewall-policy=fw-policy-${SLUG} \
  --network=vnet-${SLUG} \
  --global-firewall-policy

Create PSC network attachment

Create a PSC network attachment configured to automatically accept new producer connections.

# create psc network attachment
gcloud compute network-attachments create psc-na-${REGION}-agw \
  --region=${REGION} \
  --subnets=subnet-${REGION}-agw \
  --connection-preference=ACCEPT_AUTOMATIC

Verify PSC network attachment

# show psc network attachment details
gcloud compute network-attachments describe psc-na-${REGION}-agw --region=${REGION}
# fetch psc na uri
export PSC_NA_URI=$(gcloud compute network-attachments describe psc-na-${REGION}-agw \
  --region=${REGION} \
  --format="value(selfLink.scope(v1))")
echo ${PSC_NA_URI}

Create PSC endpoint

Create an IP address reservation for the global PSC endpoint. The address used here cannot come from, or overlap with, an existing subnet.

# set env var for psc ep ip address
export PSC_EP_IP="240.0.0.10"
echo ${PSC_EP_IP}
# reserve internal global ipv4 address
gcloud compute addresses create ip-psc2gapis \
  --global \
  --purpose=PRIVATE_SERVICE_CONNECT \
  --addresses=${PSC_EP_IP} \
  --network=vnet-${SLUG}

Create a PSC endpoint for Google APIs using the all-apis bundle, which includes Cloud Run (run.app).

# create psc endpoint for google apis
gcloud compute forwarding-rules create psc2gapis \
  --global \
  --network=vnet-${SLUG} \
  --address=ip-psc2gapis \
  --target-google-apis-bundle=all-apis

Verify PSC endpoint

# show psc endpoint details
gcloud compute forwarding-rules describe psc2gapis --global

Create DNS zone and records

Create a private Cloud DNS managed zone for the run.app domain.

# create private dns zone
gcloud dns managed-zones create priv-zone-run \
  --description="private zone for cloud run" \
  --dns-name="run.app." \
  --visibility=private \
  --networks=vnet-${SLUG}

Create a wildcard DNS A record for *.run.app. pointing to the IP address of the PSC endpoint.

# create dns record
gcloud dns record-sets create *.run.app \
  --zone=priv-zone-run \
  --type=A \
  --ttl=300 \
  --rrdatas=${PSC_EP_IP}

Create a Cloud DNS policy to enable DNS query logging.

# create dns policy (logging)
gcloud dns policies create dns-policy-${SLUG} \
  --description="dns logging for vnet-${SLUG}" \
  --networks=vnet-${SLUG} \
  --enable-logging

Create Cloud NAT

Create a Cloud Router and Cloud NAT to support Cloud Run direct VPC egress for the MCP server to reach external weather data through the VPC network.

# create router for nat
gcloud compute routers create cr-nat-${SLUG}-${REGION} \
  --network=vnet-${SLUG} \
  --asn=16550 \
  --region=${REGION}
# create nat gateway
gcloud compute routers nats create nat-${SLUG}-${REGION} \
  --router=cr-nat-${SLUG}-${REGION} \
  --region=${REGION} \
  --auto-allocate-nat-external-ips \
  --nat-all-subnet-ip-ranges

This concludes the network portion... next on to the Gateway section.

5. Gateway

Before enforcing access controls, deploy Agent Gateway and configure the authorization extension in DRY_RUN mode. This allows outbound tool calls to succeed while logging evaluation results for audit purposes.

Here Agent Gateway uses a regional registry to support regional Agent Runtime resources and specifies the networkConfig fields for the VPC network PSC network attachment and DNS peering config.

Create gateway

# create new agent gateway config file (with network settings)
cat > cfg/${AGW_NAME}-networkConfig.yaml << EOF
name: ${AGW_NAME}
protocols:
  - MCP
googleManaged:
  governedAccessPath: AGENT_TO_ANYWHERE
registries:
  - "//agentregistry.googleapis.com/projects/${PROJ_ID}/locations/${REGION}"
networkConfig:
  egress:
    networkAttachment: ${PSC_NA_URI}
  dnsPeeringConfig:
    domains:
      - run.app.
    targetProject: ${PROJ_ID}
    targetNetwork: projects/${PROJ_ID}/global/networks/vnet-${SLUG}
EOF
# import agent gateway config file (create gateway)
gcloud network-services agent-gateways import ${AGW_NAME} \
  --source="cfg/${AGW_NAME}-networkConfig.yaml" \
  --location=${REGION}

Verify gateway

# show agent gateway details
gcloud network-services agent-gateways describe ${AGW_NAME} \
  --location=${REGION}
# show psc network attachment details
gcloud compute network-attachments describe psc-na-${REGION}-agw --region=${REGION}

Check that there is an accepted connection endpoint.

connectionEndpoints:
- ipAddress: 192.168.10.2
  projectIdOrNum: '[AGW_PROJECT_NO]'
  status: ACCEPTED
  subnetwork: https://www.googleapis.com/compute/v1/projects/${PROJ_ID}/regions/${REGION}/subnetworks/subnet-${REGION}-agw

This concludes the gateway portion... next on to the Authorization section.

6. Authorization

The Agent Gateway authorization extension for Identity-Aware Proxy (IAP) is a type of Service Extension used to delegate authorization decisions for all Agent Platform communications.

  1. Delegation flow: When an agent attempts to invoke an external endpoint or MCP server, it routes the request to Agent Gateway. Instead of evaluating access locally, the gateway uses the authorization extension to send a callout to the IAP evaluation service. IAP evaluates the agent (SPIFFE-based) identity against the IAM policy of the target resource in the Agent Registry. IAP returns an ALLOW or DENY decision back to the gateway, which either forwards the traffic onward or blocks it with an HTTP 403 Forbidden status code.
  2. Enforcement modes: In DRY_RUN mode, IAP evaluates requests and logs decisions in Cloud Logging without blocking traffic. In ENFORCE mode, any request from an unauthorized agent or to an unregistered target is immediately blocked.
  3. Binding layer: The service extension is connected to Agent Gateway using an authorization policy configured with the REQUEST_AUTHZ profile.

Authorization extension

An authorization extension specifies the security provider service with connectivity settings and other parameters that define how the service will operate and integrate with Agent Gateway.

Create authz extension

# create authz extension config file (dry run mode)
cat > cfg/${AGW_NAME}-svc-ext-authz-iap-dryrun.yaml << EOF
name: ${AGW_NAME}-svc-ext-authz-iap-dryrun
service: iap.googleapis.com
failOpen: true
timeout: 1s
metadata:
  iamEnforcementMode: "DRY_RUN"
  iapPolicyVersion: "V1"
EOF

Import authz extension

# import authz extension file (create extension)
gcloud service-extensions authz-extensions import ${AGW_NAME}-svc-ext-authz-iap-dryrun \
  --source=cfg/${AGW_NAME}-svc-ext-authz-iap-dryrun.yaml \
  --location=${REGION}

Verify authz extension

# show authz extension details
gcloud service-extensions authz-extensions describe ${AGW_NAME}-svc-ext-authz-iap-dryrun \
  --location=${REGION}

Authorization policy

An authorization policy binds the security provider (IAP) to the target gateway resource and specifies the interception profile (REQUEST_AUTHZ).

Create authz policy

# create authz policy config file (attach dry-run authz extension)
cat > cfg/${AGW_NAME}-authz-policy-profile-iap.yaml << EOF
name: ${AGW_NAME}-authz-policy-profile-iap
target:
  resources:
    - "projects/${PROJ_ID}/locations/${REGION}/agentGateways/${AGW_NAME}"
policyProfile: REQUEST_AUTHZ
action: CUSTOM
customProvider:
  authzExtension:
    resources:
      - "projects/${PROJ_ID}/locations/${REGION}/authzExtensions/${AGW_NAME}-svc-ext-authz-iap-dryrun"
EOF

Bind authz policy

# import authz policy config file (enable authz policy)
gcloud beta network-security authz-policies import ${AGW_NAME}-authz-policy-profile-iap \
  --source=cfg/${AGW_NAME}-authz-policy-profile-iap.yaml \
  --location=${REGION}

Verify authz policy

# show authz policy details
gcloud beta network-security authz-policies describe ${AGW_NAME}-authz-policy-profile-iap \
  --location=${REGION}

This concludes the authorization portion... next on to the Codebase section.

7. Codebase

The agent, MCP server, and endpoint registration code used for this Codelab are maintained in a remote Google Cloud GitHub repository. The following steps will clone the repository locally, copy the necessary files to the current working directory structure, and then cleanup temporary files.

A storage staging bucket is created for Agent Runtime to upload, build, and deploy the packaged agent application code and its dependency artifacts.

Fetch remote artifacts

# clone remote repository to temp local dir
git clone https://github.com/GoogleCloudPlatform/cloud-networking-solutions.git ./temp_agw_cuj_arun_egress_vpc
# copy agent runtime and endpoint definitions to working project dir
cp -r temp_agw_cuj_arun_egress_vpc/codelabs/agw-cuj-arun-egress-vpc/agent-weather ./agent-weather
cp -r temp_agw_cuj_arun_egress_vpc/codelabs/agw-cuj-arun-egress-vpc/mcp-weather ./mcp-weather
# remove temporary directory
rm -rf temp_agw_cuj_arun_egress_vpc

Create staging bucket

# create storage bucket for agent deployment artifacts
gcloud storage buckets create gs://${STAGING_BUCKET} --location=${REGION}
# verify staging bucket url
gcloud storage buckets list --format="value(storage_url)"

This concludes the codebase portion... next on to the Registry section.

8. Registry

The Agent Registry is a centralized inventory of all agents, MCP servers, and endpoints (APIs) in the AI ecosystem. It is also a catalog that can be used to list, search, and discover other registered tools and services for use by AI applications. Agent Gateway uses the registry data model as the governance framework to enforce egress access control. IAM role grants for principal calling agents are checked against the policy bound to the registry resource.

In order to bootstrap the environment and support the agent using various Google APIs and Services, create a single registry endpoint service that bundles the core API interfaces needed for this lab. This will make it easier to manage governance policies for common sets of tools that most agents will use.

Register endpoints

# create endpoint service (with multiple entries)
gcloud agent-registry services create core-gapi-services \
  --location=${REGION} \
  --display-name="gapi.core.services" \
  --description="core apis and services" \
  --endpoint-spec-type=no-spec \
  --interfaces=protocolBinding=JSONRPC,url=https://telemetry.googleapis.com \
  --interfaces=protocolBinding=JSONRPC,url=https://${REGION}-aiplatform.googleapis.com \
  --interfaces=protocolBinding=JSONRPC,url=https://cloudresourcemanager.googleapis.com \
  --interfaces=protocolBinding=JSONRPC,url=https://iamcredentials.googleapis.com

Verify endpoint registration

# list registry regional endpoints
gcloud agent-registry endpoints list --location=${REGION} \
  --flatten="interfaces[]" \
  --format="table(displayName, name.basename():label=ENDPOINT_ID, interfaces.url:label=URL)"

This concludes the registry portion... next on to the MCP server section.

9. MCP server

Grant IAM permissions for deployment

Grant permissions to the default compute service account to support deploying Cloud Run services from source code.

# grant cloud run builder role to default compute sa
gcloud projects add-iam-policy-binding ${PROJ_ID} \
  --member="serviceAccount:${PROJ_NO}-compute@developer.gserviceaccount.com" \
  --role="roles/run.builder"

Verify IAM permissions

# show iam policy on project for default compute sa
gcloud projects get-iam-policy ${PROJ_ID} \
  --flatten="bindings[].members" \
  --filter="bindings.members=serviceAccount:${PROJ_NO}-compute@developer.gserviceaccount.com" \
  --format="table(bindings.role:label=ROLE, bindings.members:label=PRINCIPAL_IDENTITY)"

Deploy MCP server

# deploy cloud run service
gcloud -q run deploy ${MCP_NAME} \
  --source=mcp-weather/server \
  --region=${REGION} \
  --no-allow-unauthenticated \
  --min-instances=1 \
  --network=vnet-${SLUG} \
  --subnet=subnet-${REGION}-crun \
  --vpc-egress=all-traffic \
  --startup-probe=httpGet.path=/warmup

Grant IAM permissions for ingress

Bind policy for user access

Grant permissions to the user account (self) for invoking the Cloud Run hosted MCP server. This is needed for running the toolspec generation script.

# grant run invoker role to user account on cloud run service level
gcloud run services add-iam-policy-binding ${MCP_NAME} \
  --region=${REGION} \
  --member="user:${USER_IDENTITY}" \
  --role="roles/run.invoker"

Verify IAM permissions

# show iam policy on cloud run service for invoker role
gcloud run services get-iam-policy ${MCP_NAME} \
  --region=${REGION} \
  --flatten="bindings[].members" \
  --filter="bindings.role=roles/run.invoker" \
  --format="table(bindings.members:label=PRINCIPAL_IDENTITY, bindings.role:label=ROLE)"

Create custom SA for MCP invocation

Create a custom service account for invoking the Cloud Run hosted MCP server. This service account will be used by the agent to fetch a valid Google-signed OIDC ID token to access Cloud Run.

# create custom sa for mcp server invocation
gcloud iam service-accounts create sa-${MCP_NAME}-invoker \
  --display-name="custom sa to invoke ${MCP_NAME}"
# set env var for service account identity
export MCP_INVOKER_SA="sa-${MCP_NAME}-invoker@${PROJ_ID}.iam.gserviceaccount.com"
echo ${MCP_INVOKER_SA}

Bind policy for service account

# grant run invoker role to custom sa on cloud run service level
gcloud run services add-iam-policy-binding ${MCP_NAME} \
  --role="roles/run.invoker" \
  --member="serviceAccount:${MCP_INVOKER_SA}" \
  --region=${REGION}

Verify IAM permissions

# show iam policy on cloud run service for invoker role
gcloud run services get-iam-policy ${MCP_NAME} \
  --region=${REGION} \
  --flatten="bindings[].members" \
  --filter="bindings.role=roles/run.invoker" \
  --format="table(bindings.members:label=PRINCIPAL_IDENTITY, bindings.role:label=ROLE)"

Grant IAM permissions for authentication

Grant service account impersonation roles to the Default Compute Service Account. This will allow the agent, running under Application Default Credentials (ADC), to mint Google-signed OIDC ID tokens on behalf of the custom invoker service account to authenticate calls to Cloud Run.

Bind policy for authentication

# grant oidc token creator role to default compute service account
gcloud iam service-accounts add-iam-policy-binding ${MCP_INVOKER_SA} \
  --role="roles/iam.serviceAccountOpenIdTokenCreator" \
  --member="serviceAccount:${PROJ_NO}-compute@developer.gserviceaccount.com"

# grant service account token creator role to default compute service account
gcloud iam service-accounts add-iam-policy-binding ${MCP_INVOKER_SA} \
  --role="roles/iam.serviceAccountTokenCreator" \
  --member="serviceAccount:${PROJ_NO}-compute@developer.gserviceaccount.com"

Verify IAM permissions

# show iam policy for the custom service account
gcloud iam service-accounts get-iam-policy ${MCP_INVOKER_SA} \
  --flatten="bindings[].members" \
  --format="table(bindings.role:label=ROLE, bindings.members:label=PRINCIPAL_IDENTITY)"

Create MCP toolspec

When registering an MCP server manually, a tool specification file must be included. A toolspec.json file lists all of the tools made available by the MCP server and lets other users discover them once registered.

The test_mcp.py script, included in the mcp-weather/ codebase, generates a toolspec.json file by connecting to the target MCP server (over SSE or HTTP) and calling list_tools() to retrieve all available tools exposed by the server.

# fetch oidc token for cloud run authentication
export OIDC_TOKEN=$(gcloud auth print-identity-token)
# generate toolspec for registry ingestion
uv --directory mcp-weather run test_mcp.py --toolspec=include --token="${OIDC_TOKEN}"

A look at the generated toolspec output shows the MCP attributes that Agent Gateway can use for egress policy evaluation and enforcement.

Verify MCP toolspec

# show toolspec mcp attributes
jq '.tools[] | {name, isReadOnly, isDestructive, isIdempotent, isOpenWorld}' mcp-weather/toolspec.json

Update Cloud Run ingress

Change the ingress setting for the Cloud Run service so it only accepts incoming requests from the VPC network. This prevents direct public access to the MCP endpoint and only allows internal resources to access it. Traffic destined to the PSC endpoint for Google APIs in the VPC network will be considered internal ingress and allowed.

# update cloud run service config
gcloud run services update ${MCP_NAME} \
  --region=${REGION} \
  --ingress=internal

Verify Cloud Run ingress

# show cloud run service details
gcloud run services describe ${MCP_NAME} --region=${REGION} | grep -iE 'ingress|egress'

Register MCP server

Now with the toolspec in hand, register the MCP server with the regional Agent Registry.

# register mcp server
gcloud agent-registry services create ${MCP_NAME} \
  --location=${REGION} \
  --display-name="${MCP_DISPLAY_NAME}" \
  --description="mcp server for weather info" \
  --mcp-server-spec-type=tool-spec \
  --mcp-server-spec-content=mcp-weather/toolspec.json \
  --interfaces=url=${MCP_URL},protocolBinding=JSONRPC

Verify MCP registration

# list registry regional mcp servers
gcloud agent-registry mcp-servers list --location=${REGION} --format="table(displayName, interfaces.url)"
# show mcp server registry details
gcloud agent-registry services describe ${MCP_NAME} --location=${REGION}
# fetch mcp server registry id
export MCP_REGISTRY_ID=$(gcloud agent-registry services describe ${MCP_NAME} \
  --location=${REGION} \
  --format="value(registryResource.basename())")
echo ${MCP_REGISTRY_ID}

This concludes the MCP server section... next on to the ADK agent section.

10. ADK agent

The agent-weather ADK agent deployed to Agent Runtime is configured with the following settings in the deployment script to integrate with Agent Platform:

  • "identity_type": types.IdentityType.AGENT_IDENTITY to provision a unique SPIFFE-based principal identity for the agent
  • "agent_gateway_config": { "agent_to_anywhere_config": {"agent_gateway": } } to direct all outbound agent-initiated traffic to Agent Gateway for policy evaluation and enforcement

The agent is also configured to use the MCP server by passing the URL and the service account for fetching the OIDC ID token.

Deploy agent

# deploy agent runtime workload
uv --directory agent-weather run python3 deploy_agent.py \
  --project=${PROJ_ID} \
  --region=${REGION} \
  --src-dir=./agent \
  --staging-bucket=${STAGING_BUCKET} \
  --display-name="${RE_AGENT_NAME}" \
  --description="agent for weather info" \
  --mcp-server-url="${MCP_URL}" \
  --mcp-invoker-sa="${MCP_INVOKER_SA}" \
  --enable-telemetry \
  --enable-agent-identity \
  --agent-gateway-egress=${AGW_URI} \
  --allow-token-sharing

Verify deployment

Fetch deployment vitals

# fetch agent runtime resource (reasoning engine) id
export RE_ENGINE_ID=$(curl -s -X GET "https://${REGION}-aiplatform.googleapis.com/v1/projects/${PROJ_ID}/locations/${REGION}/reasoningEngines" \
  -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" | \
  jq -r --arg name "${RE_AGENT_NAME}" '.reasoningEngines[] | select(.displayName==$name) | .name | split("/") | last')
echo ${RE_ENGINE_ID}
# fetch agent runtime (reasoning engine) agent identity
export RE_AGENT_IDENTITY=$(gcloud agent-registry agents list \
  --location=${REGION} --filter="displayName=${RE_AGENT_NAME}" \
  --format="value(attributes.'agentregistry.googleapis.com/system/RuntimeIdentity'.principal)")
echo ${RE_AGENT_IDENTITY}
# fetch agent registry uid for agent
export RE_AGENT_REGISTRY_ID=$(gcloud agent-registry agents list \
  --location=${REGION} \
  --filter="displayName=${RE_AGENT_NAME}" \
  --format="value(uid)")
echo ${RE_AGENT_REGISTRY_ID}

Verify registry entry

# show agent resource registry details
gcloud agent-registry agents describe ${RE_AGENT_REGISTRY_ID} --location=${REGION}

Verify gateway config

# show agent config details (gateway config)
curl -s -X GET "https://${REGION}-aiplatform.googleapis.com/v1/projects/${PROJ_ID}/locations/${REGION}/reasoningEngines/${RE_ENGINE_ID}" \
  -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  | jq '{displayName: .displayName, name: .name, effectiveIdentity: .spec.effectiveIdentity, agentGatewayConfig: .spec.deploymentSpec.agentGatewayConfig}'

Grant IAM permissions for authentication

When an agent is configured to use Agent Identity, it receives a token (JWT-SVID format) from the federated Security Token Service (STS) which is signed by sts.googleapis.com (the SPIFFE identity provider).

However, the built-in Cloud Run IAM invoker check only trusts and can verify standard OIDC ID tokens signed by the main Google OIDC issuer (accounts.google.com).

So a custom service account is used to exchange the federated STS token (JWT-SVID) obtained by the agent identity for a standard Google (OIDC) token obtained using service account impersonation at runtime.

Create the role bindings needed for the custom service account.

Bind policy for authentication (impersonation)

# grant service account token creator role to agent principal identity
gcloud iam service-accounts add-iam-policy-binding ${MCP_INVOKER_SA} \
  --role="roles/iam.serviceAccountTokenCreator" \
  --member="${RE_AGENT_IDENTITY}"

# grant service account openid token creator role to agent principal identity
gcloud iam service-accounts add-iam-policy-binding ${MCP_INVOKER_SA} \
  --role="roles/iam.serviceAccountOpenIdTokenCreator" \
  --member="${RE_AGENT_IDENTITY}"

Verify IAM permissions

# show iam policy for custom service account
gcloud iam service-accounts get-iam-policy ${MCP_INVOKER_SA} \
  --flatten="bindings[].members" \
  --format="table(bindings.members.sub('^.*locations/', '.../locations/'):label=PRINCIPAL_IDENTITY, bindings.role:label=ROLE)"

Grant IAM permissions for runtime

Granting IAM permissions for runtime enables the agent to access to AI platform services and other core Google APIs and services using its SPIFFE-based principal agent identity. These policies are enforced using IAM at the resource level.

Create the role bindings needed for the agent identity.

Bind policies for agent access

# grant ai platform user role
gcloud projects add-iam-policy-binding ${PROJ_ID} \
  --member="${RE_AGENT_IDENTITY}" \
  --role="roles/aiplatform.user"

# grant agent default access role
gcloud projects add-iam-policy-binding ${PROJ_ID} \
  --member="${RE_AGENT_IDENTITY}" \
  --role="roles/aiplatform.agentDefaultAccess"

# grant agent registry viewer role
gcloud projects add-iam-policy-binding ${PROJ_ID} \
  --member="${RE_AGENT_IDENTITY}" \
  --role="roles/agentregistry.viewer"
# grant cloud logging writer role
gcloud projects add-iam-policy-binding ${PROJ_ID} \
  --member="${RE_AGENT_IDENTITY}" \
  --role="roles/logging.logWriter"

# grant cloud monitoring writer role
gcloud projects add-iam-policy-binding ${PROJ_ID} \
  --member="${RE_AGENT_IDENTITY}" \
  --role="roles/monitoring.metricWriter"

# grant browser role for resource manager lookup
gcloud projects add-iam-policy-binding ${PROJ_ID} \
  --member="${RE_AGENT_IDENTITY}" \
  --role="roles/browser"

Verify IAM permissions

# show iam policy on project for agent identity
gcloud projects get-iam-policy ${PROJ_ID} \
  --flatten="bindings[].members" \
  --filter="bindings.members:${RE_AGENT_IDENTITY}" \
  --format="table(bindings.members.sub('^.*locations/', '.../locations/'):label=PRINCIPAL_IDENTITY, bindings.role:label=ROLE)"

This concludes the ADK agent portion... next on to the Policies section.

11. Policies

Agent governance authorization policies permit traffic to be routed from an AI agent through Agent Gateway and on to the ultimate resource destination (eg, a private MCP server endpoint for data retrieval). Once traffic reaches the destination, regular service-level or application-level permissions authorize data access.

Granting IAM roles for Agent Gateway egress enables access policies that allow the agent principal identity access to registered destination resources through Agent Gateway. Policies are enforced at the Agent Gateway level and validated using Identity-Aware Proxy (IAP) IAM policies.

Grant IAM roles for Agent Gateway egress

Agent Gateway checks incoming requests to validate the calling agent identity has the iap.webServiceVersions.egressViaIAP permission (granted by the role roles/iap.egressor) on the target resource.

Granting roles/iap.egressor to the agent identity or Agent Runtime principal set on the selected destination resource permits this egress traffic. In this codelab, the role is applied to the principal agent which grants permission to the specific Agent Runtime agent identity.

The IAM policies are bound to the destination resources as defined in the Agent Registry data model. The iap_web/agentRegistry resource represents a "registry-wide" level in the IAM hierarchy. Where agentRegistry serves as the parent to the individual child resources for agents, mcpServers, and endpoints. In this codelab, the IAM policy is bound to the mcpServer and endpoint levels which applies the permission to the specific child resources.

Configure agent to MCP server policy

Create policy for agent to mcp server

# fetch active etag on policy for mcp server
export IAP_ETAG=$(gcloud beta iap web get-iam-policy \
  --resource-type=agent-registry --region=${REGION} \
  --mcp-server=${MCP_REGISTRY_ID} --format="value(etag)")
echo ${IAP_ETAG}

NOTE: etag: should return default ACAB since no policies have been defined yet.

# create policy config file
cat > cfg/iap-policy-agent-to-mcp.json << EOF
{
  "bindings": [
    {
      "role": "roles/iap.egressor",
      "members": [
        "${RE_AGENT_IDENTITY}"
      ]
    }
  ],
  "etag": "${IAP_ETAG}"
}
EOF

Bind policy for agent to mcp server

# import policy file
gcloud beta iap web set-iam-policy cfg/iap-policy-agent-to-mcp.json \
  --resource-type=agent-registry \
  --mcp-server=${MCP_REGISTRY_ID} \
  --region=${REGION}

Verify IAM permissions

# show iap iam policy on mcp server for agent
gcloud beta iap web get-iam-policy \
  --region=${REGION} \
  --resource-type=agent-registry \
  --mcp-server=${MCP_REGISTRY_ID}

This concludes the policies portion... next on to the Audit section.

12. Audit

The Agent Gateway authorization extension is operating in DRY_RUN mode. Outbound requests are observed and logged but not blocked. Analyzing the logs before switching to ENFORCED mode will confirm which policies need to be configured to allow the intended traffic.

Test agent queries

Open a browser window to the Google Cloud console UI and navigate to:

  • Agent Platform → Agents → Deployments → agent-weather
  • Select the Playground tab

Or echo this terminal command and click the link to jump to the agent playground:

# playground
echo "https://console.cloud.google.com/agent-platform/runtimes/locations/${REGION}/agent-engines/${RE_ENGINE_ID}/playground?project=${PROJ_ID}"

Submit some test queries....

  • What is the weather like in London?
  • How warm is it in New York?

Verify the queries return valid responses.

Analyze audit logs

View logs and inspect the dry-run policy evaluations.

# show gateway logs for endpoints and authz status (duplicates removed)
gcloud logging read \
  "resource.type=\"networkservices.googleapis.com/Gateway\" \
  AND httpRequest.requestUrl:*" \
  --project=${PROJ_ID} \
  --limit=50 \
  --freshness=24h \
  --format="table( \
    timestamp.date(tz=LOCAL):label=TIMESTAMP, \
    httpRequest.status:label=STATUS, \
    jsonPayload.authzPolicyInfo.result:label=AUTHZ, \
    httpRequest.requestUrl.sub('(https?://[^/:]+).*', '\1'):label=ENDPOINT \
  )" | awk 'NR==1 {print; next} ! seen[$2,$3,$4]++'
TIMESTAMP            STATUS  AUTHZ    ENDPOINT
YYYY-MM-DDTHH:MM:SS  200     ALLOWED  https://cloudresourcemanager.googleapis.com
YYYY-MM-DDTHH:MM:SS  200     ALLOWED  https://${REGION}-aiplatform.googleapis.com
YYYY-MM-DDTHH:MM:SS  200     ALLOWED  https://iamcredentials.googleapis.com
YYYY-MM-DDTHH:MM:SS  200     ALLOWED  https://mcp-weather-${PROJ_NO}.${REGION}.run.app
YYYY-MM-DDTHH:MM:SS  200     ALLOWED  https://telemetry.googleapis.com
YYYY-MM-DDTHH:MM:SS  202     ALLOWED  https://mcp-weather-${PROJ_NO}.${REGION}.run.app

Configure agent to endpoints policy

Because the core Google APIs for agent-weather were registered under a collective endpoint service in the registry (gapi.core.services), just a single policy is needed to grant agent egress permissions for endpoint access.

Create policy for agent to endpoint

# set var for endpoint display name
export ENDPOINT_DISPLAY_NAME="gapi.core.services"
echo ${ENDPOINT_DISPLAY_NAME}
# fetch endpoint registry id
export ENDPOINT_REGISTRY_ID=$(gcloud agent-registry services list \
  --location=${REGION} \
  --filter="displayName=${ENDPOINT_DISPLAY_NAME}" \
  --format="value(registryResource.basename())")
echo ${ENDPOINT_REGISTRY_ID}
# create policy config file
cat > cfg/iap-policy-agent-to-ep.json << EOF
{
  "bindings": [
    {
      "role": "roles/iap.egressor",
      "members": [
        "${RE_AGENT_IDENTITY}"
      ]
    }
  ]
}
EOF

Bind policy for agent to endpoint

# import policy file (bind iam policy)
gcloud -q beta iap web set-iam-policy cfg/iap-policy-agent-to-ep.json \
  --resource-type=agent-registry \
  --endpoint=${ENDPOINT_REGISTRY_ID} \
  --region=${REGION}

Verify IAM permissions

# show iap iam policy on endpoint for agent
gcloud beta iap web get-iam-policy \
  --region=${REGION} \
  --resource-type=agent-registry \
  --endpoint=${ENDPOINT_REGISTRY_ID}

This concludes the audit portion... next on to the Enforce section.

13. Enforce

The agent was able to make successful requests through the gateway in DRY_RUN mode. The access logs and endpoints were analyzed to create a set of governance policies allowing the necessary egress traffic from the agent. Now transition the Agent Gateway IAP authorization extension to ENFORCE mode by updating the authorization policy.

Update authorization extension

Create authz extension

# create authz extension config file (enforced mode)
cat > cfg/${AGW_NAME}-svc-ext-authz-iap-enforced.yaml << EOF
name: ${AGW_NAME}-svc-ext-authz-iap-enforced
service: iap.googleapis.com
failOpen: false
timeout: 1s
metadata:
  iapPolicyVersion: "V1"
EOF

Import authz extension

# import authz extension file (create extension)
gcloud service-extensions authz-extensions import ${AGW_NAME}-svc-ext-authz-iap-enforced \
  --source=cfg/${AGW_NAME}-svc-ext-authz-iap-enforced.yaml \
  --location=${REGION}

Verify authz extension

# list authz extensions (imported configs)
gcloud service-extensions authz-extensions list --location=${REGION}

Update authorization policy

Create authz policy

# re-write authz policy config file (pointing to enforced authz extension)
cat > cfg/${AGW_NAME}-authz-policy-profile-iap.yaml << EOF
name: ${AGW_NAME}-authz-policy-profile-iap
target:
  resources:
    - "projects/${PROJ_ID}/locations/${REGION}/agentGateways/${AGW_NAME}"
policyProfile: REQUEST_AUTHZ
action: CUSTOM
customProvider:
  authzExtension:
    resources:
      - "projects/${PROJ_ID}/locations/${REGION}/authzExtensions/${AGW_NAME}-svc-ext-authz-iap-enforced"
EOF

Bind authz policy

# import authz policy config file (enable new authz policy)
gcloud beta network-security authz-policies import ${AGW_NAME}-authz-policy-profile-iap \
  --source=cfg/${AGW_NAME}-authz-policy-profile-iap.yaml \
  --location=${REGION}

Verify authz policy

# show authz policy details (verify enforced mode)
gcloud beta network-security authz-policies describe ${AGW_NAME}-authz-policy-profile-iap \
  --location=${REGION}

Verify enforcement

Return to the agent Playground in the console UI.

  • Click the "+ New Session" button to create a new chat session

Submit an additional test query...

  • What is the current temperature in Los Angeles?

Observe the agent requests to the Gemini foundation model (${REGION}-aiplatform.googleapis.com/...) and MCP server endpoint (mcp-weather-${PROJ_NO}.${REGION}.run.app/mcp) are passed with HTTP 200 OK status codes.

Analyze gateway logs

# show gateway logs for http requests
gcloud logging read \
  "resource.type=\"networkservices.googleapis.com/Gateway\" \
  AND httpRequest.requestUrl:*" \
  --project=${PROJ_ID} \
  --limit=10 \
  --format="table(
    timestamp.date(tz=LOCAL):label=TIMESTAMP,
    httpRequest.requestMethod:label=METHOD,
    httpRequest.status:label=STATUS,
    jsonPayload.authzPolicyInfo.result:label=IAP_AUTHZ,
    jsonPayload.agentGatewayInfo.mcpInfo.method:label=MCP_METHOD,
    httpRequest.requestUrl:label=URL)"

Check there are no requests being blocked. Look for any denied egress attempts with HTTP 403 Forbidden status codes in the gateway audit logs.

# show gateway logs for authz denies
gcloud logging read \
  "resource.type=\"networkservices.googleapis.com/Gateway\" \
  AND (jsonPayload.authzPolicyInfo.result=\"DENIED\" OR httpRequest.status=403)" \
  --project=${PROJ_ID} \
  --limit=10 \
  --format="table(
    timestamp.date(tz=LOCAL):label=TIMESTAMP,
    httpRequest.requestMethod:label=METHOD,
    httpRequest.status:label=STATUS,
    jsonPayload.enforcedGatewaySecurityPolicy.serverNameIndication:label=SNI,
    jsonPayload.authzPolicyInfo.result:label=AUTHZ_RESULT,
    jsonPayload.authzPolicyInfo.policies[0].name.basename():label=DENYING_POLICY
  )"

View the Agent Gateway MCP logs and note how the MCP application payload (JSON-RPC) is inspected to expose the protocol metadata, including which which tools are being executed.

# show gateway logs for mcp requests with method and tool name
gcloud logging read \
  "resource.type=\"networkservices.googleapis.com/Gateway\" \
  AND jsonPayload.agentGatewayInfo.mcpInfo.method:*" \
  --project=${PROJ_ID} \
  --limit=10 \
  --format="table(
    timestamp.date(tz=LOCAL):label=TIMESTAMP,
    httpRequest.status:label=STATUS,
    jsonPayload.authzPolicyInfo.result:label=IAP_AUTHZ,
    jsonPayload.agentGatewayInfo.mcpInfo.method:label=MCP_METHOD,
    jsonPayload.agentGatewayInfo.mcpInfo.parameter:label=TOOL_NAME,
    httpRequest.requestUrl:label=URL
  )"

Analyze firewall logs

View logs for network firewall policy rule priority 1001.

# show firewall logs for rule 1001
gcloud logging read \
  "jsonPayload.rule_details.priority=1001" \
  --project=${PROJ_ID} \
  --limit=10 \
  --format="table(
    timestamp.date(tz=LOCAL):label=TIMESTAMP,
    jsonPayload.disposition:label=ACTION,
    jsonPayload.connection.src_ip:label=SRC_IP,
    jsonPayload.connection.dest_ip:label=DEST_IP,
    jsonPayload.connection.dest_port:label=DEST_PORT,
    jsonPayload.vpc.subnetwork_name:label=SUBNET
  )"

Note the network-level view of the different traffic paths:

  • Agent Gateway communicating with internal VPC network resources using subnet-${REGION}-agw
  • The Cloud Run hosted MCP server making calls to external weather APIs using subnet-${REGION}-crun
TIMESTAMP            ACTION   SRC_IP        DEST_IP        DEST_PORT  SUBNET
YYYY-MM-DDTHH:MM:SS  ALLOWED  10.10.10.16   188.40.99.226  443        subnet-${REGION}-crun
YYYY-MM-DDTHH:MM:SS  ALLOWED  10.10.10.16   202.61.206.6   443        subnet-${REGION}-crun
YYYY-MM-DDTHH:MM:SS  ALLOWED  10.10.10.16   188.40.99.226  443        subnet-${REGION}-crun
YYYY-MM-DDTHH:MM:SS  ALLOWED  10.10.10.16   202.61.206.6   443        subnet-${REGION}-crun
YYYY-MM-DDTHH:MM:SS  ALLOWED  10.10.10.16   202.61.206.6   443        subnet-${REGION}-crun
YYYY-MM-DDTHH:MM:SS  ALLOWED  10.10.10.16   202.61.206.6   443        subnet-${REGION}-crun
YYYY-MM-DDTHH:MM:SS  ALLOWED  192.168.10.2  240.0.0.10     443        subnet-${REGION}-agw
YYYY-MM-DDTHH:MM:SS  ALLOWED  192.168.10.2  240.0.0.10     443        subnet-${REGION}-agw
YYYY-MM-DDTHH:MM:SS  ALLOWED  192.168.10.2  240.0.0.10     443        subnet-${REGION}-agw

Analyze DNS logs

View DNS query logs for requests resolved by the private DNS zone (priv-zone-run).

gcloud logging read \
  "resource.type=\"dns_query\" AND resource.labels.target_name=\"priv-zone-run\"" \
  --project=${PROJ_ID} \
  --limit=10 \
  --format="table(
    timestamp.date(tz=LOCAL):label=TIMESTAMP,
    jsonPayload.queryName:label=QUERY_NAME,
    jsonPayload.queryType:label=TYPE,
    jsonPayload.rdata:label=RESPONSE_RDATA,
    jsonPayload.sourceNetwork:label=NETWORK,
    resource.labels.source_type:label=SRC_TYPE
  )"

Note DNS lookups for the Cloud Run service (mcp-weather...run.app) originate from the VPC network using DNS peering and are successfully resolved to the internal PSC endpoint for Google APIs (240.0.0.10).

TIMESTAMP            QUERY_NAME                                     TYPE  RESPONSE_RDATA                                                     NETWORK   SRC_TYPE
YYYY-MM-DDTHH:MM:SS  mcp-weather-${PROJ_NO}.${REGION}.run.app.  A     mcp-weather-${PROJ_NO}.${REGION}.run.app. 300     IN      a       240.0.0.10  vnet-${SLUG}  peering-zone
YYYY-MM-DDTHH:MM:SS  mcp-weather-${PROJ_NO}.${REGION}.run.app.  A     mcp-weather-${PROJ_NO}.${REGION}.run.app. 300     IN      a       240.0.0.10  vnet-${SLUG}  peering-zone
YYYY-MM-DDTHH:MM:SS  mcp-weather-${PROJ_NO}.${REGION}.run.app.  A     mcp-weather-${PROJ_NO}.${REGION}.run.app. 300     IN      a       240.0.0.10  vnet-${SLUG}  peering-zone
YYYY-MM-DDTHH:MM:SS  mcp-weather-${PROJ_NO}.${REGION}.run.app.  A     mcp-weather-${PROJ_NO}.${REGION}.run.app. 300     IN      a       240.0.0.10  vnet-${SLUG}  peering-zone

This concludes the enforce portion... next on to the Cleanup section.

14. Cleanup

# delete agent runtime
curl -s -X DELETE "https://${REGION}-aiplatform.googleapis.com/v1/projects/${PROJ_ID}/locations/${REGION}/reasoningEngines/${RE_ENGINE_ID}?force=true" \
  -H "Authorization: Bearer $(gcloud auth application-default print-access-token)"

# next
# delete agent resources
gcloud -q storage rm --recursive gs://${STAGING_BUCKET}

for ROLE in \
  "roles/aiplatform.user" \
  "roles/aiplatform.agentDefaultAccess" \
  "roles/agentregistry.viewer" \
  "roles/logging.logWriter" \
  "roles/monitoring.metricWriter" \
  "roles/browser"; do
  gcloud -q projects remove-iam-policy-binding ${PROJ_ID} \
    --member="${RE_AGENT_IDENTITY}" \
    --role="${ROLE}"
done

# next
# delete mcp (cloud run) resources
gcloud -q projects remove-iam-policy-binding ${PROJ_ID} \
  --member="serviceAccount:${PROJ_NO}-compute@developer.gserviceaccount.com" \
  --role="roles/run.builder"

gcloud -q run services delete ${MCP_NAME} --region=${REGION}

gcloud -q artifacts repositories delete cloud-run-source-deploy --location=${REGION}

gcloud -q iam service-accounts delete sa-${MCP_NAME}-invoker@${PROJ_ID}.iam.gserviceaccount.com

gcloud -q agent-registry services delete ${MCP_NAME} --location=${REGION}

# next
# delete gateway resources (policy binding must be deleted synchronously first)
gcloud -q beta network-security authz-policies delete ${AGW_NAME}-authz-policy-profile-iap --location=${REGION}

gcloud -q beta service-extensions authz-extensions delete ${AGW_NAME}-svc-ext-authz-iap-dryrun --location=${REGION} --async

gcloud -q beta service-extensions authz-extensions delete ${AGW_NAME}-svc-ext-authz-iap-enforced --location=${REGION} --async

gcloud -q network-services agent-gateways delete ${AGW_NAME} --location=${REGION} --async

# next
# remove policies
export IAP_ETAG=$(gcloud beta iap web get-iam-policy --resource-type=agent-registry --endpoint=${ENDPOINT_REGISTRY_ID} --region=${REGION} --format="value(etag)")
cat > cfg/iap-policy-empty.json << EOF
{
  "bindings": [],
  "etag": "${IAP_ETAG}"
}
EOF

gcloud -q beta iap web set-iam-policy cfg/iap-policy-empty.json \
  --resource-type=agent-registry \
  --endpoint=${ENDPOINT_REGISTRY_ID} \
  --region=${REGION}

# next
# delete dns resources
gcloud -q dns record-sets delete *.run.app --type=A --zone=priv-zone-run

gcloud -q dns managed-zones delete priv-zone-run

gcloud -q dns policies update dns-policy-${SLUG} --networks=""

gcloud -q dns policies delete dns-policy-${SLUG}

# next
# delete router (includes nat) and psc resources
gcloud -q compute routers delete cr-nat-${SLUG}-${REGION} --region=${REGION}

gcloud -q compute forwarding-rules delete psc2gapis --global

gcloud -q compute addresses delete ip-psc2gapis --global

# next
# delete firewall resources
gcloud -q compute network-firewall-policies associations delete --name=fw-policy-bind-${SLUG} --firewall-policy=fw-policy-${SLUG} --global-firewall-policy

gcloud -q compute network-firewall-policies rules delete 1001 --firewall-policy=fw-policy-${SLUG} --global-firewall-policy

gcloud -q compute network-firewall-policies delete fw-policy-${SLUG} --global

# next
# delete network resources
gcloud -q compute networks subnets delete subnet-${REGION}-agw --region=${REGION}

gcloud -q compute networks subnets delete subnet-${REGION}-crun --region=${REGION}

gcloud -q compute network-attachments delete psc-na-${REGION}-agw --region=${REGION}

gcloud -q compute networks delete vnet-${SLUG}

# next
# clean up local working directories and generated configs
rm -rf cfg agent-weather mcp-weather

# end

This concludes the cleanup portion... next on to the Conclusion!

15. Conclusion

Congratulations! You have successfully deployed Agent Gateway and governed outbound AI agent traffic!

cosmopup

Cosmopup thinks Codelabs are the cat's pajamas!

What is next?

Feel free to offer any comments, questions, or corrections by using this feedback form.

Thank you!