Gemini Enterprise と Agent Gateway の全トラフィック VPC 下り(外向き)と Cloud NAT 静的 IP から外部 MCP サーバー

1. はじめに

この Codelab では、agent-to-anywhere(外向き)モードで Agent Gateway を使用して、Gemini Enterprise の静的 IP 外向き接続について説明します。接続テンプレート構成を使用して すべてのアウトバウンド トラフィックを Agent Gateway 経由でルーティングし、厳格な送信元 IP 許可リストと OAuth 2.0 認証で保護されたカスタム Model Context Protocol(MCP)サーバーを呼び出すように Gemini Enterprise アプリを構成します。Private Service Connect(PSC)ネットワーク アタッチメントは、Agent Gateway を VPC ネットワークに接続します。また、Cloud NAT は、インターネット下り(外向き)トラフィックに安定した外部 IP アドレスを提供します。

デフォルトでは、Gemini Enterprise からパブリック インターネット エンドポイントへのアウトバウンド接続は、単一の顧客所有 IP 許可リストルールに固定できない動的な共有 Google IP アドレスプールから発信されます。Agent Gateway を ALL_TRAFFIC モードの AgentConnectivityTemplate とペア設定すると、すべての下り(外向き)エージェント トラフィックが VPC ネットワークにルーティングされます。ここで、Cloud NAT はインターネット バウンド接続を制御下の予約済み静的外部 IPv4 アドレスに変換し、Agent Registry と Identity-Aware Proxy(IAP)を介して一元化された MCP ツールのガバナンスを維持します。

構築内容

  • Agent Connectivity テンプレート(vpcEgress: ALL_TRAFFIC)と、Agent Registry エンドポイント検証を使用して外向き(agent-to-anywhere)モードで動作する Agent Gateway
  • 予約済みの静的外部 IPv4 アドレスにバインドされた VPC ネットワーク、PSC ネットワーク アタッチメント、Cloud Router、Cloud NAT ゲートウェイ
  • Caddy TLS によってフロントエンド処理され、ファイアウォール ルールによって Cloud NAT 静的 IP に厳密に制限され、https://oauth2.googleapis.com/tokeninfo に対してユーザー委任の Google OAuth 2.0 署名なしトークンを検証する外部ストリーミング可能な HTTP MCP リソース サーバー(mcp-sprockets)
  • Google Auth Platform OAuth 2.0 同意画面とウェブ アプリケーション OAuth 2.0 クライアント認証情報(accounts.google.com と oauth2.googleapis.com/token)
  • Agent Registry MCP サーバー カタログ エントリとツール仕様(toolspec.json)
  • Agent Gateway の Identity-Aware Proxy(IAP)認可拡張機能と Gateway 認可ポリシー
  • Agent Gateway にバインドされ、Agent Registry からインポートされた OAuth 2.0 カスタム MCP サーバー データストアに接続された Gemini Enterprise アプリ

figure1

図 1. Codelab アーキテクチャ

学習内容

  • vpcEgress: ALL_TRAFFIC を使用して AgentConnectivityTemplate を構成し、すべてのアウトバウンド Agent Gateway トラフィックを VPC ネットワークにルーティングする方法
  • 静的外部 IPv4 アドレスを予約して PSC ネットワーク アタッチメント サブネットの Cloud NAT にバインドする方法
  • Google OAuth 2.0 署名なしトークン(oauth2.googleapis.com/tokeninfo)を検証し、Cloud NAT 静的 IP(${NAT_EXT_IP}/32)に厳密に制限する外部ストリーミング HTTP MCP リソース サーバーをデプロイする方法
  • Google Identity OAuth 2.0 クライアント認証情報を構成し、MCP サーバーとそのツールスキーマを Agent Registry に登録する方法
  • 登録済みの OAuth 2.0 MCP サーバーを Gemini Enterprise に接続し、ユーザー委任ツール呼び出しを Agent Gateway 経由でルーティングする方法
  • CLI コマンドと Cloud Logging を使用して、静的 IP 変換、エンドユーザーの OAuth 2.0 ID 検証(authenticated_user)、ゲートウェイ ポリシーの適用を検証する方法

必要なもの

  • 課金を有効にした Google Cloud プロジェクト
  • 有効な Gemini Enterprise ライセンスまたは30 日間のトライアル
  • ネットワーキング サービス、組織のポリシー、Gemini Enterprise、Agent Platform リソースをプロビジョニングするための IAM 権限
  • Google Cloud CLI(gcloud)、curl、jq がインストールされた POSIX 互換シェル(bash または zsh)

これで導入部分は終わりです。次はコンセプトのセクションに進みます。

2. コンセプト

デプロイ シーケンス

この Codelab では、まずネットワーキングとガバナンスのインフラストラクチャをデプロイして、MCP ツールを Gemini Enterprise に登録して接続する前に、静的 IP 下り(外向き)パスとゲートウェイ制御が動作するようにします。

  1. ネットワーク インフラストラクチャ: 静的外部 IPv4 アドレスを予約し、下り(外向き)VPC、PSC アタッチメント サブネット、PSC ネットワーク アタッチメント、Cloud Router、Cloud NAT ゲートウェイ、外部 MCP ネットワーク ファイアウォール ルールをプロビジョニングします。
  2. エージェント接続テンプレートと Agent Gateway: Agent Registry 統合(registries)を使用して、外向きモード(AGENT_TO_ANYWHERE)で AgentConnectivityTemplate(vpcEgress: ALL_TRAFFIC)とリージョン AgentGateway をデプロイします。
  3. 認可ポリシー: IAP 認可拡張機能(AuthzExtension)、Gateway 認可ポリシー(AuthzPolicy)、IAM v3 統合アクセス ポリシー(AccessPolicy と PolicyBinding)を構成します。
  4. MCP サーバーをデプロイして登録する: Caddy TLS の背後に外部 mcp-sprockets OAuth 2.0 リソース サーバーをデプロイし、その Google OAuth 2.0 tokeninfo の適用を確認し、その上り(内向き)ファイアウォールを Cloud NAT 静的 IP(${NAT_EXT_IP}/32)に厳密にロックダウンし、Agent Registry にサービスとツールの仕様(get_sprockets と get_sprocket_by_id)を登録します。
  5. Gemini Enterprise アプリと OAuth 2.0 クライアント: Google ID プロバイダ、トライアル ライセンス、OAuth 2.0 同意画面とウェブ アプリケーション クライアント認証情報(OAUTH_CLIENT_ID と OAUTH_CLIENT_SECRET)を構成し、Gemini Enterprise アプリ(Engine)を作成して、アウトバウンド エグレスを Agent Gateway(agentGatewaySetting)にバインドします。
  6. カスタム MCP データコネクタをインポートする: 登録済みの MCP サーバーのバックエンド データストアを Gemini Enterprise アプリにリンクするために、auth_type: "OAUTH" で構成された REGISTRY_MCP データコネクタ(:setUpDataConnector)を作成して有効にします。
  7. 検証: 1 回限りのユーザー OAuth 2.0 同意フローを完了し、Gemini Enterprise アシスタントを介して MCP ツールを呼び出し(egress_source_ip と authenticated_user の両方を確認)、Cloud Logging でポリシーの適用と静的 IP NAT 変換を確認します。

中央 IdP + OAuth で保護された MCP リソース サーバー アーキテクチャ

エンタープライズ デプロイでは、ID 管理とツール実行は、一元化された ID プロバイダ(認可サーバー)と保護された MCP リソース サーバー間のロールの標準 OAuth 2.0 分離に従います。

OAuth 2.0 ロール

この Codelab のコンポーネント

責任範囲

リソース オーナー

エンドユーザー(${USER_IDENTITY})

Gemini Enterprise ウェブアプリにログインし、1 回限りの 3-legged OAuth 2.0 認証フローで同意を付与します。

OAuth 2.0 クライアント

Gemini Enterprise custom_mcp データコネクタ

OAUTH_CLIENT_ID と OAUTH_CLIENT_SECRET を保持し、oauth2.googleapis.com/token で認証コードを交換し、ユーザーの OAuth 2.0 トークンを保管し、すべての送信 MCP ツール呼び出しで Authorization: Bearer [access_token] を提示 します。

認可サーバー

Google Identity OAuth 2.0(accounts.google.com と oauth2.googleapis.com/token)

ブラウザの同意リダイレクト中にユーザーを認証し、OAuth 2.0 アクセス トークンと更新トークンを Gemini Enterprise に発行します。

リソース サーバー

外部 mcp-sprocketsサーバー(https://mcp.[MCP_EXT_IP].sslip.io/mcp)

VPC ファイアウォール(${NAT_EXT_IP}/32)を使用してレイヤ 3 ネットワーク ID を適用し、https://oauth2.googleapis.com/tokeninfo に対して Authorization: Bearer [access_token] ヘッダーを検証してレイヤ 7 ユーザー ID を適用します。

ブラウザの同意のリダイレクト(accounts.google.com)とバックチャネル トークンの交換(oauth2.googleapis.com/token)は Google 管理の OAuth 2.0 エンドポイントに対して行われるため、外部 MCP サーバー ファイアウォールは、Cloud NAT 経由で Agent Gateway からランタイム MCP JSON-RPC リクエスト(POST /mcp)のみを受信します。これにより、上り(内向き)ファイアウォールを ${NAT_EXT_IP}/32 に厳密にロックダウンできます。

ディスカバリー エンジン リソースモデル

Gemini Enterprise app(Discovery Engine API の Engine リソースとして表されます)は、エンドユーザー向けの中央オーケストレーション レイヤと会話インターフェースです。ユーザーのチャット セッションを管理し、生成モデルを企業データに基づいてグラウンディングし、動的ツールの実行を調整します。

Gemini Enterprise アプリは、データストアを介してデータとシステムを操作します。

  • ナレッジ データストア: 検索拡張生成(RAG)用に静的コンテンツ(Cloud Storage、Google ドライブ、BigQuery など)を取り込んでインデックス登録します。
  • データコネクタ(アクション プロバイダ): 動的なサードパーティ製 API またはカスタム API に接続します。カスタム MCP サーバー データストアは、Model Context Protocol(MCP)で定義されたツールを公開し、モデルが会話中に外部関数を動的に呼び出すことを可能にします。

Gemini Enterprise の下り(外向き)

Gemini Enterprise は、Engine の agentGatewaySetting と DataConnector の use_agent_gateway_egress: true の両方が構成されている場合、カスタム MCP サーバー ツール リクエストを Agent Gateway に転送します。

figure2

図 2. Gemini Enterprise の下り(外向き)アーキテクチャ

Gemini Enterprise app は、4 つの主要分野でツール ルーティングを整理します。

  1. ウィジェット(default_search_widget_config):
    • ウェブ クライアント インターフェースを提供します。ウィジェットはユーザーからのプロンプトを受け取り、ツールで認証が必要な場合は 1 回限りの OAuth 2.0 ユーザーの同意リダイレクトをトリガーし、基盤となるエンジンとのチャット セッションを開始します。
  2. コア アシスタント(assistants/default_assistant/agents/default/core_assistant):
    • エンジン内のルート会話型推論エージェント。ユーザー クエリを評価する際、Core Assistant は利用可能なツールを検査し、合成された Agent Gateway サブエージェントに実行を委任します。
  3. データストアとデータコネクタ:
    • DataStore: :setUpDataConnector の実行時に専用の Collection 内でプロビジョニングされ、インポートされた Agent Registry ツール スキーマ(get_sprockets、get_sprocket_by_id)、引数の型、エージェントの手順を Gemini Enterprise Engine にリンク(dataStoreIds)します。
    • DataConnector: リモート MCP サーバー(instance_uri)への REGISTRY_MCP アクション接続(createBapConnection: true)を管理し、Agent Registry MCP サーバー リソース(registry_mcp_server_name)を解決し、ユーザーの Vault に保存された OAuth 2.0 署名なしトークン(Authorization: Bearer [access_token])を関連付け、Agent Gateway 下り(外向き)(use_agent_gateway_egress: true)を有効にします。
  4. Agent Identity、Agent Registry、Agent Gateway:
    • データコネクタがアウトバウンド ツール呼び出しをディスパッチすると、agentGatewaySetting で指定されたゲートウェイにトラフィックが転送されます。コア アシスタントは、ワークロード ID principal://agents.global.org-.../agents/default/core_assistant をアサートする SPIFFE ID トークンを生成します。
    • Agent Gateway は registries フィールドを使用して Agent Registry と統合し、宛先エンドポイントと登録済みツールスキーマを動的に解決します。また、IAP を使用して認可ポリシーを評価し、許可されたリクエスト(ユーザー Authorization: Bearer ヘッダーを保持)を AgentConnectivityTemplate を使用して VPC ネットワークに転送します。

ゲートウェイ VPC 接続と静的 IP NAT

Agent Gateway は、ゲートウェイの agentConnectivityTemplate によって参照される AgentConnectivityTemplate リソースを使用して VPC ネットワーク接続をサポートします。テンプレート egressNetworkConfig は、PSC ネットワーク アタッチメントを介して VPC にトラフィックがどのように入るかを定義します。

  • PRIVATE_RANGES_ONLY(デフォルト): RFC 1918、RFC 6598、クラス E のプライベート IP 範囲のみを PSC ネットワーク アタッチメント経由でルーティングします。インターネット宛てのトラフィックは VPC をバイパスし、Google のデフォルトのインターネット下り(外向き)を介して直接送信されます。
  • ALL_TRAFFIC(この Codelab で使用): Agent Gateway からのすべてのアウトバウンド トラフィック(公共のインターネットや外部パートナーの MCP エンドポイントなど)を、PSC ネットワーク アタッチメント(10.10.10.2)を介して VPC サブネットに転送します。

ALL_TRAFFIC モードで動作する場合、PSC アタッチメント サブネットには次の 2 つの構成が必要です。

  • プライベート Google アクセス(--enable-private-ip-google-access): Google Cloud APIs(*.googleapis.com)への内部呼び出しが Google ネットワーク内で引き続きプライベートにルーティングされるようにします。
  • Cloud Router と Cloud NAT(MANUAL_ONLY): PSC アタッチメント サブネットに入るインターネット バウンド トラフィックは、Cloud NAT を介してデフォルト ルート(0.0.0.0/0)に従います。Cloud NAT は、予約済みの静的外部 IPv4 アドレス(${NAT_EXT_IP})に対して送信元ネットワーク アドレス変換(SNAT)を実行します。したがって、外部ファイアウォールはインバウンド アクセスを ${NAT_EXT_IP}/32 に厳密に制限できます。

制限事項と要件

  • StreamableHTTP のみ: 以前のサーバー送信イベント(SSE)転送はサポートされていません。MCP サーバーは StreamableHTTP を使用する必要があります。
  • 組織のポリシーのオーバーライド: データ コネクタを登録する前に、カスタム MCP データストアの組織のポリシーを明示的にオーバーライドする必要があります。
  • リージョン整合性: Gemini Enterprise は、アプリのロケーション、Agent Gateway、Agent Registry 間のリージョン整合性を適用します。

Gemini Enterprise app のロケーション

Agent Gateway と NAT のロケーション

Agent Registry のロケーション

global

us-central1

us-central1、us、globalのいずれか

us

us-central1

us-central1 または us

eu

europe-west1

europe-west1 または eu

これでコンセプトの説明は終わりです。次は設定セクションに進みます。

3. セットアップ

必要な IAM のロール

この Codelab を完了するには、次のロールが必要です。

ドメイン

必要な IAM のロール

プロジェクトと IAM

roles/orgpolicy.policyAdmin
roles/resourcemanager.projectIamAdmin
roles/iam.accessPolicyAdmin
roles/serviceusage.serviceUsageAdmin
roles/iam.serviceAccountUser

ネットワーキングとゲートウェイ

roles/networkservices.admin
roles/networksecurity.admin
roles/serviceextensions.admin
roles/iap.admin
roles/compute.networkAdmin
roles/compute.securityAdmin

Gemini Enterprise と Registry

roles/discoveryengine.admin
roles/agentregistry.admin

ワークロード

roles/compute.instanceAdmin.v1(または roles/compute.admin)

オブザーバビリティ

roles/logging.viewer

または、roles/owner などの広範な基本ロールを roles/orgpolicy.policyAdmin と組み合わせて使用します(roles/owner 単独では組織のポリシーを変更できないため)。

プロジェクトにアクセスする

この Codelab では、1 つの Google Cloud プロジェクトを使用します。構成手順では、gcloud CLI と Linux シェルコマンドを使用します。

まず、Google Cloud プロジェクトのコマンドラインにアクセスします。

プロジェクト ID を設定する

# set target project id
gcloud config set project SET_YOUR_PROJECT_ID_HERE

セッションを認証する

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

シェル環境変数を設定する

# set custom var for slug (eg, "foo") and region preference
export SLUG="foo"
export REGION="us-central1"
export ZONE="us-central1-a"

echo ${SLUG}
echo ${REGION}
echo ${ZONE}
# 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 for agent platform (automatic)
export AGW_NAME="agw-${SLUG}-${REGION}-ata"
export AGW_URI="projects/${PROJ_ID}/locations/${REGION}/agentGateways/${AGW_NAME}"
export MCP_NAME="mcp-sprockets"

echo ${AGW_NAME}
echo ${AGW_URI}
echo ${MCP_NAME}
# create resource vars for gemini enterprise (automatic)
export GE_LOCATION="global"
export GE_APP_ORG_NAME="${SLUG}, Inc."
export GE_APP_DISPLAY_NAME="${SLUG} ${GE_LOCATION} app"
export GE_APP_NAME="app-${SLUG}-${GE_LOCATION}"
export GE_APP_INIT="${GE_APP_NAME}_$(date +%s)"

echo ${GE_LOCATION}
echo ${GE_APP_ORG_NAME}
echo ${GE_APP_DISPLAY_NAME}
echo ${GE_APP_NAME}
echo ${GE_APP_INIT}

エージェント ID の信頼ドメインを設定する

if-then-else ステートメントは、プリンシパル エージェント ID の正しい信頼ドメインを設定するために、プロジェクトが組織に属しているかどうかを確認します。

# set var for trust domain
if [[ -n "${ORG_ID}" ]]; then
  export TRUST_DOMAIN="agents.global.org-${ORG_ID}.system.id.goog"
else
  export TRUST_DOMAIN="agents.global.proj-${PROJ_NO}.system.id.goog"
fi

echo "trust domain: ${TRUST_DOMAIN}"

請求先プロジェクトと割り当てプロジェクトを設定する

# set cli quota project
gcloud config set billing/quota_project ${PROJ_ID}
# set api quota project
gcloud auth application-default set-quota-project ${PROJ_ID}

構成ファイル用のローカル ディレクトリを作成する

# create config folder
mkdir -p cfg

Google Cloud SDK のセルフマネージド インストール(Cloud Shell の外部など)を実行している場合は、コンポーネントを最新バージョンに更新します。

# update gcloud cli
gcloud components update

API サービスを有効にする

# enable google cloud apis
gcloud services enable \
  agentregistry.googleapis.com \
  aiplatform.googleapis.com \
  cloudapiregistry.googleapis.com \
  cloudresourcemanager.googleapis.com \
  compute.googleapis.com \
  discoveryengine.googleapis.com \
  dns.googleapis.com \
  iam.googleapis.com \
  iap.googleapis.com \
  logging.googleapis.com \
  monitoring.googleapis.com \
  networkconnectivity.googleapis.com \
  networksecurity.googleapis.com \
  networkservices.googleapis.com \
  orgpolicy.googleapis.com \
  serviceextensions.googleapis.com \
  serviceusage.googleapis.com

組織のポリシー

デフォルトの Google Cloud のマネージド組織のポリシーの制約とサンドボックス ポリシーにより、この Codelab で使用される機能が制限される可能性があります。

  • compute.vmExternalIpAccess: ロックダウンされたサンドボックス組織の Compute Engine VM の外部 IPv4 アドレスを制限します。
  • discoveryengine.managed.disableCustomMcpServerConnector: カスタム MCP サーバー(custom_mcp)をデータソースとして使用するデータコネクタの作成を制限します(デフォルトで適用されます)。
  • discoveryengine.managed.allowedEgressFqdns: VPC Service Controls(VPC-SC)が有効になっている場合、またはプロジェクトが enforcedProjects にリストされている場合に、データコネクタのアウトバウンド エグレス ドメイン(instance_uri FQDN)を制限します。
  • discoveryengine.managed.allowedDataSources: VPC-SC が有効になっている場合、またはプロジェクトが enforcedProjects にリストされている場合に、許可されるデータ コネクタ タイプ(dataSource)を制限します。

プロジェクト レベルで継承された組織のポリシーの制限をオーバーライドします。

カスタム MCP 制約を無効にする

# disable data connector constraint (allow custom mcp servers)
gcloud org-policies set-policy "/dev/stdin" <<EOF
name: projects/${PROJ_ID}/policies/discoveryengine.managed.disableCustomMcpServerConnector
spec:
  rules:
  - enforce: false
EOF

条件付きデータ コネクタの制約を無効にする

# disable egress fqdn constraint on project
gcloud org-policies set-policy "/dev/stdin" <<EOF
name: projects/${PROJ_ID}/policies/discoveryengine.managed.allowedEgressFqdns
spec:
  rules:
  - enforce: false
EOF
# disable allowed data sources constraint on project
gcloud org-policies set-policy "/dev/stdin" <<EOF
name: projects/${PROJ_ID}/policies/discoveryengine.managed.allowedDataSources
spec:
  rules:
  - enforce: false
EOF

アクセス ポリシーの制約を無効にする

# disable iam v3 constraint (allow v3 access policies)
gcloud org-policies set-policy /dev/stdin << EOF
name: projects/${PROJ_NO}/policies/iam.managed.disableAccessPolicyBinding
spec:
  rules:
  - enforce: false
EOF

外部 IP アドレスを許可する

# allow external ip address for vms on project
gcloud org-policies set-policy "/dev/stdin" <<EOF
name: projects/${PROJ_ID}/policies/compute.vmExternalIpAccess
spec:
  rules:
  - allowAll: true
EOF

組織のポリシーを確認する

compute.vmExternalIpAccess が allowAll: True に設定され、他のポリシーが enforce: False であることを確認します。

# verify effective org policy constraints on project
for policy in \
  discoveryengine.managed.disableCustomMcpServerConnector \
  discoveryengine.managed.allowedEgressFqdns \
  discoveryengine.managed.allowedDataSources \
  iam.managed.disableAccessPolicyBinding \
  compute.vmExternalIpAccess; do
  gcloud org-policies describe ${policy} \
    --project=${PROJ_ID} \
    --effective \
    --format="flattened[no-pad](name.basename(),spec.rules[0])"
done

サービス エージェントのバインディングを確認する(予防措置)

新しいプロジェクトでは、networkservices.googleapis.com が最初に有効になったときに、Google Cloud が Agent Gateway サービス エージェントを自動的にプロビジョニングし、roles/agentgateway.serviceAgent を付与します。以前のクリーンアップでデフォルトのサービス エージェント バインディングが削除された可能性がある既存のプロジェクトを再利用する場合は、次のコマンドをフェイルセーフとして実行し、ID とロール バインディングがそのまま残っていることを確認します。

# ensure network services service account has been created
gcloud beta services identity create \
  --service=networkservices.googleapis.com \
  --project="${PROJ_ID}"

# ensure network services service account has service agent roles applied
gcloud projects add-iam-policy-binding "${PROJ_ID}" \
  --member="serviceAccount:service-${PROJ_NO}@gcp-sa-agentgateway.iam.gserviceaccount.com" \
  --role="roles/agentgateway.serviceAgent"

これでセットアップは完了です。次は [ネットワーク] セクションに進みます。

4. ネットワーク

このセクションでは、静的外部 IPv4 アドレスを予約し、内部下り(外向き)VPC ネットワークと分離された外部 VPC ネットワークを作成し、PSC ネットワーク アタッチメントをプロビジョニングし、Cloud NAT を構成して、ファイアウォール ルールを作成します。

静的外部 IP アドレスを予約する

Cloud NAT と外部 MCP サーバー VM 用に 2 つのリージョン静的外部 IPv4 アドレスを予約します。

# reserve static external ipv4 address for cloud nat
gcloud compute addresses create ip-${SLUG}-nat \
  --region=${REGION}

# reserve static external ipv4 address for the mcp server
gcloud compute addresses create ip-${SLUG}-mcp \
  --region=${REGION}

静的外部 IP アドレスを確認する

Gemini Enterprise では、リモート MCP エンドポイントで、公的に信頼できる TLS 証明書を使用した HTTPS を使用する必要があります。このラボでは、カスタム ドメインを登録する代わりに、IP アドレス(mcp.[IP].sslip.io など)を含むホスト名をその IP に直接解決する無料のワイルドカード DNS サービスである sslip.io を使用します。Let's Encrypt などの認証局はベア IP アドレスに証明書を発行しないため、この sslip.io ホスト名により、Caddy リバース プロキシに有効なドメイン名が提供され、MCP サーバーの信頼できる TLS 証明書が自動的にプロビジョニングされます。

IPv4 アドレスをエクスポートし、外部 MCP サーバーの sslip.io ドメイン名と URL を作成します。

# export static ip addresses and set vars for domain
export NAT_EXT_IP=$(gcloud compute addresses describe ip-${SLUG}-nat \
  --region=${REGION} \
  --format="value(address)")

export MCP_EXT_IP=$(gcloud compute addresses describe ip-${SLUG}-mcp \
  --region=${REGION} \
  --format="value(address)")

export MCP_DOMAIN="mcp.${MCP_EXT_IP}.sslip.io"
export MCP_URL="https://${MCP_DOMAIN}/mcp"

echo "cloud nat external ip    : ${NAT_EXT_IP}"
echo "mcp server external ip   : ${MCP_EXT_IP}"
echo "mcp url                  : ${MCP_URL}"

出力例:

cloud nat external ip    : 203.0.113.10
mcp server external ip   : 203.0.113.20
mcp url                  : https://mcp.203.0.113.20.sslip.io/mcp

内部ネットワークを作成する

Agent Gateway 接続テンプレートが networkAttachment を含む egressNetworkConfig を使用するように構成されている場合、PSC ネットワーク アタッチメントをホストするサブネットは、少なくとも /28 CIDR ブロックであり、プライベート Google アクセス(--enable-private-ip-google-access)が有効になっている必要があります。

内部 Agent Gateway の下り(外向き)用にグローバル VPC ネットワークを作成します。

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

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

内部ファイアウォール ルールを作成する

ロギングが有効になっているすべての下り(外向き)トラフィックを許可するファイアウォール ポリシーを作成します。これは、Agent Gateway から内部 VPC ネットワークに送信されるトラフィックをモニタリングするために使用されます。Cloud NGFW は、ネットワーク セキュリティとトラフィック モニタリングの両方で Essentials ティアと Standard ティアをサポートしています。

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

PSC ネットワーク アタッチメントを作成する

Agent Gateway からの接続を自動的に受け入れるように構成された Private Service Connect(PSC)ネットワーク アタッチメントを作成します。ネットワーク アタッチメントは、アウトバウンド下り(外向き)トラフィック用に、Agent Gateway のプロデューサー側と安全にリンクする接続のコンシューマー VPC ネットワーク側を確立します。サブネットの要件と IP 範囲の仕様の詳細については、VPC 接続を構成するをご覧ください。

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

PSC ネットワーク アタッチメントを確認する

# show psc network attachment details
gcloud compute network-attachments describe psc-na-${REGION}-agw --region=${REGION}

PSC ネットワーク アタッチメントのリソース URI を取得し、PSC_NA_URI 環境変数に保存します。この URI は、Agent Gateway 構成(networkConfig.egress.networkAttachment)で参照され、VPC ネットワークへのネットワーク下り(外向き)用の PSC インターフェースをプロビジョニングします。

# fetch psc network attachment 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}

Cloud NAT を作成する

Cloud NAT は、Agent Gateway の下り(外向き)トラフィックの送信元 IP アドレスを予約済みのパブリック IP アドレスに変換し、パブリック インターネット経由でルーティングするために使用されます。

Agent Gateway 下り(外向き)ネットワーク アタッチメント サブネットのソースのみが、Cloud NAT を使用できるものとして定義されます。

# create router for nat
gcloud compute routers create cr-nat-${SLUG}-${REGION} \
  --network=vnet-${SLUG}-internal \
  --asn=16550 \
  --region=${REGION}

# create nat
gcloud compute routers nats create nat-${SLUG}-${REGION} \
  --router=cr-nat-${SLUG}-${REGION} \
  --region=${REGION} \
  --nat-custom-subnet-ip-ranges="subnet-${REGION}-agw" \
  --nat-external-ip-pool="ip-${SLUG}-nat" \
  --enable-logging \
  --log-filter=ALL

Cloud NAT を確認する

Cloud NAT のステータスと、想定される natIP 送信元が構成されていることを確認します。

# verify cloud nat configuration
gcloud compute routers nats describe nat-${SLUG}-${REGION} \
  --router=cr-nat-${SLUG}-${REGION} \
  --region=${REGION} \
  --format="yaml(name,natIps,subnetworks.name)"

出力例:

name: nat-foo-us-central1
natIps:
- https://www.googleapis.com/compute/v1/projects/[PROJ_ID]/regions/us-central1/addresses/ip-foo-nat

外部ネットワークを作成する

外部 MCP サーバー VM をホストする、分離された別の VPC ネットワークを作成します。

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

# create subnet for mcp server
gcloud compute networks subnets create subnet-${REGION}-mcp \
  --network=vnet-${SLUG}-external \
  --range=240.240.240.0/24 \
  --region=${REGION} \
  --enable-private-ip-google-access

外部ファイアウォール ルールを作成する

外部 VPC ネットワークに 2 つの上り(内向き)ファイアウォール ルールを作成します。

  • 2001: 受信 TCP ポート 80 を許可します。これにより、Let's Encrypt は ${MCP_DOMAIN} の ACME HTTP-01 ドメイン検証チャレンジを完了できます。
  • 2002: 最初に、0.0.0.0/0 からのインバウンド TCP ポート 443 を許可して、TLS の準備状況とローカル シェルからの OAuth 2.0 tokeninfo の適用を確認してから、${NAT_EXT_IP}/32 に厳密にロックダウンします。
# create fw policy
gcloud compute network-firewall-policies create fw-policy-${SLUG}-external --global
# create fw policy rules
gcloud compute network-firewall-policies rules create 2001 \
  --description="allow in any http" \
  --firewall-policy=fw-policy-${SLUG}-external \
  --global-firewall-policy \
  --action=allow \
  --direction=INGRESS \
  --layer4-configs=tcp:80 \
  --src-ip-ranges=0.0.0.0/0 \
  --dest-ip-ranges=240.240.240.0/24 \
  --enable-logging

gcloud compute network-firewall-policies rules create 2002 \
  --description="allow in any https" \
  --firewall-policy=fw-policy-${SLUG}-external \
  --global-firewall-policy \
  --action=allow \
  --direction=INGRESS \
  --layer4-configs=tcp:443 \
  --src-ip-ranges=0.0.0.0/0 \
  --dest-ip-ranges=240.240.240.0/24 \
  --enable-logging
# bind fw policy to network
gcloud compute network-firewall-policies associations create \
  --name=fw-policy-bind-${SLUG}-external \
  --firewall-policy=fw-policy-${SLUG}-external \
  --network=vnet-${SLUG}-external \
  --global-firewall-policy

ネットワークに関する説明は以上です。次は Agent Gateway セクションに進みます。

5. Agent Gateway

このセクションでは、vpcEgress: ALL_TRAFFIC を使用して Agent Connectivity テンプレートをデプロイし、リージョン Agent Gateway をデプロイして、Identity-Aware Proxy(IAP)に認可を委任し、アウトバウンド ネットワーク リクエストを管理する IAM 統合アクセス ポリシー ルールを定義します。

接続テンプレートを作成する

接続テンプレートの仕様では、次のことを定義します。

  • egressNetworkConfig.networkAttachment: PSC ネットワーク アタッチメントを指し、Agent Gateway を VPC ネットワークに接続します。
  • egressNetworkConfig.vpcEgress: ALL_TRAFFIC に設定すると、すべてのアウトバウンド エージェント接続(パブリック インターネット MCP エンドポイントを含む)が VPC ネットワークに転送され、Cloud NAT 送信元 IP アドレス変換を使用して終了します。

接続テンプレート構成ファイルを作成してインポートします。

# create connectivity template config file (vpcEgress: ALL_TRAFFIC)
cat > cfg/act-${AGW_NAME}.yaml << EOF
accessPath: AGENT_TO_ANYWHERE
name: projects/${PROJ_NO}/locations/${REGION}/agentConnectivityTemplates/act-${AGW_NAME}
deploymentModel: CENTRALIZED
egressNetworkConfig:
  networkAttachment: ${PSC_NA_URI}
  vpcEgress: ALL_TRAFFIC
EOF
# import connectivity template file (create template)
gcloud network-services agent-connectivity-templates import act-${AGW_NAME} \
  --source="cfg/act-${AGW_NAME}.yaml" \
  --location=${REGION}

Agent Gateway を作成する

Agent Gateway の仕様では、次のことを定義します。

  • agentConnectivityTemplate: 接続テンプレートを指します。これにより、ゲートウェイは VPC 接続に PSC ネットワーク アタッチメントを使用できます。
  • registries: Gateway をリージョン Agent Registry に関連付けて、カタログに登録された MCP エンドポイントとツールスキーマを解決します。

Agent Gateway 構成ファイルを作成してインポートします。

# create agent gateway config file
cat > cfg/${AGW_NAME}.yaml << EOF
name: ${AGW_NAME}
protocols:
  - MCP
googleManaged:
  governedAccessPath: AGENT_TO_ANYWHERE
agentConnectivityTemplate: projects/${PROJ_NO}/locations/${REGION}/agentConnectivityTemplates/act-${AGW_NAME}
registries:
  - "//agentregistry.googleapis.com/projects/${PROJ_ID}/locations/${REGION}"
EOF
# import agent gateway config file (create gateway)
gcloud network-services agent-gateways import ${AGW_NAME} \
  --source="cfg/${AGW_NAME}.yaml" \
  --location=${REGION}

Agent Gateway のデプロイを確認する

Agent Registry と接続テンプレートの構成を確認します。

# verify agent gateway deployment details
gcloud network-services agent-gateways describe ${AGW_NAME} \
  --location=${REGION} \
  --format="yaml(name,agentConnectivityTemplate,registries)"

出力例:

agentConnectivityTemplate: projects/[PROJ_NO]/locations/us-central1/agentConnectivityTemplates/act-foo-us-central1
name: projects/[PROJ_ID]/locations/us-central1/agentGateways/agw-foo-us-central1-ata
registries:
- //agentregistry.googleapis.com/projects/[PROJ_ID]/locations/us-central1

PSC ネットワーク アタッチメントを調べて、ゲートウェイ接続を確認します。

# show psc network attachment details
gcloud compute network-attachments describe psc-na-${REGION}-agw \
  --region=${REGION} \
  --format="yaml(connectionEndpoints)"

出力例:

connectionEndpoints:
- ipAddress: 10.10.10.2
  projectIdOrNum: '[TENANT_PROJ_NO]'
  status: ACCEPTED
  subnetwork: https://www.googleapis.com/compute/v1/projects/[PROJ_ID]/regions/us-central1/subnetworks/subnet-us-central1-agw

認可を委任する

Agent Gateway は、Identity-Aware Proxy(IAP)統合アクセス ポリシー(UAP)と統合された認可ポリシー(networksecurity.authzPolicies)を使用して、アウトバウンド ツール トラフィックを保護し、管理します。

Agent Gateway は基本的なインライン ALLOW ルールと DENY ルールをサポートしていますが、エンタープライズ環境では一元化された ID 中心のガバナンスが必要です。IAM 統合アクセス ポリシー(またはアクセス ポリシー)を使用すると、標準の IAM v3 アクセス ポリシーを使用して下り(外向き)アクセスルールを管理できます。

figure3

図 3. 認可アーキテクチャ

認可フローは、次の 3 つのコンポーネントを接続します。

  1. ゲートウェイ認可ポリシー(authzPolicy):
    • Agent Gateway をターゲットとするリージョン リソース。
    • すべての送信認可チェックを IAP 認可拡張機能に転送するように policyProfile: REQUEST_AUTHZ と action: CUSTOM で構成されています。
  2. IAP サービス拡張機能(authzExtension):
    • リクエストの認可を Identity-Aware Proxy(iap.googleapis.com)に委任するリージョン リソース。
    • ポリシー バージョン V2 を使用して ENFORCE モードでポリシーを評価します。
  3. IAM 統合アクセス ポリシーとバインディング(accessPolicy と policyBinding):
    • きめ細かいアクセスルールを含むグローバル IAM v3 リソース。
    • 呼び出し元エージェントの SPIFFE プリンシパル ID を認証し、ユニバーサル iap.googleapis.com/resources.egressViaIAP 権限を確認し、宛先属性に対して Common Expression Language(CEL)条件を評価します。

認可拡張機能をデプロイする

# create authz extension config file
cat > "cfg/${AGW_NAME}-svc-ext-authz-iap.yaml" << EOF
name: ${AGW_NAME}-svc-ext-authz-iap
service: iap.googleapis.com
failOpen: false
timeout: 1s
metadata:
  iapPolicyVersion: "V2"
EOF
# import authz extension config file (create authz extension)
gcloud service-extensions authz-extensions import ${AGW_NAME}-svc-ext-authz-iap \
  --source="cfg/${AGW_NAME}-svc-ext-authz-iap.yaml" \
  --location=${REGION}

認可拡張機能を確認する

認可拡張機能が有効になっていることを確認します。

# list authz extensions
gcloud service-extensions authz-extensions list \
  --location=${REGION} \
  --format="table(
    name.basename():label=NAME,
    createTime.date(tz=LOCAL):label=CREATED,
    service:label=SERVICE,
    metadata:label=METADATA,
    timeout:label=TIMEOUT
  )"

認可ポリシーをデプロイする

Agent Gateway をターゲットとし、リクエストの検証を IAP の認可拡張機能に委任する network-security 認可ポリシー構成を作成します。

# create authz policy config file
cat > cfg/${AGW_NAME}-authz-policy-iap.yaml << EOF
name: ${AGW_NAME}-authz-policy-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"
EOF
# import authz policy config file (create authz policy)
gcloud network-security authz-policies import ${AGW_NAME}-authz-policy-iap \
  --source=cfg/${AGW_NAME}-authz-policy-iap.yaml \
  --location=${REGION}

認可ポリシーを確認する

認可ポリシーが有効であることを確認します。

# list authz policies
gcloud network-security authz-policies list \
  --location=${REGION} \
  --format="table(
    name.basename():label=NAME,
    action:label=ACTION,
    policyProfile:label=POLICY_PROFILE,
    customProvider.authzExtension.resources[0].basename():label=CUSTOM_PROVIDER_RESOURCE
  )"

IAM アクセス ポリシーを作成する

Agent Gateway は、認可チェックを IAP に委任し、Agent Registry から宛先メタデータを解決するようになりました。次に、アウトバウンド ツールの実行を制御する IAM 統合アクセス ポリシー ルールを定義します。

IAP は、次の Agent Registry の宛先属性に対して CEL 属性式を評価します。

  • 登録ステータス(destination.is_registered):
    • 宛先が Agent Registry にカタログ登録されているかどうかを示すブール値(true/false)。
  • MCP サーバー名(destination.agent_registry.mcp_server.name):
    • Agent Registry に登録されている正規の MCP サーバー リソース名。
  • MCP メソッド(destination.agent_registry.mcp_server.method):
    • 呼び出される MCP メソッド(tools/call、tools/list、initialize など)。
  • ツール名(destination.agent_registry.mcp_server.tool.name):
    • 呼び出された特定のツール名(get_sprocket_by_id や get_sprockets など)。登録された MCP サーバーで、きめ細かいツールレベルの認可を有効にします。

IAM アクセス ポリシー ルールを定義する

IAM ポリシー ルール マニフェストでは、次の内容を指定します。

  • プリンシパル: Gemini Enterprise コア アシスタント エージェントを表す SPIFFE プリンシパル ID。
  • 権限: すべての IAP 管理下の下り(外向き)トラフィックに必要なユニバーサル iap.googleapis.com/resources.egressViaIAP 権限。
  • 条件: エージェントが Agent Registry にカタログ登録されたエンドポイントのみを呼び出すことができるようにする CEL 式(destination.is_registered == true)。

ポリシー ルール マニフェスト ファイルを作成します。

# create access policy rule file
cat > cfg/uap-policy-${SLUG}-rules.json << EOF
[
  {
    "description": "allow ge assistant to any registered service",
    "effect": "ALLOW",
    "principals": [
      "principal://${TRUST_DOMAIN}/resources/discoveryengine/projects/${PROJ_NO}/locations/global/engines/${GE_APP_INIT}/assistants/default_assistant/agents/default/core_assistant"
    ],
    "operation": {
      "permissions": [
        "iap.googleapis.com/resources.egressViaIAP"
      ]
    },
    "conditions": {
      "iap.googleapis.com": {
        "expression": \
        "destination.is_registered == true"
      }
    }
  }
]
EOF

IAM アクセス ポリシーをデプロイする

マニフェスト ファイルで定義されたルールを使用して、グローバル IAM アクセス ポリシーを作成します。

# create iam access policy
gcloud iam access-policies create uap-policy-${SLUG} \
  --details-rules=cfg/uap-policy-${SLUG}-rules.json \
  --project=${PROJ_ID} \
  --location=global

IAM アクセス ポリシーを確認する

IAM アクセス ポリシーが正常に作成されたことを確認し、ルールの詳細を調べます。

# show iam access policy details
gcloud iam access-policies describe uap-policy-${SLUG} \
  --project=${PROJ_ID} \
  --location=global

出力例:

details:
  rules:
  - conditions:
      iap.googleapis.com:
        expression: destination.is_registered == true
    description: allow ge assistant to any registered service
    effect: ALLOW
    operation:
      permissions:
      - iap.googleapis.com/resources.egressViaIAP
    principals:
    - principal://agents.global.org-[ORG_ID].system.id.goog/resources/discoveryengine/projects/[PROJ_NO]/locations/global/engines/app-foo-global_[EPOCH]/assistants/default_assistant/agents/default/core_assistant
name: projects/[PROJ_ID]/locations/global/accessPolicies/uap-policy-foo

IAM アクセス ポリシーをプロジェクトにバインドする

プロジェクト内のすべてのエージェント Gateway で適用を有効にするには、IAM アクセス ポリシーをプロジェクト リソースに関連付けるポリシー バインディングを作成します。

# bind iam access policy to project resource
gcloud iam policy-bindings create uap-bind-${SLUG} \
  --policy="projects/${PROJ_ID}/locations/global/accessPolicies/uap-policy-${SLUG}" \
  --target-resource="//cloudresourcemanager.googleapis.com/projects/${PROJ_ID}" \
  --project=${PROJ_ID} \
  --location=global

IAM アクセス ポリシー バインディングを確認する

アクティブなポリシー バインディングが正しいポリシーとターゲットを指していることを確認します。

# show policy binding details
gcloud iam policy-bindings describe uap-bind-${SLUG} \
  --project=${PROJ_ID} \
  --location=global

エージェント ゲートウェイの説明は以上です。次は MCP サーバーのセクションに進みます。

6. MCP サーバー

このセクションでは、get_sprockets ツールと get_sprocket_by_id ツールを公開する外部 FastMCP OAuth 2.0 リソース サーバー(mcp-sprockets)をデプロイし、https://oauth2.googleapis.com/tokeninfo に対して Google OAuth 2.0 ベアラー トークンを暗号的に検証することを確認します。次に、外部ファイアウォール ルールを Cloud NAT 静的 IP に厳密にロックダウンします。

外部 MCP サーバーをデプロイする

起動スクリプトは、Caddy と FastMCP をインストールし、127.0.0.1:8080 で mcp-sprockets サーバーを起動し、${MCP_DOMAIN} で HTTPS を提供するように Caddy を構成します。

Gemini Enterprise が MCP ツール(get_sprockets や get_sprocket_by_id など)を呼び出すたびに、サーバーは次の処理を行います。

  1. 呼び出し元の送信元 IP を X-Forwarded-For ヘッダー(egress_source_ip)から読み取ります。
  2. Authorization: Bearer [access_token] ヘッダーを抽出し、Google OAuth 2.0 検証エンドポイント(https://oauth2.googleapis.com/tokeninfo?access_token=[token])に対してトークンを検証します。
  3. "egress_source_ip" とともに "authenticated_user"([user_email])を返します。これにより、外部 MCP サーバーが Cloud NAT の静的 IP とエンドユーザーの Google OAuth 2.0 ID の両方を検証したことが証明されます。
# create startup script for the sprockets mcp server vm
cat > cfg/server-startup.sh << 'EOF'
#!/bin/bash
set -euo pipefail

MCP_DOMAIN=$(curl -s -H "Metadata-Flavor: Google" \
  http://metadata.google.internal/computeMetadata/v1/instance/attributes/mcp-domain)

export DEBIAN_FRONTEND=noninteractive
apt-get update -y
apt-get install -y caddy python3-venv curl

mkdir -p /opt/mcp-sprockets
python3 -m venv /opt/mcp-sprockets/venv
/opt/mcp-sprockets/venv/bin/pip install --upgrade pip
/opt/mcp-sprockets/venv/bin/pip install "fastmcp>=2.14.0" "uvicorn>=0.34.0"

cat <<'PYEOF' > /opt/mcp-sprockets/main.py
import json
import urllib.parse
import urllib.request
from typing import Any, Dict
from fastmcp import FastMCP
from fastmcp.server.dependencies import get_http_headers
from mcp.types import ToolAnnotations

mcp = FastMCP("mcp-sprockets")

SPROCKETS = {
    "SPK-101": {"id": "SPK-101", "model": "ANSI-40-12T", "teeth": 12, "pitch_in": 0.5, "material": "1045 Carbon Steel", "stock": 140, "unit_price": 18.50},
    "SPK-102": {"id": "SPK-102", "model": "ANSI-40-24T", "teeth": 24, "pitch_in": 0.5, "material": "1045 Carbon Steel", "stock": 85, "unit_price": 29.00},
    "SPK-103": {"id": "SPK-103", "model": "ANSI-60-16T", "teeth": 16, "pitch_in": 0.75, "material": "304 Stainless Steel", "stock": 60, "unit_price": 42.00},
    "SPK-104": {"id": "SPK-104", "model": "ANSI-60-32T", "teeth": 32, "pitch_in": 0.75, "material": "304 Stainless Steel", "stock": 35, "unit_price": 68.00},
    "SPK-105": {"id": "SPK-105", "model": "ANSI-80-20T", "teeth": 20, "pitch_in": 1.0, "material": "4140 Alloy Steel", "stock": 50, "unit_price": 95.00}
}

def verify_google_oauth_token(headers: Dict[str, str]) -> str:
    token = headers.get("authorization", "")[7:].strip() if headers.get("authorization", "").lower().startswith("bearer ") else ""
    if not token:
        return "unauthenticated (missing Bearer token)"
    url = "https://oauth2.googleapis.com/tokeninfo?" + urllib.parse.urlencode({"access_token": token})
    with urllib.request.urlopen(url, timeout=5) as resp:
        info = json.loads(resp.read().decode("utf-8"))
        return info.get("email", info.get("sub", "verified-oauth-user"))

@mcp.tool(
    description="list all sprockets in the inventory catalog",
    annotations=ToolAnnotations(
        readOnlyHint=True,
        destructiveHint=False,
        idempotentHint=True,
        openWorldHint=False
    )
)
def get_sprockets() -> Dict[str, Any]:
    headers = get_http_headers(include_all=True)
    caller_ip = headers.get("x-forwarded-for", "unknown").split(",")[0].strip()
    try:
        user_email = verify_google_oauth_token(headers)
    except Exception as exc:
        return {"egress_source_ip": caller_ip, "authenticated_user": None, "error": f"OAuth token verification failed: {exc}"}
    return {
        "egress_source_ip": caller_ip,
        "authenticated_user": user_email,
        "sprockets": list(SPROCKETS.values())
    }

@mcp.tool(
    description="fetch details of a specific sprocket by its ID (eg, SPK-101)",
    annotations=ToolAnnotations(
        readOnlyHint=True,
        destructiveHint=False,
        idempotentHint=True,
        openWorldHint=False
    )
)
def get_sprocket_by_id(sprocket_id: str) -> Dict[str, Any]:
    headers = get_http_headers(include_all=True)
    caller_ip = headers.get("x-forwarded-for", "unknown").split(",")[0].strip()
    try:
        user_email = verify_google_oauth_token(headers)
    except Exception as exc:
        return {"egress_source_ip": caller_ip, "authenticated_user": None, "error": f"OAuth token verification failed: {exc}"}
    sprocket = SPROCKETS.get(sprocket_id.upper())
    if sprocket:
        return {
            "egress_source_ip": caller_ip,
            "authenticated_user": user_email,
            "sprocket": sprocket
        }
    return {
        "egress_source_ip": caller_ip,
        "authenticated_user": user_email,
        "error": f"Sprocket '{sprocket_id}' not found."
    }

app = mcp.http_app(stateless_http=True)
PYEOF

cat <<'SVCEOF' > /etc/systemd/system/mcp-sprockets.service
[Unit]
Description=Sprockets FastMCP Server
After=network.target

[Service]
Type=simple
WorkingDirectory=/opt/mcp-sprockets
ExecStart=/opt/mcp-sprockets/venv/bin/uvicorn main:app --host 127.0.0.1 --port 8080 --forwarded-allow-ips="*"
Restart=always

[Install]
WantedBy=multi-user.target
SVCEOF

systemctl daemon-reload
systemctl enable --now mcp-sprockets.service
systemctl restart mcp-sprockets.service

cat <<CADDYEOF > "/etc/caddy/Caddyfile"
${MCP_DOMAIN} {
    reverse_proxy 127.0.0.1:8080
}
CADDYEOF

systemctl restart caddy
EOF
# create vm instance
gcloud compute instances create vm-${MCP_NAME} \
  --zone=${ZONE} \
  --machine-type=e2-micro \
  --image-family=debian-12 \
  --image-project=debian-cloud \
  --shielded-secure-boot \
  --network-interface="subnet=subnet-${REGION}-mcp,address=${MCP_EXT_IP}" \
  --metadata="mcp-domain=${MCP_DOMAIN}" \
  --metadata-from-file="startup-script=cfg/server-startup.sh"

MCP サーバーの準備状況と OAuth 2.0 tokeninfo の検証を確認する

起動スクリプトが FastMCP と Caddy をインストールし、Let's Encrypt TLS 証明書をプロビジョニングするまで数分待ちます。

# poll until caddy serves valid tls and fastmcp responds to tools/list
for i in $(seq 1 30); do
  RESP=$(curl -s -X POST "${MCP_URL}" \
    -H "Content-Type: application/json" \
    -H "Accept: application/json, text/event-stream" \
    -d '{"jsonrpc": "2.0", "id": 1, "method": "tools/list", "params": {}}' || true)
  if echo "${RESP}" | grep -q "get_sprockets"; then
    echo "mcp server and let's encrypt tls ready"
    break
  fi
  echo "...waiting for mcp server and let's encrypt tls (attempt ${i}/30)..."
  sleep 5
done

sslip.io DNS の解決、Caddy によって提供される Let's Encrypt TLS 証明書、FastMCPtools/list レスポンスを検証します。

# query mcp server for tools list
curl -v -X POST "${MCP_URL}" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc": "2.0", "id": 1, "method": "tools/list", "params": {}}' \
  2>&1 | grep -E "Connected to|subject:|issuer:|SSL certificate|HTTP/|event:|data:"

出力例:

* Connected to mcp.203.0.113.20.sslip.io (203.0.113.20) port 443
*  subject: CN=mcp.203.0.113.20.sslip.io
*  issuer: C=US; O=Let's Encrypt; CN=E5
*  SSL certificate verify ok.
> POST /mcp HTTP/2
< HTTP/2 200
event: message
data: {"jsonrpc":"2.0","id":1,"result":{"tools":[{"name":"get_sprockets","description":"list all sprockets in the inventory catalog","inputSchema":{"properties":{},"type":"object"},"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true}},{"name":"get_sprocket_by_id","description":"fetch details of a specific sprocket by its ID (eg, SPK-101)","inputSchema":{"properties":{"sprocket_id":{"type":"string"}},"required":["sprocket_id"],"type":"object"},"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true}}]}}

外部ファイアウォールをロックダウンする前に、Google OAuth 2.0 署名なしトークン(gcloud auth print-access-token)を渡すテスト tools/call を実行して、mcp-sprockets が https://oauth2.googleapis.com/tokeninfo に対してトークンを検証し、呼び出し元ソース IP アドレスとともに "authenticated_user" メールアドレスを返すことを確認します。

# query mcp server (test tools/call with google oauth 2.0 bearer token)
curl -s -X POST "${MCP_URL}" \
  -H "Authorization: Bearer $(gcloud auth print-access-token)" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{
    "jsonrpc": "2.0",
    "id": 2,
    "method": "tools/call",
    "params": {
      "name": "get_sprocket_by_id",
      "arguments": {"sprocket_id": "SPK-105"}
    }
  }' | sed -n 's/^data: //p' | jq '.result.structuredContent'

出力例(ファイアウォールのロックダウン前の Cloud Shell の直接 IP と、確認済みの Google OAuth メールアドレスが表示されています)。

{
  "egress_source_ip": "198.51.100.154",
  "authenticated_user": "user@example.com",
  "sprocket": {
    "id": "SPK-105",
    "model": "ANSI-80-20T",
    "teeth": 20,
    "pitch_in": 1.0,
    "material": "4140 Alloy Steel",
    "stock": 50,
    "unit_price": 95.0
  }
}

ファイアウォールを Cloud NAT 静的 IP にロックダウンする

TCP ポート 443 が Cloud NAT 静的外部 IP(${NAT_EXT_IP}/32)からのトラフィックのみを受け入れるように、外部 VPC ネットワーク ファイアウォール ポリシーを更新します。

# create fw policy rule allowing https only from cloud nat static ip
gcloud compute network-firewall-policies rules create 2003 \
  --description="allow in any https from nat ip" \
  --firewall-policy=fw-policy-${SLUG}-external \
  --global-firewall-policy \
  --action=allow \
  --direction=INGRESS \
  --layer4-configs=tcp:443 \
  --src-ip-ranges="${NAT_EXT_IP}/32" \
  --dest-ip-ranges=240.240.240.0/24 \
  --enable-logging
# delete temporary open fw policy rules
gcloud compute network-firewall-policies rules delete 2001 \
  --firewall-policy=fw-policy-${SLUG}-external \
  --global-firewall-policy

gcloud compute network-firewall-policies rules delete 2002 \
  --firewall-policy=fw-policy-${SLUG}-external \
  --global-firewall-policy

外部ファイアウォール ポリシー ルールが設定されていることを確認します。

# list all rules in the global network firewall policy
gcloud compute network-firewall-policies describe fw-policy-${SLUG}-external \
  --global \
  --flatten="rules[]" \
  --format="table(
    rules.priority:label=RULE,
    rules.action:label=ACTION,
    rules.direction:label=DIRECTION,
    firstof(
      rules.match.srcIpRanges,
      rules.match.srcAddressGroups,
      rules.match.srcSecureTags.name
    ).map().sub('projects/[^/]+/(locations/global/|global/)', '').list():label=SRC,
    firstof(
      rules.targetForwardingRules,
      rules.match.destIpRanges,
      rules.targetType
    ).map().sub('projects/[^/]+/(locations/global/|global/)', '').list():label=DST
  )"

Cloud NAT 静的 IP の外部への直接接続がブロックされていることを確認します。

# query mcp server
curl -i --connect-timeout 5 "${MCP_URL}" \
  || echo "Expected: Direct access blocked (not originating from ${NAT_EXT_IP})"

出力例:

curl: (28) Connection timed out after 5001 milliseconds
Expected: Direct access blocked (not originating from 203.0.113.10)

MCP サーバーの説明は以上です。次は Agent Registry セクションに進みます。

7. Agent Registry

Agent Registry は、AI エコシステム内のすべてのエージェント、MCP サーバー、エンドポイント(API)の一元化されたインベントリです。また、AI アプリケーションで使用する他の登録済みツールやサービスを一覧表示、検索、検出するために使用できるカタログでもあります。Agent Gateway は、レジストリのデータモデルをガバナンス フレームワークとして使用して、下り(外向き)アクセス制御を適用します。エージェントを呼び出すプリンシパルの IAM ロール付与は、レジストリ リソースにバインドされたポリシーに対してチェックされます。

このセクションでは、MCP サーバーとそのツール スキーマ(toolspec.json)を Agent Registry に登録します。Gemini Enterprise が Agent Gateway を介して Google API とサービスとやり取りできるようにするには、Gemini Enterprise が認証コードをトークンに交換するために使用する Google API インターフェースを指定する単一のレジストリ エンドポイント サービス(oauth2-googleapis)も作成します。

MCP サーバーを登録する

Gemini Enterprise が MCP サーバーで使用可能なツールを検出できるようにするには、Agent Registry にサービスを登録するときにツール仕様ファイル(cfg/toolspec.json)を指定します。

MCP ツールの仕様を作成する

# create tool spec file for the sprockets mcp server
cat <<'EOF' > cfg/toolspec.json
{
  "tools": [
    {
      "name": "get_sprockets",
      "description": "List all sprockets in the inventory catalog.",
      "inputSchema": {
        "type": "object",
        "properties": {}
      },
      "isReadOnly": true,
      "isDestructive": false,
      "isIdempotent": true,
      "isOpenWorld": false
    },
    {
      "name": "get_sprocket_by_id",
      "description": "Fetch details of a specific sprocket by its ID (e.g., SPK-101).",
      "inputSchema": {
        "type": "object",
        "properties": {
          "sprocket_id": {
            "type": "string",
            "description": "The sprocket identifier (e.g., SPK-101, SPK-105)."
          }
        },
        "required": [
          "sprocket_id"
        ]
      },
      "isReadOnly": true,
      "isDestructive": false,
      "isIdempotent": true,
      "isOpenWorld": false
    }
  ]
}
EOF

MCP サーバー サービスを作成する

# create registry service (mcp server)
gcloud agent-registry services create ${MCP_NAME} \
  --project=${PROJ_ID} \
  --location=${REGION} \
  --display-name="${MCP_DOMAIN}" \
  --description="MANDATORY SPROCKETS INVENTORY & CATALOG AGENT: You MUST \
ALWAYS invoke this tool (get_sprockets or get_sprocket_by_id) for ANY question \
about sprockets, inventory catalog listings, sprocket IDs (SPK-101 through \
SPK-105), ANSI models (ANSI-40, ANSI-60, ANSI-80), tooth counts, pitch, \
material, stock levels, unit prices, authenticated_user, or egress_source_ip \
verification. NEVER guess or fabricate inventory data yourself and NEVER \
transfer sprocket queries to file_and_coding_agent or web search. Always \
delegate every sprocket or inventory lookup to this tool." \
  --mcp-server-spec-type=tool-spec \
  --mcp-server-spec-content=cfg/toolspec.json \
  --interfaces=protocolBinding=JSONRPC,url="${MCP_URL}"

Agent Registry で MCP サーバーを確認する

サービスが、エンドポイント URL と利用可能なツールとともに、リージョン内の登録済み MCP サーバーとしてリストされていることを確認します。

# list registry regional mcp servers
gcloud agent-registry mcp-servers list \
  --location=${REGION} \
  --project=${PROJ_ID} \
  --format="table(
    name.basename():label=REGISTRY_ID,
    displayName:label=DISPLAY_NAME,
    interfaces[0].url:label=ENDPOINT_URL,
    tools[].name.list():label=TOOLS
  )"

出力例:

REGISTRY_ID                                          DISPLAY_NAME               ENDPOINT_URL                          TOOLS
agentregistry-00000000-0000-0000-5c39-1f6e542bb2b8   mcp.203.0.113.20.sslip.io  https://mcp.203.0.113.20.sslip.io/mcp  get_sprockets,get_sprocket_by_id

サービス構成仕様を表示して、各ツールの正確なツール定義、入力スキーマ、動作アノテーションが登録されていることを確認します。

# describe mcp server tool specs
gcloud agent-registry services describe ${MCP_NAME} \
  --location=${REGION} \
  --project=${PROJ_ID} \
  --format="yaml(mcpServerSpec.content.tools)"

OAuth エンドポイントを登録する

Gemini Enterprise DataConnector バックエンド(認可フローで OAuth クライアントとして機能)が Agent Gateway を介して Google OAuth 2.0 認可サーバー(https://oauth2.googleapis.com/token)に POST リクエストを送信できるようにするには、IAP が destination.is_registered == true を評価するように、トークン エンドポイントを Agent Registry に登録する必要があります。

エンドポイント サービスを作成する

# create registry service (endpoint)
gcloud agent-registry services create oauth2-googleapis \
  --project=${PROJ_ID} \
  --location=${REGION} \
  --display-name="oauth2.googleapis.com" \
  --description="google oauth2 endpoint" \
  --endpoint-spec-type=no-spec \
  --interfaces=protocolBinding=JSONRPC,url=https://oauth2.googleapis.com

エンドポイントの登録を確認する

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

想定される出力:

DISPLAY_NAME           ENDPOINT_ID                                         URL
oauth2.googleapis.com  agentregistry-00000000-0000-0000-0012-3456789abcde  https://oauth2.googleapis.com

IAM アクセス ポリシー

IAM アクセス ポリシーは、Gemini Enterprise アプリ core_assistant エージェント ID によってリクエストされた登録済みサービスに対する許可ポリシーを定義するため、OAuth トークン交換に追加の IAM ポリシー バインディングは必要ありません。

  - conditions:
      iap.googleapis.com:
        expression: destination.is_registered == true
    description: allow ge assistant to any registered service
    effect: ALLOW

これで Agent Registry の部分は終わりです。次は Gemini Enterprise のセクションに進みます。

8. Gemini Enterprise

このセクションでは、ID プロバイダ、トライアル ライセンス、Google Identity OAuth 2.0 クライアント認証情報を構成し、Gemini Enterprise アプリ(${GE_APP_ID})を作成し、アウトバウンド エグレスを Agent Gateway(${AGW_URI})にバインドし、登録済みの sprockets MCP サーバーを OAuth 2.0 カスタム MCP データストアとして接続します。

Gemini Enterprise アプリを作成する

次のメソッドは、discoveryengine.googleapis.com API を使用して Gemini Enterprise アプリのリソースと構成を作成します。Google Cloud コンソール UI を使用して構成するには、アプリを作成するの手順をご覧ください。

# create engine (ge app)
curl -s -X POST "https://${GE_LOCATION}-discoveryengine.googleapis.com/v1/projects/${PROJ_ID}/locations/${GE_LOCATION}/collections/default_collection/engines?engineId=${GE_APP_INIT}" \
  -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "X-Goog-User-Project: ${PROJ_ID}" -H "Content-Type: application/json" \
  -d @- <<EOF
{
  "displayName": "${GE_APP_DISPLAY_NAME}",
  "dataStoreIds": [],
  "solutionType": "SOLUTION_TYPE_SEARCH",
  "industryVertical": "GENERIC",
  "appType": "APP_TYPE_INTRANET",
  "searchEngineConfig": {
    "searchTier": "SEARCH_TIER_ENTERPRISE",
    "searchAddOns": [
      "SEARCH_ADD_ON_LLM"
    ]
  },
  "commonConfig": {
    "companyName": "${GE_APP_ORG_NAME}"
  },
  "observabilityConfig": {
    "observabilityEnabled": true,
    "sensitiveLoggingEnabled": true
  }
}
EOF

アプリの作成を確認する

# fetch engine (ge app) id
export GE_APP_ID=$(curl -s "https://${GE_LOCATION}-discoveryengine.googleapis.com/v1/projects/${PROJ_ID}/locations/${GE_LOCATION}/collections/default_collection/engines" \
  -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" -H "X-Goog-User-Project: ${PROJ_ID}" \
  | jq -r --arg name "${GE_APP_DISPLAY_NAME}" '.engines[] | select(.displayName==$name) | .name | split("/") | last')

echo "engine (ge app) id: ${GE_APP_ID}"

エンジン詳細を表示して、作成された構成を確認します。

# get engine (ge app) details
curl -s "https://${GE_LOCATION}-discoveryengine.googleapis.com/v1/projects/${PROJ_ID}/locations/${GE_LOCATION}/collections/default_collection/engines/${GE_APP_ID}" \
  -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "X-Goog-User-Project: ${PROJ_ID}"

ID プロバイダを有効にする

Gemini Enterprise アプリでエンドユーザー認証の ID プロバイダとして Google Identity を有効にします。

次の方法では、discoveryengine.googleapis.com API を使用して Gemini Enterprise アプリの ID プロバイダを構成します。Google Cloud コンソール UI を使用して構成するには、ID プロバイダを構成するの手順をご覧ください。

# set identity provider
curl -s -X PATCH "https://${GE_LOCATION}-discoveryengine.googleapis.com/v1/projects/${PROJ_ID}/locations/${GE_LOCATION}/aclConfig" \
  -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "X-Goog-User-Project: ${PROJ_ID}" -H "Content-Type: application/json" \
  -d @- <<EOF
{
  "idpConfig": {
    "idpType": "GSUITE"
  }
}
EOF

ID プロバイダを確認する

# show identity provider
curl -s "https://${GE_LOCATION}-discoveryengine.googleapis.com/v1/projects/${PROJ_ID}/locations/${GE_LOCATION}/aclConfig" \
  -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "X-Goog-User-Project: ${PROJ_ID}"

出力 "idpType": "GSUITE" は、Google Identity プロバイダに対応しています。

(省略可)Gemini Enterprise のトライアル ライセンスを有効にする

Gemini Enterprise ライセンスが割り当てられているプロジェクトを使用している場合は、この手順をスキップできます。ライセンスのない新しいプロジェクトを使用している場合は、次の手順に進みます。

ライセンス構成リソースを作成して、Gemini Enterprise ユーザーシートの 30 日間の利用資格を付与します。これにより、デフォルトのライセンスが新しいトライアルに設定され、ログインしたユーザーに自動的にシートが付与されます。

# configure free trial subscription
curl -s -X POST "https://${GE_LOCATION}-discoveryengine.googleapis.com/v1/projects/${PROJ_ID}/locations/${GE_LOCATION}/licenseConfigs?licenseConfigId=free_trial_gemini" \
  -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "X-Goog-User-Project: ${PROJ_ID}" -H "Content-Type: application/json" \
  -d @- <<EOF
{
  "subscriptionTier": "SUBSCRIPTION_TIER_SEARCH_AND_ASSISTANT",
  "freeTrial": true
}
EOF

ライセンスが適用されたことを確認する

# show license config
curl -s "https://${GE_LOCATION}-discoveryengine.googleapis.com/v1/projects/${PROJ_ID}/locations/${GE_LOCATION}/licenseConfigs/free_trial_gemini" \
  -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "X-Goog-User-Project: ${PROJ_ID}"

"subscriptionTerm": "SUBSCRIPTION_TERM_ONE_MONTH" と "freeTrial": true を確認します。

# verify auto-registration enabled on default user store
curl -s "https://${GE_LOCATION}-discoveryengine.googleapis.com/v1/projects/${PROJ_ID}/locations/${GE_LOCATION}/userStores/default_user_store" \
  -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "X-Goog-User-Project: ${PROJ_ID}"

../free_trial_gemini" と "enableLicenseAutoRegister": true を確認します。

Gemini Enterprise(OAuth 2.0 クライアント)がユーザー委任の Google OAuth 2.0 Bearer トークンを取得して、ユーザーに代わって mcp-sprocketsリソース サーバーに提示できるようにするには、Google Auth Platform OAuth 同意画面を構成し、プロジェクトにウェブ アプリケーション OAuth 2.0 クライアント ID を作成します。

プロジェクトの Google Auth Platform の概要の Google Cloud コンソールの直接 URL を出力します。

# print google auth platform consent screen url
echo "https://console.cloud.google.com/auth/overview?project=${PROJ_ID}"
  1. ブラウザで URL を開き、[始める] をクリックします(プロジェクトにまだ設定されていない場合)。
  2. [アプリ情報] で、[アプリ名] を mcp-sprockets-oauth に設定し、[ユーザー サポートメール] としてメールアドレス(${USER_IDENTITY})を選択して、[次へ] をクリックします。
  3. [対象] で、[内部](アカウントが Google Workspace 組織に属している場合は推奨)または [外部](個人用アカウントを使用している場合。外部 を選択した場合は、[**対象のテストユーザー**] に ${USER_IDENTITY} を追加します)を選択し、[次へ] をクリックします。
  4. [連絡先情報] にメールアドレスを入力し、ポリシー契約に同意して、[作成] をクリックします。

2. ウェブ アプリケーションの OAuth 2.0 クライアント ID を作成する

[クライアント] ページの Google Cloud コンソールの直接 URL を出力します。

# print google auth platform clients url
echo "https://console.cloud.google.com/auth/clients?project=${PROJ_ID}"
  1. URL を開いて [+ クライアントを作成] をクリックします。
  2. [アプリケーションの種類] で [ウェブ アプリケーション] を選択し、[名前] を ge-mcp-sprockets-client に設定します。
  3. [このクライアントは AI 搭載エージェントによって使用されます] のチェックボックスをオンにして、ユーザーに代わって動作する AI エージェント ワークフローの OAuth クライアントを指定します。
  4. [承認済みのリダイレクト URI] で、[+ URI を追加] をクリックして、Gemini Enterprise OAuth コールバック URI(
      )を追加します。
    • https://vertexaisearch.cloud.google.com/oauth-redirect
  5. [作成] をクリックし、生成されたクライアント ID とクライアント シークレットをコピーして、シェル セッションでエクスポートします。

前の手順で生成した OAuth 2.0 クライアント ID とクライアント シークレットを設定します。

# set oauth 2.0 client id (paste in id)
export OAUTH_CLIENT_ID="SET_YOUR_OAUTH_CLIENT_ID_HERE"
# set oauth 2.0 client secret (paste in secret)
export OAUTH_CLIENT_SECRET="SET_YOUR_OAUTH_CLIENT_SECRET_HERE"

クライアント ID が設定されていることを確認します。

# print oauth 2.0 client id
echo "oauth client id: ${OAUTH_CLIENT_ID}"

Agent Gateway にバインドする

Gemini Enterprise からの送信トラフィックを Agent Gateway 経由でルーティングすると、すべての AI エージェント ツールの呼び出しに対して、一元化された適用と静的 IP ルーティング境界が確立されます。

  • 一元化されたポリシー適用: Agent Gateway は、トラフィックがエージェント環境を離れる前に、認可ポリシーに対してアウトバウンド ツール リクエストを評価するインライン プロキシとして機能します。
  • 決定論的 VPC と Cloud NAT 下り(外向き): vpcEgress: ALL_TRAFFIC で構成された Agent Gateway に Gemini Enterprise をバインドすると、ツール呼び出しは PSC ネットワーク アタッチメント経由でルーティングされ、予約済みの Cloud NAT 静的 IP を使用して終了します。
  • 統合された監査機能: 接続されているすべての MCP サーバーで、リクエストのロギングと監査証跡を一元的に提供します。

エンジン agentGatewaySetting にパッチを適用して有効にします。

# bind engine (ge app) to agent gateway
curl -s -X PATCH "https://${GE_LOCATION}-discoveryengine.googleapis.com/v1/projects/${PROJ_ID}/locations/${GE_LOCATION}/collections/default_collection/engines/${GE_APP_ID}?updateMask=agentGatewaySetting.defaultEgressAgentGateway.name" \
  -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "X-Goog-User-Project: ${PROJ_ID}" -H "Content-Type: application/json" \
  -d @- <<EOF
{
  "agentGatewaySetting": {
    "defaultEgressAgentGateway": {
      "name": "${AGW_URI}"
    }
  }
}
EOF

Agent Gateway バインディングを確認する

アプリの構成を取得して、agentGatewaySetting バインディングを確認します。

# verify engine (ge app) agent gateway configuration
curl -s "https://${GE_LOCATION}-discoveryengine.googleapis.com/v1/projects/${PROJ_ID}/locations/${GE_LOCATION}/collections/default_collection/engines/${GE_APP_ID}" \
  -H "Authorization: Bearer $(gcloud auth print-access-token)" \
  -H "X-Goog-User-Project: ${PROJ_ID}" \
  | jq '{name: .name, displayName: .displayName, agentGatewaySetting: .agentGatewaySetting}'

出力例:

{
  "name": "projects/[PROJ_NO]/locations/global/collections/default_collection/engines/app-foo-global_[EPOCH]",
  "displayName": "foo global app",
  "agentGatewaySetting": {
    "defaultEgressAgentGateway": {
      "name": "projects/[PROJ_ID]/locations/us-central1/agentGateways/agw-foo-us-central1-ata"
    }
  }
}

カスタム MCP サーバーのデータストアを作成する

このセクションでは、Google Identity(https://accounts.google.com/o/oauth2/v2/auth と https://oauth2.googleapis.com/token)で構成された OAuth 2.0 カスタム MCP データストアを作成して、mcp-sprockets サーバーを Gemini Enterprise に接続します。

Discovery Engine API を使用する場合、このプロセスは次の 2 つの手順で構成されます。

  1. 作成(:setUpDataConnector): 専用の Collection リソース(${MCP_NAME}-collection-%timestamp)を作成し、DataConnector(custom_mcp)を auth_type: "OAUTH" に関連付け、そのバッキング DataStore(..._mcp_data)をプロビジョニングします。
  2. 有効化(PATCH .../dataConnector?updateMask=actionConfig): Agent Registry ツール仕様と Google Identity OAuth 2.0 パラメータを使用してコネクタ アクション ランタイム(actionState: "ACTIVE")を有効にし、DataStore(dataStoreIds)を Gemini Enterprise Engine にバインドします。
# fetch mcp server agent registry resource name
export MCP_REGISTRY_URI=$(gcloud agent-registry mcp-servers list \
  --location=${REGION} \
  --project=${PROJ_ID} \
  --filter="displayName:${MCP_DOMAIN}" \
  --format="value(name)")

echo "mcp registry name: ${MCP_REGISTRY_URI}"
echo "mcp url: ${MCP_URL}"

データコネクタを作成する

# create custom mcp data connector with google identity oauth 2.0 and link to engine
curl -s -X POST "https://${GE_LOCATION}-discoveryengine.googleapis.com/v1alpha/projects/${PROJ_ID}/locations/${GE_LOCATION}:setUpDataConnector" \
  -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "X-Goog-User-Project: ${PROJ_ID}" \
  -H "Content-Type: application/json" \
  -d @- <<EOF
{
  "collectionId": "${MCP_NAME}-collection-$(date +%s)",
  "collectionDisplayName": "${MCP_NAME}-collection",
  "dataConnector": {
    "dataSource": "custom_mcp",
    "dataSourceVersion": 1,
    "params": {
      "oauth_access_token": "unused"
    },
    "refreshInterval": "86400s",
    "entities": [
      {
        "entityName": "mcp_data"
      }
    ],
    "connectorModes": [
      "FEDERATED"
    ],
    "actionConfig": {
      "isActionConfigured": true,
      "createBapConnection": true,
      "actionParams": {
        "auth_type": "OAUTH",
        "instance_uri": "${MCP_URL}",
        "auth_uri": "https://accounts.google.com/o/oauth2/v2/auth",
        "auth_uri_params": "&access_type=offline&prompt=consent",
        "token_uri": "https://oauth2.googleapis.com/token",
        "client_id": "${OAUTH_CLIENT_ID}",
        "client_secret": "${OAUTH_CLIENT_SECRET}",
        "scopes": "openid email profile",
        "client_secret_basic_override": true,
        "mcp_server_source": "REGISTRY_MCP",
        "registry_mcp_server_name": "${MCP_REGISTRY_URI}",
        "mcp_agent_instructions": "MANDATORY SPROCKETS INVENTORY & CATALOG AGENT: Always invoke get_sprockets or get_sprocket_by_id for any question about sprockets, inventory catalog listings, sprocket IDs (SPK-101 through SPK-105), ANSI models (ANSI-40, ANSI-60, ANSI-80), tooth counts, pitch, material, stock levels, unit prices, authenticated_user, or egress_source_ip verification.",
        "use_agent_gateway_egress": true,
        "agent_gateway_engine": "projects/${PROJ_ID}/locations/global/collections/default_collection/engines/${GE_APP_ID}"
      }
    }
  }
}
EOF

データ コネクタの作成を確認する

# fetch collection id
export GE_COLLECTION_ID=$(curl -s "https://${GE_LOCATION}-discoveryengine.googleapis.com/v1alpha/projects/${PROJ_ID}/locations/${GE_LOCATION}/collections" \
  -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "X-Goog-User-Project: ${PROJ_ID}" \
  | jq -r --arg dname "${MCP_NAME}-collection" '.collections[] | select(.displayName == $dname) | .name | split("/") | last' | head -n 1)

echo "ge collection id: ${GE_COLLECTION_ID}"

"registry_mcp_server_name" フィールドに MCP サーバーの Agent Registry UUID が入力され、"auth_type" が OAUTH になっていることを確認します。

# show data connector details
curl -s "https://${GE_LOCATION}-discoveryengine.googleapis.com/v1alpha/projects/${PROJ_ID}/locations/${GE_LOCATION}/collections/${GE_COLLECTION_ID}/dataConnector" \
  -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "X-Goog-User-Project: ${PROJ_ID}" \
  | jq '{name, state, actionState, connectorModes, bapConfig, auth_type: .actionConfig.actionParams.auth_type, registry_mcp_server_name: .actionConfig.actionParams.registry_mcp_server_name}'

Google Cloud コンソール UI で MCP サーバーのレジストリ エントリを表示します。

echo "mcp server registry page url: https://console.cloud.google.com/agent-platform/agent-registry/mcp-servers/${REGION}/${MCP_REGISTRY_URI##*/}/overview?project=${PROJ_ID}"

データコネクタを有効にする

# activate and bind data connector with google identity oauth 2.0
curl -s -X PATCH "https://discoveryengine.googleapis.com/v1alpha/projects/${PROJ_ID}/locations/global/collections/${GE_COLLECTION_ID}/dataConnector?updateMask=actionConfig" \
  -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "X-Goog-User-Project: ${PROJ_ID}" \
  -H "Content-Type: application/json" \
  -d @- <<EOF
{
  "name": "projects/${PROJ_ID}/locations/global/collections/${GE_COLLECTION_ID}/dataConnector",
  "actionConfig": {
    "isActionConfigured": true,
    "createBapConnection": true,
    "actionParams": {
      "auth_type": "OAUTH",
      "instance_uri": "${MCP_URL}",
      "auth_uri": "https://accounts.google.com/o/oauth2/v2/auth",
      "auth_uri_params": "&access_type=offline&prompt=consent",
      "token_uri": "https://oauth2.googleapis.com/token",
      "client_id": "${OAUTH_CLIENT_ID}",
      "client_secret": "${OAUTH_CLIENT_SECRET}",
      "scopes": "openid email profile",
      "client_secret_basic_override": true,
      "mcp_server_source": "REGISTRY_MCP",
      "registry_mcp_server_name": "${MCP_REGISTRY_URI}",
      "mcp_agent_instructions": "MANDATORY SPROCKETS INVENTORY & CATALOG AGENT: Always invoke get_sprockets or get_sprocket_by_id for any question about sprockets, inventory catalog listings, sprocket IDs (SPK-101 through SPK-105), ANSI models (ANSI-40, ANSI-60, ANSI-80), tooth counts, pitch, material, stock levels, unit prices, authenticated_user, or egress_source_ip verification.",
      "use_agent_gateway_egress": true,
      "agent_gateway_engine": "projects/${PROJ_ID}/locations/global/collections/default_collection/engines/${GE_APP_ID}"
    }
  }
}
EOF

カスタム MCP サーバーのリンクを確認する

# show engine (ge app) details
curl -s "https://${GE_LOCATION}-discoveryengine.googleapis.com/v1/projects/${PROJ_ID}/locations/${GE_LOCATION}/collections/default_collection/engines/${GE_APP_ID}" \
  -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "X-Goog-User-Project: ${PROJ_ID}" \
  | jq '{name: .name, dataStoreIds: .dataStoreIds, agentGatewaySetting: .agentGatewaySetting}'

リンクされたデータストア "dataStoreIds": ["mcp-sprockets-collection-[EPOCH]_mcp_data"] を確認します。

# show collection details
curl -s "https://${GE_LOCATION}-discoveryengine.googleapis.com/v1alpha/projects/${PROJ_ID}/locations/${GE_LOCATION}/collections" \
  -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "X-Goog-User-Project: ${PROJ_ID}" \
  | jq --arg app "${GE_APP_ID}" '.collections[] | select(.dataConnector.actionConfig.actionParams.agent_gateway_engine // "" | endswith($app)) | .dataConnector | {name: .name, state: .state, actionState: .actionState, connectorModes: .connectorModes, auth_type: .actionConfig.actionParams.auth_type}'

"state": "ACTIVE" と "auth_type": "OAUTH" を確認します。

ツールのアクション

Gemini Enterprise ダッシュボードで mcp-sprockets-collection データストアを検査すると、[アクション] タブが使用されておらず、[↻ カスタム アクションを再読み込み] ボタンが無効になっていることがわかります。これは想定された動作です。

Google Cloud コンソール UI でデータストアの詳細ページを表示します。

echo "data store details page url: https://console.cloud.google.com/gemini-enterprise/locations/${GE_LOCATION}/collections/${GE_COLLECTION_ID}/connector/details?project=${PROJ_ID}"

カスタム MCP サーバーを Gemini Enterprise に接続する方法に応じて、ツールの検出とガバナンスは次の 2 つの方法のいずれかで処理されます。

  • 直接カスタム MCP(BYO_MCP ワークフロー): Agent Registry を使用せずに Gemini Enterprise 内でカスタム MCP サーバーを直接構成すると、データストア自体がツール カタログ(connectorModes: ["FEDERATED", "ACTIONS"])を管理します。[アクション] タブを開き、[↻ カスタム アクションを再読み込み] をクリックして tools/list スキーマを取得し、UI で個々のツール(add と subtract)を手動でオンまたはオフに切り替える必要があります。
  • Agent Registry のインポート(REGISTRY_MCPこの Codelab で使用される ワークフロー): Agent Registry から MCP サーバーをインポートすると、Agent Registry は MCP エンドポイント、そのインターフェース メタデータ、ツール カタログ(connectorModes: ["FEDERATED"])の信頼できる唯一の情報源として機能します。Gemini Enterprise は、エンジン Agent Gateway の設定を通じて、登録された MCP ツールをランタイム時に自動的に有効にします。データストア UI で手動で再読み込みしたり、アクションを切り替えたりする必要はありません。

Gemini Enterprise の説明は以上です。次は検証セクションに進みます。

9. 検証

このセクションでは、1 回限りの Google OAuth 2.0 ユーザーの同意フローを完了し、Gemini Enterprise から外部 mcp-sprockets MCP サーバーで MCP ツールを呼び出し、外部 MCP サーバーが予約済みの Cloud NAT 静的 IP(egress_source_ip)と Google OAuth 2.0 ID(authenticated_user)の両方を検証したことを確認します。

ユーザー アクセス

Gemini Enterprise ウェブアプリの URL を作成します。

# fetch app user url
export GE_WIDGET_ID=$(curl -s "https://${GE_LOCATION}-discoveryengine.googleapis.com/v1/projects/${PROJ_ID}/locations/${GE_LOCATION}/collections/default_collection/engines/${GE_APP_ID}/widgetConfigs/default_search_widget_config" \
  -H "Authorization: Bearer $(gcloud auth print-access-token)" \
  -H "X-Goog-User-Project: ${PROJ_ID}" \
  -H "Content-Type: application/json" \
  | jq -r '.configId')

export GE_APP_USER_URL="https://vertexaisearch.cloud.google.com/home/cid/${GE_WIDGET_ID}"

echo "app user url: ${GE_APP_USER_URL}"

エージェントにクエリを実行する前に、Gemini Enterprise が Google ID(${USER_IDENTITY})のアクセス トークンを取得する権限を付与する OAuth 2.0 同意フローを 1 回完了する必要があります。これにより、Gemini Enterprise は Agent Gateway を介してアウトバウンド ツール呼び出しを認証できるため、外部 mcp-sprockets サーバーはリクエストごとに ID を検証できます。

  1. ブラウザで ${GE_APP_USER_URL} を開き、[開始] をクリックします。
  2. チャット ボックスで、[コネクタ] アイコン(パズルのピースのアイコン)をクリックし、mcp-sprockets-collection の横にある [承認] をクリックします。
  3. 新しいウィンドウが開き、続行するアカウントを選択mcp-sprockets-oauth(構成した OAuth 同意画面のアプリ名)するよう求められます。ユーザー アカウント(${USER_IDENTITY})を選択します。
  4. Google OAuth 同意画面で、Google(ID プロバイダ)がユーザー ID(openid、email、profile)を mcp-sprockets-oauth と共有することを許可するリクエストされた権限を確認し、[続行] をクリックします。
  5. [コネクタ] アイコンをもう一度クリックし、mcp-sprockets-collection の切り替えが有効になっていることを確認して、承認が成功したことを確認します。

エージェントのクエリをテストする

アシスタント チャットの UI テスト

チャットで次のテストクエリを送信します。

look up sprocket SPK-105 and confirm the model, material, unit_price, authenticated_user, and egress_source_ip

チャットで 2 つ目のクエリを試します。

list all sprockets in the inventory catalog and report the authenticated_user and egress_source_ip

アシスタントが正しい回答を返し、インタラクティブなアクション引用バッジを表示することを確認します。

たとえば、各回答の下にある「Sprocket Lookup SPK-105 (5s)MCP ツールのアイコンAgent Gatewayチェックマーク アイコン」は、ツールが実行されたことを示しています。

アシスタントが、予約済みの ${NAT_EXT_IP} に一致する "egress_source_ip" と、${USER_IDENTITY} に一致する "authenticated_user" とともに、スプロケットの仕様を返していることを確認します。

アシスタント streamAssist API テスト

ユーザー アカウントが OAuth 2.0 コネクタを承認すると、CLI から default_assistant:streamAssist API を直接呼び出し、外部 mcp-sprockets サーバーが Cloud NAT 静的 IP(${NAT_EXT_IP})と検証済みの Google OAuth 2.0 ID(${USER_IDENTITY})の両方を返すことを確認することもできます。

次のテストクエリを送信します。

# query engine (ge app) streamAssist
curl -s -X POST "https://${GE_LOCATION}-discoveryengine.googleapis.com/v1alpha/projects/${PROJ_ID}/locations/${GE_LOCATION}/collections/default_collection/engines/${GE_APP_ID}/assistants/default_assistant:streamAssist" \
  -H "Authorization: Bearer $(gcloud auth print-access-token)" \
  -H "X-Goog-User-Project: ${PROJ_ID}" \
  -H "Content-Type: application/json" \
  -d @- <<EOF | jq -r '.[].answer.replies[]?.groundedContent?.content?.text // empty'
{
  "query": {
    "text": "look up sprocket SPK-103 and confirm the model, material, unit_price, authenticated_user, and egress_source_ip"
  },
  "toolsSpec": {
    "vertexAiSearchSpec": {
      "dataStoreSpecs": [
        {
          "dataStore": "projects/${PROJ_NO}/locations/${GE_LOCATION}/collections/default_collection/dataStores/${GE_COLLECTION_ID}_mcp_data"
        }
      ]
    }
  }
}
EOF

出力例:

Here are the details for sprocket SPK-103 from the inventory catalog:
- Model: ANSI-60-16T
- Material: 304 Stainless Steel
- Unit Price: $42.00
- Authenticated User (Google OAuth 2.0): user@example.com
- Egress Source IP (Cloud NAT): 203.0.113.10

別のクエリを試してみます。

# query engine (ge app) streamAssist
curl -s -X POST "https://${GE_LOCATION}-discoveryengine.googleapis.com/v1alpha/projects/${PROJ_ID}/locations/${GE_LOCATION}/collections/default_collection/engines/${GE_APP_ID}/assistants/default_assistant:streamAssist" \
  -H "Authorization: Bearer $(gcloud auth print-access-token)" \
  -H "X-Goog-User-Project: ${PROJ_ID}" \
  -H "Content-Type: application/json" \
  -d @- <<EOF | jq -r '.[].answer.replies[]?.groundedContent?.content?.text // empty'
{
  "query": {
    "text": "find a sprocket model i can get for under \$20 and report the details, authenticated_user, and egress_source_ip"
  },
  "toolsSpec": {
    "vertexAiSearchSpec": {
      "dataStoreSpecs": [
        {
          "dataStore": "projects/${PROJ_NO}/locations/${GE_LOCATION}/collections/default_collection/dataStores/${GE_COLLECTION_ID}_mcp_data"
        }
      ]
    }
  }
}
EOF

Cloud Logging でログを検査する

Cloud Logging でログを調べて、Gemini Enterprise が Agent Gateway、PSC ネットワーク アタッチメント、Cloud NAT を介してツール呼び出しをルーティングしたことを確認します。

Agent Gateway のログを確認する

Agent Gateway がリクエストをインターセプトし、Agent Registry でターゲットを解決し、認可ポリシーを評価して、ツール呼び出しを転送したことを確認します。

# show agent gateway logs
gcloud logging read \
  "resource.type=networkservices.googleapis.com/Gateway
   AND resource.labels.location=${REGION}" \
  --project=${PROJ_ID} \
  --limit=8 \
  --format="table(
    timestamp.date('', tz=LOCAL):label=TIMESTAMP,
    httpRequest.status:label=STATUS,
    httpRequest.serverIp:label=SERVER_IP,
    jsonPayload.agentGatewayInfo.mcpInfo.method:label=MCP_METHOD,
    jsonPayload.agentGatewayInfo.mcpInfo.parameter:label=TOOL,
    jsonPayload.authzPolicyInfo.result:label=AUTHZ,
    jsonPayload.agentGatewayInfo.agentRegistryResource.basename():label=REGISTRY_MCP
  )"

出力例:

TIMESTAMP                STATUS  SERVER_IP         MCP_METHOD                 TOOL                AUTHZ    REGISTRY_MCP
YYYY-MM-DDTHH:MM:SS.ZZZ  200     203.0.113.20:443  tools/call                 get_sprocket_by_id  ALLOWED  agentregistry-00000000-0000-0000-5c39-1f6e542bb2b8
YYYY-MM-DDTHH:MM:SS.ZZZ  200     203.0.113.20:443  tools/list                                     ALLOWED  agentregistry-00000000-0000-0000-5c39-1f6e542bb2b8
YYYY-MM-DDTHH:MM:SS.ZZZ  202     203.0.113.20:443  notifications/initialized                      ALLOWED  agentregistry-00000000-0000-0000-5c39-1f6e542bb2b8
YYYY-MM-DDTHH:MM:SS.ZZZ  200     203.0.113.20:443  initialize                                     ALLOWED  agentregistry-00000000-0000-0000-5c39-1f6e542bb2b8

出力に次の内容が含まれていることを確認します。

  • STATUS: 200(実行成功)と 202(notifications/initialized ハンドシェイク)。
  • SERVER_IP: 外部 MCP サーバーの IP とポート(${MCP_EXT_IP}:443)。
  • MCP_METHOD、TOOL: MCP プロトコル シーケンス(initialize、notifications/initialized、tools/list、tools/call(get_sprocket_by_id を含む))。
  • AUTHZ: ALLOWED(IAP 認可で許可された下り(外向き))。
  • REGISTRY_MCP: 解決された Agent Registry リソース ID(agentregistry-...)。

Cloud NAT ログを確認する

${MCP_EXT_IP}:443 に接続するときに、Cloud NAT が Agent Gateway PSC ネットワーク アタッチメント IP(10.10.10.2)から予約済みの静的外部 IPv4 アドレス(${NAT_EXT_IP})にアウトバウンド接続を変換したことを確認します。

# show cloud nat logs
gcloud logging read \
  "resource.type=nat_gateway
   AND resource.labels.gateway_name=nat-${SLUG}-${REGION}
   AND jsonPayload.connection.dest_ip=${MCP_EXT_IP}" \
  --project=${PROJ_ID} \
  --limit=8 \
  --format="table(
    timestamp.date('', tz=LOCAL):label=TIMESTAMP,
    jsonPayload.allocation_status:label=ALLOCATION_STATUS,
    jsonPayload.connection.src_ip:label=SRC_IP,
    jsonPayload.connection.nat_ip:label=NAT_IP,
    jsonPayload.connection.dest_ip:label=DEST_IP,
    jsonPayload.connection.dest_port:label=PORT
  )"

出力例:

TIMESTAMP                ALLOCATION_STATUS  SRC_IP      NAT_IP        DEST_IP       PORT
YYYY-MM-DDTHH:MM:SS.ZZZ  OK                 10.10.10.2  203.0.113.10  203.0.113.20  443

ファイアウォール ログを確認する

内部ファイアウォール ポリシー(fw-policy-${SLUG}-internal)で、Agent Gateway からインターネット宛先への下り(外向き)トラフィックが許可されていることを確認します。

# show firewall policy logs (internal)
gcloud logging read \
  "logName:compute.googleapis.com%2Ffirewall
   AND jsonPayload.rule_details.reference:fw-policy-${SLUG}-internal" \
  --project=${PROJ_ID} \
  --limit=8 \
  --format="table(
    timestamp.date('', tz=LOCAL):label=TIMESTAMP,
    jsonPayload.connection.src_ip:label=SRC_IP,
    jsonPayload.connection.dest_ip:label=DEST_IP,
    jsonPayload.connection.dest_port:label=PORT,
    jsonPayload.vpc.vpc_name:label=VPC_NETWORK,
    jsonPayload.rule_details.reference.basename():label=POLICY,
    jsonPayload.rule_details.priority:label=RULE,
    jsonPayload.rule_details.direction:label=DIRECTION,
    jsonPayload.disposition:label=DISPOSITION
  )"

外部ファイアウォール ポリシー(fw-policy-${SLUG}-external)で、内部ネットワークのパブリック NAT IP アドレスから MCP サーバーへの上り(内向き)トラフィックが許可されていることを確認します。

# show firewall policy logs (external)
gcloud logging read \
  "logName:compute.googleapis.com%2Ffirewall
   AND jsonPayload.rule_details.reference:fw-policy-${SLUG}-external" \
  --project=${PROJ_ID} \
  --limit=8 \
  --format="table(
    timestamp.date('', tz=LOCAL):label=TIMESTAMP,
    jsonPayload.connection.src_ip:label=SRC_IP,
    jsonPayload.connection.dest_ip:label=DEST_IP,
    jsonPayload.connection.dest_port:label=PORT,
    jsonPayload.vpc.vpc_name:label=VPC_NETWORK,
    jsonPayload.rule_details.reference.basename():label=POLICY,
    jsonPayload.rule_details.priority:label=RULE,
    jsonPayload.rule_details.direction:label=DIRECTION,
    jsonPayload.disposition:label=DISPOSITION
  )"

これで検証部分は終了です。次は、クリーンアップ セクションに進みます。

10. クリーンアップ

このラボで作成したリソースと構成を削除する手順は次のとおりです。

Gemini Enterprise コンポーネントを削除する

# delete gemini enterprise engine (app)
curl -s -X DELETE "https://${GE_LOCATION}-discoveryengine.googleapis.com/v1/projects/${PROJ_ID}/locations/${GE_LOCATION}/collections/default_collection/engines/${GE_APP_ID}" \
  -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "X-Goog-User-Project: ${PROJ_ID}"

# delete custom mcp collection, data connector, and backing data store
curl -s -X DELETE "https://${GE_LOCATION}-discoveryengine.googleapis.com/v1alpha/projects/${PROJ_ID}/locations/${GE_LOCATION}/collections/${GE_COLLECTION_ID}" \
  -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "X-Goog-User-Project: ${PROJ_ID}"

# reset identity provider configuration
curl -s -X PATCH "https://${GE_LOCATION}-discoveryengine.googleapis.com/v1/projects/${PROJ_ID}/locations/${GE_LOCATION}/aclConfig" \
  -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "X-Goog-User-Project: ${PROJ_ID}" \
  -H "Content-Type: application/json" \
  -d '{"idpConfig":{"idpType":"IDP_TYPE_UNSPECIFIED"}}'

OAuth 2.0 クライアント認証情報(ge-mcp-sprockets-client)を削除するには、Google Auth Platform Clients ページを開き、ge-mcp-sprockets-client の横にある [削除](ゴミ箱)アイコンをクリックします。

# print google auth platform clients url
echo "https://console.cloud.google.com/auth/clients?project=${PROJ_ID}"

MCP サーバーと Agent Registry コンポーネントを削除する

# delete external mcp server vm
gcloud -q compute instances delete vm-${MCP_NAME} --zone=${ZONE}

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

# delete agent registry oauth2 endpoint
gcloud -q agent-registry services delete oauth2-googleapis --location=${REGION}

Agent Gateway コンポーネントを削除する

# delete gateway authz policy and iap service extension
gcloud -q network-security authz-policies delete ${AGW_NAME}-authz-policy-iap \
  --location=${REGION}

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

# delete agent gateway and connectivity template
gcloud -q network-services agent-gateways delete ${AGW_NAME} \
  --location=${REGION}

gcloud -q network-services agent-connectivity-templates delete act-${AGW_NAME} \
  --location=${REGION}
# delete iam unified access policy binding and access policy
gcloud -q iam policy-bindings delete uap-bind-${SLUG} \
  --location=global \
  --project=${PROJ_ID}

gcloud -q iam access-policies delete uap-policy-${SLUG} \
  --location=global \
  --project=${PROJ_ID}

ファイアウォール コンポーネントを削除する

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

gcloud -q compute network-firewall-policies associations delete \
  --name=fw-policy-bind-${SLUG}-external \
  --firewall-policy=fw-policy-${SLUG}-external \
  --global-firewall-policy

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

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

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

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

ネットワーク コンポーネントを削除する

# delete cloud nat gateway and cloud router
gcloud -q compute routers nats delete nat-${SLUG}-${REGION} \
  --router=cr-nat-${SLUG}-${REGION} \
  --region=${REGION}

gcloud -q compute routers delete cr-nat-${SLUG}-${REGION} \
  --region=${REGION}

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

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

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

# delete reserved static external ip addresses
gcloud -q compute addresses delete ip-${SLUG}-nat ip-${SLUG}-mcp \
  --region=${REGION}

# delete internal and external vpc networks
gcloud -q compute networks delete \
  vnet-${SLUG}-internal \
  vnet-${SLUG}-external

組織のポリシーのオーバーライドとローカル ファイルを削除する

# delete project-level organization policy overrides
for policy in \
  discoveryengine.managed.disableCustomMcpServerConnector \
  discoveryengine.managed.allowedEgressFqdns \
  discoveryengine.managed.allowedDataSources \
  iam.managed.disableAccessPolicyBinding \
  compute.vmExternalIpAccess; do
  gcloud -q org-policies delete ${policy} --project=${PROJ_ID} 2>/dev/null || true
done

# remove local project files
rm -rf cfg

これでクリーンアップ作業は完了です。次はまとめに進みます。

11. まとめ

おめでとうございます!Gemini Enterprise appが、外部 IP 許可リストに登録され、OAuth 2.0 で保護されたカスタム MCP サーバー上のツールを安全に検出して呼び出すことができるエンドツーエンドのアーキテクチャを構築しました。

  • 静的 IP VPC 下り(外向き)(ALL_TRAFFIC と Cloud NAT): すべてのアウトバウンド Agent Gateway トラフィックを PSC ネットワーク アタッチメント(10.10.10.2)経由で転送し、Cloud NAT を使用してインターネット宛てのリクエストを予約済みの静的外部 IPv4 アドレス(${NAT_EXT_IP})に変換しました。
  • OAuth 2.0 MCP リソース サーバーと Agent Registry: 厳格な送信元 IP ファイアウォール許可リスト(${NAT_EXT_IP}/32)と Google OAuth 2.0 tokeninfo 検証で保護された外部ストリーミング可能な HTTP FastMCP サーバー(mcp-sprockets)をデプロイし、そのエンドポイントとツール スキーマ(get_sprockets と get_sprocket_by_id)を Agent Registry に登録しました。
  • Gemini Enterprise OAuth 2.0 の統合とガバナンス: Gemini Enterprise アプリをプロビジョニングし、アウトバウンド ツール トラフィックを Agent Gateway にバインドし、登録済みの MCP サーバーを OAuth 2.0 REGISTRY_MCP データコネクタとして接続し、Cloud Logging テレメトリーとともに egress_source_ip と authenticated_user の両方を確認しました。

cosmopup

Cosmopup は Codelab が大好きです。

次のステップ

ご意見、ご質問、修正のご提案は、こちらのフィードバック フォームからお気軽にお寄せください。

ありがとうございました