1. 소개
이 Codelab에서는 agent-to-anywhere (이그레스) 모드에서 Agent Gateway를 사용하여 Gemini Enterprise의 고정 IP 이그레스 연결을 살펴봅니다. 연결 템플릿 구성을 사용하여 Agent Gateway를 통해 모든 아웃바운드 트래픽을 라우팅하여 엄격한 소스 IP 허용 목록과 OAuth 2.0 인증으로 보호되는 맞춤 모델 컨텍스트 프로토콜 (MCP) 서버를 호출하도록 Gemini Enterprise 앱을 구성합니다. Private Service Connect (PSC) 네트워크 연결은 Agent Gateway를 VPC 네트워크에 연결하고 Cloud NAT는 인터넷 이그레스 트래픽에 안정적인 외부 IP 주소를 제공합니다.
기본적으로 Gemini Enterprise에서 공개 인터넷 엔드포인트로의 아웃바운드 연결은 단일 고객 소유 IP 허용 목록 규칙에 고정할 수 없는 동적 공유 Google IP 주소 풀에서 시작됩니다. ALL_TRAFFIC 모드에서 Agent Gateway를 AgentConnectivityTemplate와 페어링하면 모든 아웃바운드 에이전트 트래픽이 VPC 네트워크로 라우팅됩니다. 여기서 Cloud NAT는 인터넷 바운드 연결을 사용자가 제어하는 예약된 고정 외부 IPv4 주소로 변환하는 동시에 Agent Registry 및 Identity-Aware Proxy (IAP)를 통해 중앙 집중식 MCP 도구 거버넌스를 유지합니다.
빌드할 항목
- Agent Connectivity Template (
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 Bearer 토큰을 확인하는 외부 스트리밍 가능 HTTP MCP 리소스 서버 (mcp-sprockets) - Google 인증 플랫폼 OAuth 2.0 동의 화면 및 웹 애플리케이션 OAuth 2.0 클라이언트 사용자 인증 정보 (
accounts.google.com및oauth2.googleapis.com/token) - Agent Registry MCP 서버 카탈로그 항목 및 도구 사양 (
toolspec.json) - IAP (Identity-Aware Proxy) 승인 확장 프로그램 및 Agent Gateway용 게이트웨이 승인 정책
- Agent Gateway에 바인딩되고 Agent Registry에서 가져온 OAuth 2.0 맞춤 MCP 서버 데이터 스토어에 연결된 Gemini Enterprise 앱
그림 1. Codelab 아키텍처
학습 내용
- 모든 아웃바운드 Agent Gateway 트래픽을 VPC 네트워크로 라우팅하기 위해
vpcEgress: ALL_TRAFFIC로AgentConnectivityTemplate를 구성하는 방법 - PSC 네트워크 연결 서브넷에서 고정 외부 IPv4 주소를 예약하고 Cloud NAT에 바인딩하는 방법
- Google OAuth 2.0 베어러 토큰 (
oauth2.googleapis.com/tokeninfo)을 확인하고 수신 방화벽을 Cloud NAT 고정 IP (${NAT_EXT_IP}/32)로 엄격하게 제한하는 스트리밍 가능한 외부 HTTP MCP 리소스 서버를 배포하는 방법 - Google 아이덴티티 OAuth 2.0 클라이언트 사용자 인증 정보를 구성하고 Agent Registry에 MCP 서버와 해당 도구 스키마를 등록하는 방법
- 등록된 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 이그레스 경로와 게이트웨이 컨트롤이 작동하도록 합니다.
- 네트워크 인프라: 고정 외부 IPv4 주소를 예약하고 이그레스 VPC, PSC 연결 서브넷, PSC 네트워크 연결, Cloud Router, Cloud NAT 게이트웨이, 외부 MCP 네트워크 방화벽 규칙을 프로비저닝합니다.
- 에이전트 연결 템플릿 및 Agent Gateway: Agent Registry 통합 (
registries)을 사용하여 이그레스 모드 (AGENT_TO_ANYWHERE)로AgentConnectivityTemplate(vpcEgress: ALL_TRAFFIC) 및 지역AgentGateway을 배포합니다. - 승인 정책: IAP 승인 확장 프로그램 (
AuthzExtension), 게이트웨이 승인 정책 (AuthzPolicy), IAM v3 통합 액세스 정책 (AccessPolicy및PolicyBinding)을 구성합니다. - MCP 서버 배포 및 등록: Caddy TLS 뒤에 외부
mcp-sprocketsOAuth 2.0 리소스 서버를 배포하고, Google OAuth 2.0tokeninfo적용을 확인하고, 수신 방화벽을 Cloud NAT 고정 IP (${NAT_EXT_IP}/32)로 엄격하게 잠그고, 서비스 및 도구 사양 (get_sprockets및get_sprocket_by_id)을 Agent Registry에 등록합니다. - Gemini Enterprise 앱 및 OAuth 2.0 클라이언트: Google ID 제공업체, 무료 라이선스, OAuth 2.0 동의 화면 및 웹 애플리케이션 클라이언트 사용자 인증 정보 (
OAUTH_CLIENT_ID및OAUTH_CLIENT_SECRET)를 구성하고, Gemini Enterprise 앱 (Engine)을 만들고, 아웃바운드 이그레스를 에이전트 게이트웨이 (agentGatewaySetting)에 바인딩합니다. - 맞춤 MCP 데이터 커넥터 가져오기: 등록된 MCP 서버 지원 데이터 스토어를 Gemini Enterprise 앱에 연결하기 위해
auth_type: "OAUTH"로 구성된REGISTRY_MCP데이터 커넥터 (:setUpDataConnector)를 만들고 활성화합니다. - 검증: 일회성 사용자 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의 구성요소 | 책임 |
리소스 소유자 | 최종 사용자 ( | Gemini Enterprise 웹 앱에 로그인하고 일회성 3단계 OAuth 2.0 승인 흐름 중에 동의를 부여합니다. |
OAuth 2.0 클라이언트 | Gemini Enterprise |
|
승인 서버 | Google 아이덴티티 OAuth 2.0 ( | 브라우저 동의 리디렉션 중에 사용자를 인증하고 OAuth 2.0 액세스 및 새로고침 토큰을 Gemini Enterprise에 발급합니다. |
리소스 서버 | 외부 | VPC 방화벽 ( |
브라우저 동의 리디렉션 (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로 엄격하게 잠글 수 있습니다.
Discovery Engine 리소스 모델
Gemini Enterprise 앱 (Discovery Engine API에서 Engine 리소스로 표시됨)은 최종 사용자를 위한 중앙 조정 레이어이자 대화형 인터페이스입니다. 사용자 채팅 세션을 관리하고, 엔터프라이즈 데이터에 생성형 모델을 기반으로 하며, 동적 도구 실행을 조정합니다.
Gemini Enterprise 앱은 데이터 스토어를 통해 데이터 및 시스템과 상호작용합니다.
- 지식 데이터 스토어: 검색 증강 생성 (RAG)을 위해 정적 콘텐츠 (예: Cloud Storage, Google Drive, BigQuery)를 수집하고 색인을 생성합니다.
- 데이터 커넥터 (작업 제공업체): 동적 서드 파티 또는 맞춤 API에 연결합니다. 맞춤 MCP 서버 데이터 스토어는 모델 컨텍스트 프로토콜 (MCP)로 정의된 도구를 노출하여 모델이 대화 중에 외부 함수를 동적으로 호출할 수 있도록 합니다.
Gemini Enterprise 이그레스
Engine의 agentGatewaySetting와 DataConnector의 use_agent_gateway_egress: true가 모두 구성된 경우 Gemini Enterprise는 맞춤 MCP 서버 도구 요청을 Agent Gateway로 라우팅합니다.
그림 2. Gemini Enterprise 이그레스 아키텍처
Gemini Enterprise 앱은 다음 4가지 주요 영역에서 도구 라우팅을 정리합니다.
- 위젯 (
default_search_widget_config):- 웹 클라이언트 인터페이스를 제공합니다. 위젯은 사용자로부터 프롬프트를 수신하고, 도구에 승인이 필요한 경우 일회성 OAuth 2.0 사용자 동의 리디렉션을 트리거하며, 기본 엔진과의 채팅 세션을 시작합니다.
- 핵심 어시스턴트 (
assistants/default_assistant/agents/default/core_assistant):- 엔진 내의 루트 대화형 추론 에이전트입니다. 사용자 쿼리를 평가할 때 핵심 어시스턴트는 사용 가능한 도구를 검사하고 합성된 Agent Gateway 하위 에이전트에 실행을 위임합니다.
- 데이터 스토어 및 데이터 커넥터:
DataStore::setUpDataConnector이 실행될 때 전용Collection내에 프로비저닝되며, 가져온 Agent Registry 도구 스키마 (get_sprockets,get_sprocket_by_id), 인수 유형, 에이전트 명령어를 Gemini EnterpriseEngine에 연결 (dataStoreIds)합니다.DataConnector: 원격 MCP 서버 (instance_uri)에 대한REGISTRY_MCP작업 연결 (createBapConnection: true)을 관리하고, 에이전트 레지스트리 MCP 서버 리소스 (registry_mcp_server_name)를 확인하고, 사용자 볼트 OAuth 2.0 베어러 토큰 (Authorization: Bearer [access_token])을 연결하고, 에이전트 게이트웨이 이그레스 (use_agent_gateway_egress: true)를 사용 설정합니다.
- 상담사 ID, 상담사 등록, 상담사 게이트웨이:
- 데이터 커넥터가 아웃바운드 도구 호출을 디스패치하면
agentGatewaySetting에 지정된 게이트웨이로 트래픽을 라우팅합니다. 코어 어시스턴트는 워크로드 아이덴티티principal://agents.global.org-.../agents/default/core_assistant를 어설션하는 SPIFFE 아이덴티티 토큰을 생성합니다. - 에이전트 게이트웨이는
registries필드를 사용하여 Agent Registry와 통합하여 대상 엔드포인트와 등록된 도구 스키마를 동적으로 확인하고, IAP를 사용하여 승인 정책을 평가하고, 허용된 요청 (사용자Authorization: Bearer헤더 유지)을AgentConnectivityTemplate를 사용하여 VPC 네트워크로 전달합니다.
- 데이터 커넥터가 아웃바운드 도구 호출을 디스패치하면
게이트웨이 VPC 연결 및 고정 IP NAT
Agent Gateway는 게이트웨이에서 agentConnectivityTemplate로 참조되는 AgentConnectivityTemplate 리소스를 사용하여 VPC 네트워크 연결을 지원합니다. 템플릿 egressNetworkConfig은 PSC 네트워크 연결을 통해 트래픽이 VPC로 들어오는 방식을 정의합니다.
PRIVATE_RANGES_ONLY(기본값): PSC 네트워크 연결을 통해 RFC 1918, RFC 6598, 클래스 E 비공개 IP 범위만 라우팅합니다. 인터넷 연결 트래픽은 VPC를 우회하여 Google 기본 인터넷 이그레스를 통해 직접 종료됩니다.ALL_TRAFFIC(이 Codelab에서 사용됨): PSC 네트워크 연결(10.10.10.2)을 통해 Agent Gateway의 모든 아웃바운드 트래픽(공용 인터넷 및 외부 파트너 MCP 엔드포인트 포함)을 VPC 서브넷으로 라우팅합니다.
ALL_TRAFFIC 모드로 작동하는 경우 PSC 연결 서브넷에는 다음 두 가지 구성이 필요합니다.
- 비공개 Google 액세스 (
--enable-private-ip-google-access): Google Cloud API (*.googleapis.com)에 대한 내부 호출이 Google 네트워크 내에서 비공개로 계속 라우팅되도록 합니다. - Cloud Router 및 Cloud NAT (
MANUAL_ONLY): PSC 연결 서브넷으로 들어오는 인터넷 바운드 트래픽은 예약된 정적 외부 IPv4 주소 (${NAT_EXT_IP})에 소스 네트워크 주소 변환 (SNAT)을 실행하는 Cloud NAT를 통해 기본 경로 (0.0.0.0/0)를 따릅니다. 따라서 외부 방화벽은 인바운드 액세스를${NAT_EXT_IP}/32로 엄격하게 제한할 수 있습니다.
제한사항 및 요구사항
- StreamableHTTP만 해당: 기존 서버 전송 이벤트 (SSE) 전송은 지원되지 않습니다. MCP 서버는 StreamableHTTP를 사용해야 합니다.
- 조직 정책 재정의: 데이터 커넥터를 등록하기 전에 맞춤 MCP 데이터 스토어의 조직 정책을 명시적으로 재정의해야 합니다.
- 지역 정렬: Gemini Enterprise는 앱 위치, Agent Gateway, Agent Registry 간의 지역 정렬을 적용합니다.
Gemini Enterprise 앱 위치 | Agent Gateway 및 NAT 위치 | 에이전트 레지스트리 위치 |
|
|
|
|
|
|
|
|
|
이로써 개념 부분이 마무리되었습니다. 다음은 설정 섹션입니다.
3. 설정
필요한 IAM 역할
이 Codelab을 완료하려면 다음 역할이 필요합니다.
도메인 | 필요한 IAM 역할 |
프로젝트 및 IAM |
|
네트워킹 및 게이트웨이 |
|
Gemini Enterprise 및 Registry |
|
워크로드 |
|
관측 가능성 |
|
또는 roles/orgpolicy.policyAdmin와 결합된 roles/owner와 같은 광범위한 기본 역할을 사용합니다 (roles/owner만으로는 조직 정책을 수정할 수 없음).
프로젝트 액세스
이 Codelab에서는 단일 Google Cloud 프로젝트를 사용합니다. 구성 단계에서는 gcloud CLI 및 Linux 셸 명령어를 사용합니다.
먼저 Google Cloud 프로젝트 명령줄에 액세스합니다.
shell.cloud.google.com의 Cloud ShellgcloudCLI가 설치된 로컬 터미널
프로젝트 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
gcloud cli 업데이트 (권장)
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 서비스 제어 (VPC-SC)가 활성화되어 있거나 프로젝트가enforcedProjects에 나열되어 있는 경우 데이터 커넥터의 아웃바운드 이그레스 도메인 (instance_uriFQDN)을 제한합니다.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용 리전 고정 외부 IPv4 주소 2개를 예약합니다.
# 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 네트워크 연결 만들기
에이전트 게이트웨이에서 연결을 자동으로 수락하도록 구성된 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는 VPC 네트워크로의 네트워크 이그레스를 위해 PSC 인터페이스를 프로비저닝하는 Agent Gateway 구성 (networkConfig.egress.networkAttachment)에서 참조됩니다.
# 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 네트워크에 두 개의 인그레스 방화벽 규칙을 만듭니다.
2001: Let's Encrypt가${MCP_DOMAIN}의 ACME HTTP-01 도메인 유효성 검사 챌린지를 완료할 수 있도록 인바운드 TCP 포트80을 허용합니다.2002: TLS 준비 상태와 OAuth 2.0tokeninfo적용을 확인하기 위해 처음에는0.0.0.0/0에서 인바운드 TCP 포트443를 허용하여 로컬 셸에서${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
이것으로 네트워크 부분이 마무리되었습니다. 다음으로 에이전트 게이트웨이 섹션으로 이동합니다.
5. Agent Gateway
이 섹션에서는 vpcEgress: ALL_TRAFFIC를 사용하여 에이전트 연결 템플릿을 배포하고, 리전별 Agent Gateway를 배포하고, Identity-Aware Proxy (IAP)에 승인을 위임하고, 아웃바운드 네트워크 요청을 관리하는 IAM 통합 액세스 정책 규칙을 정의합니다.
연결 템플릿 만들기
연결 템플릿 사양은 다음을 정의합니다.
egressNetworkConfig.networkAttachment: 에이전트 게이트웨이를 VPC 네트워크에 연결하는 PSC 네트워크 연결을 가리킵니다.egressNetworkConfig.vpcEgress: 모든 아웃바운드 에이전트 연결 (공용 인터넷 MCP 엔드포인트 포함)이 VPC 네트워크로 라우팅되고 Cloud NAT 소스 IP 주소 변환을 사용하여 종료되도록ALL_TRAFFIC로 설정합니다.
연결 템플릿 구성 파일을 만들고 가져옵니다.
# 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: 카탈로그에 등록된 MCP 엔드포인트와 도구 스키마를 확인하기 위해 게이트웨이를 리전별 Agent Registry와 연결합니다.
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는 IAP (Identity-Aware Proxy) 통합 액세스 정책 (UAP)과 통합된 승인 정책 (networksecurity.authzPolicies)을 사용하여 아웃바운드 도구 트래픽을 보호하고 관리합니다.
Agent Gateway는 기본적인 인라인 ALLOW 및 DENY 규칙을 지원하지만, 엔터프라이즈 환경에서는 중앙 집중식 ID 중심 거버넌스가 필요합니다. IAM 통합 액세스 정책 (또는 액세스 정책)을 사용하면 표준 IAM v3 액세스 정책을 사용하여 이그레스 액세스 규칙을 관리할 수 있습니다.
그림 3. 승인 아키텍처
승인 흐름은 다음 세 가지 구성요소를 연결합니다.
- 게이트웨이 승인 정책 (
authzPolicy):- Agent Gateway를 타겟팅하는 리전 리소스입니다.
- 모든 아웃바운드 승인 검사를 IAP 승인 확장 프로그램으로 라우팅하도록
policyProfile: REQUEST_AUTHZ및action: CUSTOM로 구성됩니다.
- IAP 서비스 확장 프로그램 (
authzExtension):- 요청 승인을 IAP (
iap.googleapis.com)에 위임하는 리전 리소스입니다. - 정책 버전
V2을 사용하여ENFORCE모드에서 정책을 평가합니다.
- 요청 승인을 IAP (
- 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
)"
승인 정책 배포
에이전트 게이트웨이를 타겟팅하고 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는 다음 에이전트 레지스트리 대상 속성에 대해 CEL 속성 표현식을 평가합니다.
- 등록된 상태 (
destination.is_registered):- 대상 항목이 Agent Registry에 등록되어 있는지 나타내는 불리언 (
true/false)입니다.
- 대상 항목이 Agent Registry에 등록되어 있는지 나타내는 불리언 (
- MCP 서버 이름 (
destination.agent_registry.mcp_server.name):- Agent Registry에 등록된 표준 MCP 서버 리소스 이름입니다.
- MCP 메서드 (
destination.agent_registry.mcp_server.method):- 호출되는 MCP 메서드 (예:
tools/call,tools/list,initialize)
- 호출되는 MCP 메서드 (예:
- 도구 이름 (
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 액세스 정책 바인딩
프로젝트의 모든 에이전트 게이트웨이에서 시행을 활성화하려면 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)를 호출할 때마다 서버는 다음을 실행합니다.
X-Forwarded-For헤더 (egress_source_ip)에서 호출자 소스 IP를 읽습니다.Authorization: Bearer [access_token]헤더를 추출하고 Google OAuth 2.0 확인 엔드포인트 (https://oauth2.googleapis.com/tokeninfo?access_token=[token])에 대해 토큰을 확인합니다."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 Bearer 토큰 (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 서버 부분이 마무리되었습니다. 다음은 에이전트 레지스트리 섹션입니다.
7. 에이전트 레지스트리
Agent Registry는 AI 생태계의 모든 에이전트, MCP 서버, 엔드포인트 (API)의 중앙 집중식 인벤토리입니다. 또한 AI 애플리케이션에서 사용할 수 있도록 등록된 다른 도구와 서비스를 나열, 검색, 탐색하는 데 사용할 수 있는 카탈로그이기도 합니다. Agent Gateway는 레지스트리 데이터 모델을 거버넌스 프레임워크로 사용하여 이그레스 액세스 제어를 적용합니다. 주 구성원 호출 에이전트에 대한 IAM 역할 부여는 레지스트리 리소스에 바인딩된 정책에 대해 확인됩니다.
이 섹션에서는 Agent Registry에 MCP 서버와 도구 스키마 (toolspec.json)를 등록합니다. Gemini Enterprise가 에이전트 게이트웨이를 통해 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}"
에이전트 레지스트리에서 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
이로써 에이전트 레지스트리 부분이 마무리되었습니다. 다음은 Gemini Enterprise 섹션입니다.
8. Gemini Enterprise
이 섹션에서는 ID 공급자, 무료 라이선스, Google 아이덴티티 OAuth 2.0 클라이언트 사용자 인증 정보를 구성하고, Gemini Enterprise 앱 (${GE_APP_ID})을 만들고, 아웃바운드 이그레스를 Agent Gateway (${AGW_URI})에 바인딩하고, 등록된 스프로킷 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 ID를 사용 설정합니다.
다음 메서드는 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 아이덴티티 프로바이더와 일치합니다.
(선택사항) Gemini Enterprise 체험 라이선스 사용 설정
Gemini Enterprise 라이선스가 할당된 프로젝트를 사용하는 경우 이 단계를 건너뛸 수 있습니다. 라이선스 없이 새 프로젝트를 사용하는 경우 계속해서 다음 단계를 따르세요.
30일 동안 Gemini Enterprise 사용자 시트의 권한을 부여하는 라이선스 구성 리소스를 만듭니다. 이렇게 하면 기본 라이선스가 새 무료 체험으로 설정되므로 로그인하는 모든 사용자에게 자동으로 시트가 부여됩니다.
# 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을 확인합니다.
OAuth 2.0 동의 및 사용자 인증 정보 구성
Gemini Enterprise (OAuth 2.0 클라이언트)가 사용자 위임 Google OAuth 2.0 전달자 토큰을 획득하여 사용자를 대신해 mcp-sprockets리소스 서버에 제공할 수 있도록 Google 인증 플랫폼 OAuth 동의 화면을 구성하고 프로젝트에서 웹 애플리케이션 OAuth 2.0 클라이언트 ID를 만드세요.
1. OAuth 동의 화면 구성
프로젝트에서 Google 인증 플랫폼 개요의 직접 Google Cloud 콘솔 URL을 출력합니다.
# print google auth platform consent screen url
echo "https://console.cloud.google.com/auth/overview?project=${PROJ_ID}"
- 브라우저에서 URL을 열고 시작하기를 클릭합니다 (프로젝트에 아직 구성되지 않은 경우).
- 앱 정보에서 앱 이름을
mcp-sprockets-oauth로 설정하고 이메일 주소 (${USER_IDENTITY})를 사용자 지원 이메일로 선택한 다음 다음을 클릭합니다. - 대상에서 내부(Google Workspace 조직에 속한 계정인 경우 권장) 또는 외부(개인 계정 사용 시)를 선택하고(외부를 선택하는 경우 **대상 테스트 사용자**에
${USER_IDENTITY}추가) 다음을 클릭합니다. - 연락처 정보에 이메일 주소를 입력하고 정책 동의에 체크한 다음 만들기를 클릭합니다.
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}"
- URL을 열고 + 클라이언트 만들기를 클릭합니다.
- 애플리케이션 유형으로 웹 애플리케이션을 선택하고 이름을
ge-mcp-sprockets-client로 설정합니다. - 이 클라이언트는 AI 기반 에이전트에서 사용됩니다 체크박스를 선택하여 사용자를 대신하여 작동하는 AI 에이전트 워크플로의 OAuth 클라이언트를 지정합니다.
- 승인된 리디렉션 URI에서 + URI 추가를 클릭하고 Gemini Enterprise OAuth 콜백 URI를 추가합니다.
https://vertexaisearch.cloud.google.com/oauth-redirect
- 만들기를 클릭하고 생성된 클라이언트 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에 바인딩
Agent Gateway를 통해 Gemini Enterprise의 아웃바운드 트래픽을 라우팅하면 모든 AI 에이전트 도구 호출에 대해 중앙 집중식 적용 및 고정 IP 라우팅 경계가 설정됩니다.
- 중앙 집중식 정책 적용: 에이전트 게이트웨이는 트래픽이 에이전트 환경을 벗어나기 전에 승인 정책에 대해 아웃바운드 도구 요청을 평가하는 인라인 프록시 역할을 합니다.
- 결정적 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 아이덴티티 (https://accounts.google.com/o/oauth2/v2/auth 및 https://oauth2.googleapis.com/token)로 구성된 OAuth 2.0 맞춤 MCP 데이터 스토어를 만들어 mcp-sprockets 서버를 Gemini Enterprise에 연결합니다.
Discovery Engine API를 사용하는 경우 다음 두 단계로 이루어집니다.
- 생성 (
:setUpDataConnector): 전용Collection리소스 (${MCP_NAME}-collection-%timestamp)를 만들고,auth_type: "OAUTH"이 있는DataConnector(custom_mcp)를 연결하고, 지원DataStore(..._mcp_data)를 프로비저닝합니다. - 활성화 (
PATCH .../dataConnector?updateMask=actionConfig): Agent Registry 도구 사양, Google 아이덴티티 OAuth 2.0 매개변수를 사용하여 커넥터 작업 런타임 (actionState: "ACTIVE")을 활성화하고DataStore(dataStoreIds)을 Gemini EnterpriseEngine에 바인딩합니다.
# 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에 연결하는 방법에 따라 도구 검색 및 거버넌스는 다음 두 가지 방법 중 하나로 처리됩니다.
- 직접 맞춤 MCP (
BYO_MCP워크플로): Agent Registry 없이 Gemini Enterprise 내에서 직접 맞춤 MCP 서버를 구성하면 데이터 스토어 자체에서 도구 카탈로그 (connectorModes: ["FEDERATED", "ACTIONS"])를 관리합니다. 작업 탭을 열고 ↻ 맞춤 작업 다시 로드를 클릭하여tools/list스키마를 가져온 다음 UI에서 개별 도구 (add및subtract)를 수동으로 사용 설정 또는 사용 중지해야 합니다. - Agent Registry 가져오기 (이 Codelab에서 사용되는
REGISTRY_MCP워크플로): Agent Registry에서 MCP 서버를 가져오면 Agent Registry가 MCP 엔드포인트, 인터페이스 메타데이터, 도구 카탈로그 (connectorModes: ["FEDERATED"])의 신뢰할 수 있는 정보 소스 역할을 합니다. Gemini Enterprise는 데이터 스토어 UI에서 작업을 수동으로 다시 로드하거나 전환하지 않아도 엔진 Agent Gateway 설정을 통해 등록된 MCP 도구를 런타임에 자동으로 사용 설정합니다.
이로써 Gemini Enterprise 부분이 마무리되었습니다. 다음으로 유효성 검사 섹션으로 이동합니다.
9. 검증
이 섹션에서는 일회성 Google OAuth 2.0 사용자 동의 흐름을 완료하고, Gemini Enterprise에서 외부 mcp-sprockets 서버의 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}"
OAuth 동의 완료
에이전트를 쿼리하기 전에 Gemini Enterprise가 Google ID (${USER_IDENTITY})의 액세스 토큰을 획득할 수 있는 권한을 부여하는 일회성 OAuth 2.0 동의 흐름을 완료해야 합니다. 이를 통해 Gemini Enterprise는 Agent Gateway를 통해 아웃바운드 도구 호출을 인증할 수 있으므로 외부 mcp-sprockets 서버가 모든 요청에서 ID를 확인할 수 있습니다.
- 브라우저에서
${GE_APP_USER_URL}을 열고 시작하기를 클릭합니다. - 채팅창에서 커넥터 아이콘 (퍼즐 조각 아이콘)을 클릭하고
mcp-sprockets-collection옆에 있는 승인을 클릭합니다. - 계속할 계정을 선택하세요
mcp-sprockets-oauth(구성한 OAuth 동의 화면 앱 이름)이라는 메시지가 표시된 새 창이 나타납니다. 여기에서 사용자 계정 (${USER_IDENTITY})을 선택합니다. - Google OAuth 동의 화면에서 Google (ID 공급업체)이
mcp-sprockets-oauth와 사용자 ID (openid,email,profile)를 공유할 수 있도록 요청된 권한을 검토한 다음 계속을 클릭합니다. - 커넥터 아이콘을 다시 클릭하고
mcp-sprockets-collection전환 버튼이 사용 설정되어 있는지 확인하여 승인이 완료되었는지 확인합니다.
에이전트 쿼리 테스트
어시스턴트 채팅 UI 테스트
채팅에 다음 테스트 쿼리를 제출합니다.
look up sprocket SPK-105 and confirm the model, material, unit_price, authenticated_user, and egress_source_ip
채팅에서 두 번째 쿼리를 시도해 보세요.
list all sprockets in the inventory catalog and report the authenticated_user and egress_source_ip
어시스턴트가 올바른 답변을 반환하고 대화형 작업 인용 배지를 표시하는지 확인합니다.
예를 들어 각 대답 아래의 'Sprocket Lookup SPK-105 (5s)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가 에이전트 게이트웨이, 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 프로토콜 시퀀스 (get_sprocket_by_id이 있는initialize,notifications/initialized,tools/list,tools/call).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 인증 플랫폼 클라이언트 페이지를 열고 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 앱이 외부 IP 허용 목록에 추가되고 OAuth 2.0으로 보호되는 맞춤 MCP 서버에서 도구를 안전하게 검색하고 호출할 수 있는 엔드 투 엔드 아키텍처를 빌드했습니다.
- 고정 IP VPC 이그레스 (
ALL_TRAFFIC및 Cloud NAT): PSC 네트워크 연결 (10.10.10.2)을 통해 모든 아웃바운드 Agent Gateway 트래픽을 라우팅하고 Cloud NAT를 사용하여 인터넷 바운드 요청을 예약된 고정 외부 IPv4 주소 (${NAT_EXT_IP})로 변환했습니다. - OAuth 2.0 MCP 리소스 서버 및 에이전트 레지스트리: 엄격한 소스 IP 방화벽 허용 목록 (
${NAT_EXT_IP}/32) 및 Google OAuth 2.0tokeninfo인증으로 보호되는 외부 스트리밍 가능 HTTP FastMCP 서버 (mcp-sprockets)를 배포하고 에이전트 레지스트리에 엔드포인트 및 도구 스키마 (get_sprockets및get_sprocket_by_id)를 등록했습니다. - Gemini Enterprise OAuth 2.0 통합 및 거버넌스: Gemini Enterprise 앱을 프로비저닝하고, 아웃바운드 도구 트래픽을 Agent Gateway에 바인딩하고, 등록된 MCP 서버를 OAuth 2.0
REGISTRY_MCP데이터 커넥터로 연결하고, Cloud Logging 원격 분석과 함께egress_source_ip및authenticated_user를 모두 확인했습니다.

Cosmopup은 Codelabs가 아주 멋지다고 생각합니다.
다음 단계는 무엇인가요?
- 고급 기능과 튜토리얼은 Gemini Enterprise Agent Platform 문서를 참고하세요.
- 추가 AI 안전 및 보안을 위해 Agent Gateway에서 Model Armor 가드레일을 구성합니다.
- 시맨틱 거버넌스 정책을 살펴보고 자연어 쿼리에 대한 비즈니스 규칙과 규정 준수를 적용하세요.
이 의견 양식을 사용하여 의견, 질문 또는 수정사항을 자유롭게 제공해 주세요.
감사합니다.