1. 소개
이 Codelab에서는 Google Cloud Agent Registry 및 에이전트 개발 키트 (ADK)를 사용하여 런타임에 등록된 모델 컨텍스트 프로토콜 (MCP) 서버를 동적으로 검색하고 호출하여 하드코딩된 엔드포인트와 도구 구성을 제거하는 방법을 알아봅니다.
이 실습에서는 ADK와 Python 프로그래밍 언어를 사용하여 애플리케이션을 빌드합니다. 다른 프로그래밍 언어와 다른 에이전트 개발 프레임워크를 사용하는 경우에도 동일한 원칙을 적용할 수 있습니다.
실습할 내용
- 프로젝트에서 Google Cloud Agent Registry 사용 설정 및 구성
- Cloud Run에 MCP 서버를 배포하고 Agent Registry에 등록
- 하드코딩된 URL을 사용하여 MCP 서버를 호출하는 ADK 에이전트 빌드하기
- 에이전트 레지스트리에서 ID로 MCP 서버를 검색하도록 에이전트 변경
- 시맨틱 검색을 사용하여 런타임에 MCP 서버를 검색하도록 에이전트 변경
필요한 항목
- 최신 웹브라우저 (예: Chrome)
- 결제가 사용 설정된 Google Cloud 프로젝트
- Google Cloud 프로젝트에서 리소스를 만들고 관리하며 IAM 정책을 구성할 수 있는 권한이 있는 계정
2. 환경 설정
이 Codelab의 안내에서는 Google Cloud 콘솔의 Cloud Shell에서 명령어를 실행한다고 가정합니다. Cloud Shell은 Google Cloud에서 실행되는 명령줄 환경으로, 필요한 도구가 미리 로드되어 있습니다. 로컬 환경에 gcloud CLI, git, Python 3이 설치되어 있으면 로컬 환경에서 명령어를 실행할 수도 있습니다.
Google Cloud 프로젝트 만들기
- Google Cloud 콘솔의 프로젝트 선택기 페이지에서 Google Cloud 프로젝트를 만듭니다.
- Cloud 프로젝트에 결제가 사용 설정되어 있는지 확인합니다. 프로젝트에 결제가 사용 설정되어 있는지 확인하는 방법을 알아보세요.
Cloud Shell 터미널 시작
- 리소스 선택 도구를 사용하여 만든 Google Cloud 프로젝트를 선택합니다.
- Google Cloud 콘솔 상단에서 Cloud Shell 활성화
를 클릭합니다.
- Cloud Shell에 연결되면 인증을 확인합니다.
gcloud auth list - 프로젝트가 구성되었는지 확인합니다.
gcloud config get project - 프로젝트가 예상대로 설정되지 않았거나 Cloud Shell 대신 로컬 머신의 환경을 사용하는 경우 프로젝트 ID를 설정합니다.
export GOOGLE_CLOUD_PROJECT=<YOUR_PROJECT_ID> gcloud config set project "${GOOGLE_CLOUD_PROJECT}"
Google Cloud 프로젝트 선택 및 환경 변수 설정
- 필수 Google Cloud API를 사용 설정합니다.
gcloud services enable \ agentregistry.googleapis.com \ aiplatform.googleapis.com \ cloudbuild.googleapis.com \ run.googleapis.com \ --project="${GOOGLE_CLOUD_PROJECT}"
Python 환경 설정
- 가상 환경을 만들고 활성화하고 필요한 종속 항목을 설치합니다.
mkdir -p adk-dynamic-discovery && cd adk-dynamic-discovery python3 -m venv .venv source .venv/bin/activate pip install "fastmcp<5.0.0" "google-adk[mcp,a2a,agent-identity]>=2.5.0,<3.0.0"
3. 동물원 MCP 서버 만들기 및 등록하기
이 단계에서는 동물원 MCP 서버를 만들고 등록합니다. 서버는 세 가지 도구를 노출합니다. list_species는 동물원에 사는 모든 종을 나열하고, get_animals_by_species는 특정 종의 동물을 나열하며, get_animal_details는 특정 동물에 관한 추가 정보를 가져옵니다.
GitHub에서 이 단계의 모든 파일을 찾을 수 있습니다.
MCP 서버 만들기
- MCP 서버의 폴더 및 Python 프로젝트 설정
mkdir mcp-on-cloudrun && cd mcp-on-cloudrun uv init --description "Example of deploying an MCP server on Cloud Run" --bare --python 3.13 uv add "fastmcp<5.0.0" --no-sync - MCP 서버 코드를 다운로드합니다.
curl -fsSL https://raw.githubusercontent.com/GoogleCloudPlatform/devrel-demos/main/codelabs/agent-registry-dynamic-discovery-with-adk/steps/step3/server.py -o server.py
Cloud Run에 MCP 서버를 배포하고 Agent Registry에 등록합니다.
다음으로 서버의 Dockerfile를 만들고 서버를 Cloud Run에 배포합니다.
Dockerfile만들기:cat << EOF > Dockerfile # Use the official Python image FROM python:3.13-slim # Install uv COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/ # Install the project into /app COPY . /app WORKDIR /app # Allow statements and log messages to immediately appear in the logs ENV PYTHONUNBUFFERED=1 # Install dependencies RUN uv sync # Run the FastMCP server CMD ["uv", "run", "server.py"] EOF- 서버에 지정된 서비스 계정을 만듭니다.
gcloud iam service-accounts create zoo-mcp-server-sa \ --display-name="Zoo MCP Server Service Account" us-west1리전에 서버를 배포합니다. 다음 프롬프트가 표시되면 새 Artifact Registry 저장소 생성을 확인합니다.gcloud run deploy zoo-mcp-server \ --service-account=zoo-mcp-server-sa@$GOOGLE_CLOUD_PROJECT.iam.gserviceaccount.com \ --region=us-west1 \ --source=. \ --labels=dev-tutorial=codelab-dynamic-mcp-discovery \ --invoker-iam-check \ --no-allow-unauthenticatedus-west1리전의 프로젝트에 Artifact Registry 저장소가 없는 경우 다음 프롬프트가 표시됩니다.Deploying from source requires an Artifact Registry Docker repository to store built containers. A repository named [cloud-run-source-deploy] in region [us-west1] will be created. Do you want to continue (Y/n)?- '전역' 위치의 Agent Registry에 MCP 서버를 등록합니다.
이 명령어는 도구 매니페스트를 사용하여fastmcp inspect server.py --format mcp | jq 'del(..|nulls) | del(.tools[]?._meta?)' > toolspec.json MCP_URL=$(gcloud run services describe zoo-mcp-server --region=us-west1 --format='value(status.url)') gcloud agent-registry services create zoo-mcp-server \ --display-name="zoo-mcp-server" \ --project="${GOOGLE_CLOUD_PROJECT}" \ --location=global \ --mcp-server-spec-type="tool-spec" \ --mcp-server-spec-content="toolspec.json" \ --interfaces="url=${MCP_URL}/mcp,protocolBinding=jsonrpc"toolspec.json를 생성하고 Agent Registry API를 호출하여 서버를 등록합니다. - 에이전트가 레지스트리에 표시되는지 확인합니다.
다음과 같이 MCP 서버 정보가 포함된 YAML 출력이 표시됩니다.gcloud agent-registry mcp-servers list \ --location=global \ --filter="display_name=zoo-mcp-server"--- attributes: agentregistry.googleapis.com/system/RuntimeReference: uri: //agentregistry.googleapis.com/projects/[PROJECT_NUMBER]/locations/global/services/zoo-mcp-server createTime: '<create time datetime in ISO 8601>' displayName: zoo-mcp-server interfaces: - protocolBinding: JSONRPC url: https://zoo-mcp-server-[PROJECT_NUMBER].us-west1.run.app/mcp mcpServerId: urn:mcp:projects-[PROJECT_NUMBER]:projects:[PROJECT_NUMBER]:locations:global:agentregistry:services:zoo-mcp-server name: projects/[PROJECT_ID]/locations/global/mcpServers/agentregistry-00000000-0000-0000-1234-56789abcdef tools: - annotations: idempotentHint: true readOnlyHint: true description: |- Returns every species in the zoo. Use it first for zoo-wide questions such as counting all animals or listing everything the zoo has. name: list_species - annotations: idempotentHint: true readOnlyHint: true description: |- Retrieves all animals of a specific species from the zoo. Can also be used to collect the base data for aggregate queries of animals of a specific species - like counting the number of penguins or finding the oldest lion. name: get_animals_by_species - annotations: idempotentHint: true readOnlyHint: true description: Retrieves the details of a specific animal by its name. name: get_animal_details updateTime: '<last updated datetime in ISO 8601>'
4. 동물원 MCP 서버를 사용하는 기본 ADK 에이전트 만들기
이 단계에서는 하드코딩된 동물원 MCP 서버 URL을 사용하여 동물원에 관한 사용자의 요청에 답변하는 ADK 에이전트를 만듭니다. 하드코딩을 사용하면 에이전트가 배포되기 전에 MCP 서버의 URL을 알아야 합니다.
이 단계의 소스 파일은 GitHub에서 찾을 수 있습니다.
에이전트 애플리케이션 만들기
ADK 프로젝트와 동물원 MCP 서버를 사용하는 동물원 에이전트의 스캐폴드를 만듭니다.
- 동물원 에이전트의 폴더와 프로젝트를 설정합니다.
mkdir ../sample-agent && cd ../sample-agent uv init --description "Example of ADK agent to showcase dynamic MCP discovery" --bare --python 3.13 uv add "google-adk[mcp,a2a,agent-identity]>=2.5.0,<3.0.0" --no-sync - 에이전트 패키지 폴더를 만들고
__init__.py파일을 다운로드합니다.mkdir zoo_agent curl -fsSL https://raw.githubusercontent.com/GoogleCloudPlatform/devrel-demos/main/codelabs/agent-registry-dynamic-discovery-with-adk/steps/step4/zoo_agent/__init__.py -o zoo_agent/__init__.py .env파일을 만들고 이 단계와 다음 단계에서 사용되는 환경 변수로 채웁니다. 이 명령어는 이 단계와 후속 탐색 단계에서 필요한 모든 환경 변수를MCP_URL=$(gcloud run services describe zoo-mcp-server --region=us-west1 --format='value(status.url)') cat << EOF > zoo_agent/.env GOOGLE_GENAI_USE_ENTERPRISE=TRUE GOOGLE_CLOUD_LOCATION=global GOOGLE_CLOUD_PROJECT="${GOOGLE_CLOUD_PROJECT}" GOOGLE_CLOUD_PROJECT_NUMBER=$(gcloud projects describe ${GOOGLE_CLOUD_PROJECT} \ --format='value(projectNumber)') MCP_SERVER_URL="${MCP_URL}" EOFzoo_agent/.env에 작성하므로 나중에 파일을 다시 만들 필요가 없습니다. 이 단계에서는 다음 변수가 사용됩니다.GOOGLE_GENAI_USE_ENTERPRISE,GOOGLE_CLOUD_PROJECT,GOOGLE_CLOUD_LOCATION: ADK 및 Google GenAI SDK에서 암시적으로 사용하여 Google Cloud 프로젝트 및 위치 (global)의 Vertex AI를 통해 Gemini 모델 호출을 라우팅합니다. 또한 MCP 서버가 등록된 에이전트 레지스트리의 위치를 저장합니다.GOOGLE_CLOUD_PROJECT_NUMBER: 이 단계에서는 사용되지 않으며 다음 단계에서 사용하기 위해 여기에 자동 입력됩니다.MCP_SERVER_URL:zoo_agent/agent.py에서 직접 읽어 동물원 MCP 서버의 하드코딩된 Cloud Run URL로McpToolset를 구성합니다.
- 동물원 MCP 서버에 연결하는 동물원 에이전트의 코드가 포함된
agent.py를 다운로드합니다.curl -fsSL https://raw.githubusercontent.com/GoogleCloudPlatform/devrel-demos/main/codelabs/agent-registry-dynamic-discovery-with-adk/steps/step4/zoo_agent/agent.py -o zoo_agent/agent.py
MCP 서버 승인 작동 방식
zoo-mcp-server Cloud Run 서비스가 --invoker-iam-check 및 --no-allow-unauthenticated 플래그를 사용하여 배포되었으므로 MCP 서버에 대한 모든 HTTP 요청은 Authorization 헤더에 Google에서 서명한 ID 토큰을 포함해야 합니다. 토큰은 서비스에 대한 run.routes.invoke 권한이 있는 호출자를 식별해야 합니다.
zoo_agent/agent.py에서 _GoogleAuthHeaders 클래스는 두 가지 방법 중 하나로 ID 토큰 사용자 인증 정보를 가져옵니다. 먼저 fetch_id_token_credentials()를 호출하여 메타데이터 서버 또는 서비스 계정 키에서 서비스 URL의 ID 토큰을 가져옵니다. 실패하면 사용자 애플리케이션 기본 사용자 인증 정보 (ADC)의 ID 토큰을 사용합니다. 인스턴스가 호출될 때마다 필요한 경우 토큰을 새로고침하고 Authorization 헤더를 반환합니다.
class _GoogleAuthHeaders:
def __init__(self, audience: str):
self._request = Request()
self._id_token = False
try:
self._credentials = id_token.fetch_id_token_credentials(audience, request=self._request)
except exceptions.DefaultCredentialsError:
self._credentials, _ = google.auth.default()
self._id_token = True
if not isinstance(self._credentials, credentials.Credentials):
raise
def __call__(self, unused_context: ReadonlyContext | None = None) -> dict[str, str]:
if not self._credentials.valid or (self._id_token and not self._credentials.id_token):
self._credentials.refresh(self._request)
token = self._credentials.id_token if self._id_token else self._credentials.token
return {"Authorization": f"Bearer {token}"}
에이전트는 클래스의 인스턴스를 McpToolset에 header_provider 인수로 전달합니다. ADK는 헤더 제공자를 호출하고 반환된 헤더를 MCP 서버에 대한 모든 요청에 연결합니다. HTTP 클라이언트는 ADK에 의해 생성되므로 설치된 MCP Python SDK 및 HTTP 라이브러리 버전에 관계없이 동일한 코드가 작동합니다.
zoo_mcp_toolset = McpToolset(
connection_params=StreamableHTTPConnectionParams(
url=f"{MCP_SERVER_URL}/mcp",
timeout=30.0,
),
header_provider=_GoogleAuthHeaders(MCP_SERVER_URL),
)
요청된 대상은 서비스 URL (MCP_SERVER_URL)이고 연결 URL에는 FastMCP 서버가 MCP 프로토콜을 제공하는 /mcp 경로가 포함됩니다. 30초 제한 시간은 Cloud Run 콜드 스타트를 위한 여유를 제공합니다.
로컬 환경에서 ADK 플레이그라운드를 사용하여 에이전트 테스트
에이전트를 테스트하기 전에 사용자 계정이 MCP 서버를 호출할 수 있는지 확인하세요.
- 로컬 터미널만 해당 (Cloud Shell에서는 이 단계 건너뛰기): 사용자 인증 정보로 ADC를 초기화합니다.
안내에 따라 ADC 초기화를 완료합니다.gcloud auth application-default login - (선택사항) 사용자 계정에 원격 MCP 서버를 호출할 수 있는 권한을 부여합니다.
gcloud run services add-iam-policy-binding zoo-mcp-server \ --region=us-west1 \ --project=${GOOGLE_CLOUD_PROJECT} \ --member=user:$(gcloud config get-value account) \ --role='roles/run.invoker' adk run명령어로 에이전트를 실행합니다. 또는adk run zoo_agentadk web명령어로 ADK 웹 인터페이스를 실행합니다. 그런 다음 브라우저에서 명령어가 출력하는 URL을 엽니다. Cloud Shell을 사용하는 경우adk web --port 8080--allow_origins인수를 추가하여 Cloud Shell에서 웹 인터페이스를 열 수 있도록 합니다. 명령어를 실행한 후 Cloud Shell 툴바 (오른쪽)에서 웹 미리보기adk web --port 8080 --allow_origins="regex:https://.*\.cloudshell\.dev"
아이콘을 찾습니다. 아이콘을 클릭하고 포트 8080에서 미리보기를 선택합니다. Cloud Shell에서 ADK 웹 인터페이스가 표시된 새 브라우저 탭이 열립니다.- 상담사와 채팅을 시작합니다. 예를 들어 동물원의 동물에 관해 물어봅니다.
에이전트는How many animals are in the zoo?list_species를 호출한 다음 각 종에 대해get_animals_by_species를 호출하여 답을 계산합니다.
ADK 웹 인터페이스를 사용하면 동물원 MCP 서버에 대한 호출을 포함하여 에이전트의 작동 방식을 살펴볼 수 있습니다. 자세한 방법은 웹 UI 문서를 참고하세요.
5. MCP 서버 엔드포인트 동적 검색
이 단계에서는 에이전트가 엔드포인트 URL을 하드코딩하는 대신 에이전트 레지스트리에 등록된 동물원 MCP 서버 엔드포인트를 검색하도록 변경합니다.
GitHub에서 이 단계의 모든 파일을 찾을 수 있습니다.
에이전트 레지스트리와의 ADK 통합을 사용하여 서버의 URL을 찾고 검색합니다. 이를 구현하려면 다음 정보가 필요합니다.
- MCP 서버가 등록된 Agent Registry의 프로젝트 번호 및 위치 (
global) - MCP 서버를 등록하는 데 사용한 서비스 ID (
zoo-mcp-server)입니다.
Agent Registry를 사용하도록 에이전트 업데이트
- GitHub에서 업데이트된
agent.py파일을 다운로드하여 에이전트의 첫 번째 버전을 재정의합니다.curl -fsSL https://raw.githubusercontent.com/GoogleCloudPlatform/devrel-demos/main/codelabs/agent-registry-dynamic-discovery-with-adk/steps/step5/zoo_agent/agent.py -o zoo_agent/agent.py이 단계의 환경 변수
이 업데이트를 통해 에이전트는zoo_agent/.env의 다음 환경 변수를 사용합니다.GOOGLE_CLOUD_PROJECT_NUMBER: 이제 MCP 서버의 ID URN (MCP_SERVER_URN)을 구성하는 데 필요합니다.GOOGLE_CLOUD_PROJECT,GOOGLE_GENAI_USE_ENTERPRISE,GOOGLE_CLOUD_LOCATION: ADK에서 Vertex AI 액세스를 구성하는 데 사용됩니다. 위치는 MCP 서버를 검색하는 데도 사용됩니다.- 더 이상 필요하지 않음: 에이전트가 환경에서 MCP 서버 엔드포인트 URL을 읽는 대신 에이전트 레지스트리에서 가져오므로
MCP_SERVER_URL는 더 이상 사용되지 않습니다.
에이전트 레지스트리에서 등록된 각 MCP 서버에는AgentRegistry에서 MCP 서버 ID 및 검색이 작동하는 방식mcpServerId필드에 저장된 고유 식별자가 있으며, 이 식별자는 Uniform Resource Name (URN) 형식으로 지정됩니다. 3단계의gcloud agent-registry mcp-servers list명령어 출력에서 확인할 수 있습니다. 에이전트는 환경 변수에서 이 식별자를 재구성합니다. URN은 레지스트리의 프로젝트 번호 (PROJECT_ID = os.environ["GOOGLE_CLOUD_PROJECT"] PROJECT_NUMBER = os.environ["GOOGLE_CLOUD_PROJECT_NUMBER"] LOCATION = os.environ.get("GOOGLE_CLOUD_LOCATION", "global") MCP_SERVER_URN = f"urn:mcp:projects-{PROJECT_NUMBER}:projects:{PROJECT_NUMBER}:locations:{LOCATION}:agentregistry:services:zoo-mcp-server"projects-{PROJECT_NUMBER}), 서버가 등록된 프로젝트 번호 및 위치 (projects:{PROJECT_NUMBER}:locations:{LOCATION}),gcloud agent-registry services create명령어에 사용한 서비스 ID (agentregistry:services:zoo-mcp-server)를 인코딩합니다. 에이전트는 등록된 MCP 서버를registry.list_mcp_servers()로 나열하고 각 서버의mcpServerId를MCP_SERVER_URN와 일치시킵니다. 그런 다음 일치하는 서버의 리소스name(projects/.../locations/.../mcpServers/...)를registry.get_mcp_toolset()에 전달합니다. 그러면registry.get_mcp_toolset()는 서버의 등록된 인터페이스 (https://...run.app/mcp)에서 엔드포인트 URL을 읽고 해당 URL에 연결된McpToolset를 반환합니다.header_provider = _GoogleAuthHeaders() registry = AgentRegistry( project_id=PROJECT_ID, location=LOCATION, header_provider=header_provider, ) mcp_server_name = next( ( s["name"] for s in registry.list_mcp_servers().get("mcpServers", []) if s.get("mcpServerId") == MCP_SERVER_URN ), None, ) if not mcp_server_name: raise RuntimeError(f"MCP server '{MCP_SERVER_URN}' not found in Agent Registry.") zoo_mcp_toolset = registry.get_mcp_toolset(mcp_server_name=mcp_server_name) header_provider.bind(zoo_mcp_toolset.connection_params.url) zoo_mcp_toolset.connection_params.timeout = 30.0get_mcp_toolset()는 도구 이름 앞에 서버의 표시 이름을 붙이므로 에이전트에는 도구가zoo_mcp_server_list_species,zoo_mcp_server_get_animals_by_species,zoo_mcp_server_get_animal_details로 표시됩니다. 이 프리픽스는 에이전트가 여러 MCP 서버의 도구를 사용할 때 이름 충돌을 방지합니다. 마지막 줄은 Cloud Run 콜드 스타트를 위한 여유를 두기 위해 연결 제한 시간을 기본값인 5초에서 늘립니다.AgentRegistry에 따른 승인 변경 방법AgentRegistry는 ADC를 사용하여list_mcp_servers()와 같은 자체 API 호출을 인증하므로 검색된 MCP 서버에 대한 호출에만 헤더 제공자가 필요합니다.AgentRegistry는 내부적으로McpToolset를 만들기 때문에 헤더 제공자를AgentRegistry생성자에 전달합니다.이전 단계에서 잠재고객은 미리 알고 있던MCP_SERVER_URL값이었습니다. 이제 URL은 검색 후에만 알 수 있습니다. ADK는AgentRegistry가 생성될 때가 아니라 에이전트가 MCP 서버에 요청을 보낼 때 헤더 제공자를 호출합니다. 이렇게 하면 에이전트가 먼저 제공자를 만들고get_mcp_toolset()가 반환된 후 검색된 엔드포인트에 바인딩할 수 있습니다.bind()메서드는 이전 단계의 생성자와 동일한 방식으로 사용자 인증 정보를 가져오며,/mcp경로 없이 검색된 엔드포인트 URL에서 대상을 가져옵니다.def bind(self, endpoint_url: str) -> None: parsed = urlparse(endpoint_url) audience = f"{parsed.scheme}://{parsed.netloc}" try: self._credentials = id_token.fetch_id_token_credentials(audience, request=self._request) except exceptions.DefaultCredentialsError: self._credentials, _ = google.auth.default() self._id_token = True if not isinstance(self._credentials, credentials.Credentials): raise__call__메서드는bind()전에 호출되면 오류를 발생시킨다는 점을 제외하면 이전 단계와 동일합니다. - 에이전트를 실행하여 변경사항을 테스트합니다.
adk run명령어를 사용합니다. 또는adk run zoo_agentadk web명령어로 ADK 웹 인터페이스를 실행합니다. 그런 다음 브라우저에서 명령어가 출력하는 URL을 엽니다. Cloud Shell을 사용하는 경우adk web --port 8080--allow_origins인수를 추가하여 Cloud Shell에서 웹 인터페이스를 열 수 있도록 합니다. 에이전트에게 이전 단계와 동일한 질문을 합니다. 대답은 동일하지만 ADK 웹 인터페이스에서 도구 호출이 이제adk web --port 8080 --allow_origins="regex:https://.*\.cloudshell\.dev"zoo_mcp_server_list_species및zoo_mcp_server_get_animals_by_species로 표시됩니다.
이 변경사항으로 인해 명시적인 MCP 서버 URL이 필요하지 않게 되었습니다. 하지만 MCP 서버를 등록하는 데 사용되는 프로젝트 번호, 레지스트리 위치, 서비스 ID를 알고 있어야 하며, 탐색은 에이전트 초기화 중에 정적으로 수행됩니다.
6. 동적 검색에서 MCP 서버에 시맨틱 검색 사용
이 단계에서는 시작 시 정확한 URN 일치를 런타임 시맨틱 검색으로 대체하여 MCP 서버 동적 검색을 개선합니다. 이 접근 방식을 사용하면 서버가 배포된 프로젝트 번호나 서버의 정확한 ID (MCP_SERVER_URN)를 알 필요가 없으며, 초기화 시 처리하는 대신 에이전트의 표준 런타임 워크플로에 도구 검색을 직접 통합할 수 있습니다.
GitHub에서 이 단계의 모든 파일을 찾을 수 있습니다.
런타임에 시맨틱 검색을 사용하도록 에이전트 업데이트
- GitHub에서 업데이트된
agent.py파일을 다운로드하여 기존 구현을 덮어씁니다.curl -fsSL https://raw.githubusercontent.com/GoogleCloudPlatform/devrel-demos/main/codelabs/agent-registry-dynamic-discovery-with-adk/steps/step6/zoo_agent/agent.py -o zoo_agent/agent.py이 단계의 환경 변수
에이전트가 더 이상 MCP 서버 URN을 구성하지 않으므로 필수 구성이 더욱 간소화됩니다.GOOGLE_CLOUD_PROJECT,GOOGLE_GENAI_USE_ENTERPRISE,GOOGLE_CLOUD_LOCATION: ADK에서 Vertex AI 액세스를 구성하는 데 사용됩니다. 위치는 MCP 서버를 검색하는 데도 사용됩니다.- 더 이상 필요하지 않음:
GOOGLE_CLOUD_PROJECT_NUMBER가 더 이상 필요하지 않습니다 (이전 단계에서 이미 삭제된MCP_SERVER_URL외).
완전 일치 검색에서 시맨틱 검색으로 전환
모듈 초기화 중에registry.list_mcp_servers()를 호출하고 각 항목을 구성된MCP_SERVER_URN와 비교하는 대신agent.py는 이제search_type="SEMANTIC"를 사용하여registry.search_mcp_servers()를 호출하는 검색 도구 함수discover_zoo_mcp_servers를 정의합니다. 이렇게 하면 에이전트가 런타임에 필요한 기능에 대한 자연어 설명 (예:def discover_zoo_mcp_servers(query: str, tool_context: ToolContext) -> dict[str, Any]: """Discovers Zoo MCP server(s) matching the query using semantic search. Args: query: Semantic description of zoo capabilities needed (e.g. 'zoo animal info') """ cached = _fresh_discovery(tool_context.state) if cached: return { "status": "already_discovered", "message": "Zoo MCP servers are already discovered.", "servers": cached["servers"], } try: response = registry.search_mcp_servers( search_string=query, search_type="SEMANTIC", page_size=5, ) except Exception as e: return { "status": "error", "message": f"Agent Registry search failed: {e}", } found_servers = response.get("mcpServers", []) if not found_servers: return { "status": "not_found", "message": f"No MCP servers found in Agent Registry at {LOCATION} matching '{query}'.", } valid_servers = [s["name"] for s in found_servers if "name" in s] tool_context.state[DISCOVERY_STATE_KEY] = { "servers": valid_servers, "discovered_at": time.time(), } return { "status": "discovered", "message": f"Successfully discovered {len(valid_servers)} Zoo MCP server(s).", "servers": valid_servers, }"zoo animals, exhibits, habitat, care, feeding schedule")을 사용하여 에이전트 레지스트리를 검색하고 검색된 서버 리소스 이름을 ADK 세션 상태 (tool_context.state)에 저장할 수 있습니다.동적 탐색이 에이전트 워크플로에 통합되는 방식
런타임에 검색된 서버에서 도구를 마운트하고 검색 후 호출 흐름을 통해 에이전트를 안내하기 위해agent.py는 커스텀BaseToolset구현 (RegistryDiscoveredToolset)을 동적 안내 제공자 (zoo_guide_instruction)와 결합합니다.- 동적 도구 모음 (
RegistryDiscoveredToolset):BaseToolset를 서브클래스로 지정하고self._use_invocation_cache = False를 설정하여 ADK가 처음에 비어 있는 도구 목록을 캐시하는 대신discover_zoo_mcp_servers가 세션 상태를 업데이트한 직후에 사용 가능한 도구를 다시 평가하도록 합니다.get_tools()가 호출되면 상태에서 탐색된 서버 이름을 읽고, 작업자 스레드에서 각 서버에 대해 인증된McpToolset를 빌드하고, 모든 활성 도구를 반환합니다. 한 서버에 연결할 수 없어도 다른 서버에는 영향을 미치지 않습니다._build_toolset()는 차단 I/O를 실행하므로_tools_of()는 작업자 스레드에서 실행합니다.class RegistryDiscoveredToolset(BaseToolset): def __init__(self, agent_registry: AgentRegistry): super().__init__() self._registry = agent_registry self._toolsets: dict[str, McpToolset] = {} self._use_invocation_cache = False def _build_toolset(self, server_name: str) -> McpToolset: header_provider = _GoogleAuthHeaders() server_registry = AgentRegistry( project_id=self._registry.project_id, location=self._registry.location, header_provider=header_provider, ) toolset = server_registry.get_mcp_toolset(server_name) header_provider.bind(toolset.connection_params.url) toolset.connection_params.timeout = 30.0 return toolset async def _tools_of(self, server_name: str, ctx: ReadonlyContext) -> list[BaseTool]: try: if server_name not in self._toolsets: self._toolsets[server_name] = await asyncio.to_thread( self._build_toolset, server_name ) return await self._toolsets[server_name].get_tools_with_prefix(ctx) except Exception as e: print(f"Skipping MCP server {server_name}: {e}") return [] async def get_tools(self, readonly_context: ReadonlyContext | None = None) -> list[BaseTool]: if not readonly_context: return [] record = _fresh_discovery(readonly_context.state) if not record or not record.get("servers"): if self._toolsets: await self.close() return [] per_server = await asyncio.gather( *(self._tools_of(name, readonly_context) for name in record["servers"]) ) return [tool for tools in per_server for tool in tools] - 동적 에이전트 요청 사항 (
zoo_guide_instruction): 각 모델 차례 전에 세션 상태를 평가합니다. 아직 서버가 검색되지 않은 경우 에이전트가 먼저discover_zoo_mcp_servers를 호출하도록 지시하는_DISCOVERY_PROMPT를 추가합니다. 검색이 성공하면 활성 MCP 서버를 나열하도록 프롬프트를 업데이트하고 에이전트에게 검색을 다시 실행하지 않고 마운트된 동물원 도구를 직접 사용하도록 지시합니다.def zoo_guide_instruction(ctx: ReadonlyContext) -> str: record = _fresh_discovery(ctx.state) if record is None: return _ROLE + _DISCOVERY_PROMPT return _ROLE + ( f"\nYour active Zoo MCP servers: {', '.join(record['servers'])}. " "Do not run discovery again; use the active zoo tools directly to fulfill the visitor's request." )
동적으로 탐색된 서버 승인
시맨틱 검색은 여러 서버를 반환할 수 있으며 ADK는 요청이 어느 서버로 전송되는지 알리지 않고 헤더 프로바이더를 호출합니다. 따라서_build_toolset()는 검색된 각 서버에 전용_GoogleAuthHeaders및AgentRegistry를 만들고 제공업체를 해당 서버의 엔드포인트에 바인딩합니다. 제공자는 이전 단계와 동일하며,bind()이전에 사용되면 실패하게 만드는_bound플래그가 있습니다.보관 및 새로고침 로직
발견된 엔드포인트를 주기적으로 새로고침하면서도 매번 Agent Registry를 쿼리하지 않도록agent.py는 TTL (수명)을 사용하여 상태 보관을 구현합니다.DISCOVERY_STATE_KEY = "app:zoo_mcp_servers" DISCOVERY_TTL_SECONDS = 24 * 60 * 60 def _fresh_discovery(state: Any) -> dict[str, Any] | None: if not state: return None record = state.get(DISCOVERY_STATE_KEY) if not record or not record.get("servers"): return None if time.time() - record.get("discovered_at", 0) > DISCOVERY_TTL_SECONDS: return None return record- 애플리케이션 범위 보관: 검색된 서버 이름과
discovered_at타임스탬프는app:zoo_mcp_servers키 아래의 세션 상태에 저장됩니다. ADK의app:프리픽스를 사용하면 애플리케이션 실행 전반에 걸쳐 캐시된 검색 레코드의 범위를 지정할 수 있습니다. - TTL 검증 및 자동 새로고침:
_fresh_discovery()는 저장된 레코드가 존재하고 수명이DISCOVERY_TTL_SECONDS(24시간)을 초과하지 않는지 확인합니다. 레코드가 최신 상태인 동안RegistryDiscoveredToolset는 캐시된McpToolset연결 (self._toolsets)을 재사용하고,zoo_guide_instruction는 검색 프롬프트를 생략하며,discover_zoo_mcp_servers에 대한 반복 호출은 즉시"already_discovered"를 반환합니다. 24시간 TTL이 만료되면_fresh_discovery()에서None를 반환하여RegistryDiscoveredToolset.get_tools()에서 기존 MCP 클라이언트 세션 (await self.close())을 닫고zoo_guide_instruction에서 에이전트에게 새로운 탐색을 수행하라는 메시지를 표시합니다.
- 에이전트를 실행하여 시맨틱 검색 워크플로를 테스트합니다.
adk run명령어를 사용합니다. 또는adk run zoo_agentadk web명령어로 ADK 웹 인터페이스를 실행합니다. 그런 다음 브라우저에서 명령어가 출력하는 URL을 엽니다. Cloud Shell을 사용하는 경우adk web --port 8080--allow_origins인수를 추가하여 Cloud Shell에서 웹 인터페이스를 열 수 있도록 합니다. 에이전트에게 동물원에 관한 질문(예:adk web --port 8080 --allow_origins="regex:https://.*\.cloudshell\.dev"How many animals are in the zoo?)을 하면 에이전트가 먼저discover_zoo_mcp_servers를 호출하고, 검색된 MCP 서버에서 도구를 동적으로 마운트한 다음,zoo_mcp_server_list_species및zoo_mcp_server_get_animals_by_species와 같은 동물원 도구를 호출하여 질문에 답변하는 것을 확인할 수 있습니다.후속 질문(예:Which is the oldest lion?)을 합니다. 에이전트는 이미 마운트된 동물원 도구를 사용하여 답변하고discover_zoo_mcp_servers를 다시 호출하지 않습니다. 세션 상태의 탐색 기록이 아직 최신 상태이기 때문입니다.
7. 삭제
옵션 1: 프로젝트 삭제
이 Codelab을 위해 전용 프로젝트를 만든 경우 비용이 청구되지 않도록 하는 가장 쉬운 방법은 프로젝트를 삭제하는 것입니다.
gcloud projects delete "${GOOGLE_CLOUD_PROJECT}" --quiet
옵션 2: 개별 리소스 삭제
기존 프로젝트를 사용했고 이를 유지하려면 이 Codelab 중에 생성된 개별 리소스를 삭제합니다.
- 에이전트 레지스트리에서 MCP 서버 등록을 삭제합니다. 서버를 수동으로 등록했으므로 Cloud Run 서비스를 삭제해도 등록이 삭제되지 않습니다.
gcloud agent-registry services delete zoo-mcp-server \ --location=global \ --project="${GOOGLE_CLOUD_PROJECT}" \ --quiet - Cloud Run MCP 서버 서비스를 삭제합니다.
gcloud run services delete zoo-mcp-server \ --region=us-west1 \ --project="${GOOGLE_CLOUD_PROJECT}" \ --quiet - MCP 서버용으로 만든 서비스 계정을 삭제합니다.
gcloud iam service-accounts delete \ "zoo-mcp-server-sa@${GOOGLE_CLOUD_PROJECT}.iam.gserviceaccount.com" \ --project="${GOOGLE_CLOUD_PROJECT}" \ --quiet - 이 Codelab을 진행하는 동안
cloud-run-source-deployArtifact Registry 저장소를 만든 경우 컨테이너 이미지 스토리지 요금이 청구되지 않도록 삭제합니다.gcloud artifacts repositories delete cloud-run-source-deploy \ --location=us-west1 \ --project="${GOOGLE_CLOUD_PROJECT}" \ --quiet - 필요한 경우 Cloud Shell에서 만든 로컬 프로젝트 디렉터리를 삭제합니다.
cd ~ && rm -rf adk-dynamic-discovery
8. 축하합니다
이 Codelab에서는 Google Cloud Agent Registry와 에이전트 개발 키트 (ADK)를 사용하여 MCP 서버를 동적으로 검색하고 사용하는 방법을 알아보았습니다.
- MCP 서버 등록: 보안 MCP 서버를 Cloud Run에 배포하고 Agent Registry에 등록했습니다.
- ID로 MCP 서버 검색: URL을 하드코딩하는 대신 에이전트 레지스트리에서 MCP 서버 엔드포인트를 확인했습니다.
- 시맨틱하게 검색된 MCP 서버: 에이전트가 자연어 검색을 사용하여 런타임에 MCP 도구를 찾고 마운트할 수 있으며, 보관 및 새로고침이 가능합니다.