1. 簡介
在本程式碼實驗室中,您將瞭解如何使用 Google Cloud Agent Registry 和 Agent Development Kit (ADK),在執行階段動態探索及叫用已註冊的 Model Context Protocol (MCP) 伺服器,不必硬式編碼端點和工具設定。
本實驗室會使用 ADK 和 Python 程式設計語言建構應用程式。同樣的原則也適用於其他程式設計語言和代理開發架構。
學習內容
- 在專案中啟用及設定 Google Cloud Agent Registry
- 將 MCP 伺服器部署至 Cloud Run,並在 Agent Registry 註冊
- 建構 ADK 代理,使用硬式編碼網址呼叫 MCP 伺服器
- 變更代理,以便在 Agent Registry 中依身分尋找 MCP 伺服器
- 變更代理,在執行階段使用語意搜尋探索 MCP 伺服器
軟硬體需求
- 新式網路瀏覽器 (例如 Chrome)
- 已啟用計費功能的 Google Cloud 雲端專案
- 具備建立及管理資源,以及設定 Google Cloud 專案 IAM 政策權限的帳戶
2. 設定環境
本程式碼實驗室的操作說明假設您在 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. 建立及註冊 Zoo MCP 伺服器
在這個步驟中,您將建立並註冊 Zoo 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
將 MCP 伺服器部署至 Cloud Run,並在 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 的「global」位置註冊 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 來註冊伺服器。 - 確認代理程式顯示在登錄檔中:
您會看到 YAML 輸出內容,其中包含 MCP 伺服器資訊,如下所示: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. 建立基本的 ADK 代理,以便使用 Zoo MCP 伺服器
在這個步驟中,您將建立 ADK 代理,並使用硬式編碼的動物園 MCP 伺服器網址,回答使用者對動物園的提問。硬式編碼會強制您在部署代理之前,先知道 MCP 伺服器的網址。
您可以在 GitHub 找到這個步驟的來源檔案。
建立代理應用程式
建立 ADK 專案和 Zoo 代理的鷹架,並使用 Zoo MCP 伺服器。
- 為 Zoo 代理設定資料夾和專案:
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 雲端專案和位置 (global) 中的 Vertex AI 轉送 Gemini 模型呼叫。此外,這些變數也會儲存 MCP 伺服器註冊的 Agent Registry 位置。GOOGLE_CLOUD_PROJECT_NUMBER:這個步驟不會用到,此處預先填入是為了在下一個步驟使用。MCP_SERVER_URL:直接在zoo_agent/agent.py中閱讀,使用 Zoo MCP 伺服器的硬式編碼 Cloud Run 網址設定McpToolset。
- 下載
agent.py,其中包含連至動物園 MCP 伺服器的動物園代理程式碼: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(),從中繼資料伺服器或服務帳戶金鑰取得服務網址的 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}"}
代理會將類別的執行個體做為 header_provider 引數傳遞至 McpToolset。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),
)
要求的目標對象是服務網址 (MCP_SERVER_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 網頁介面: 然後在瀏覽器中開啟指令輸出的網址。如果您使用 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 網頁介面,瞭解代理的運作方式,包括對 Zoo MCP 伺服器的呼叫。如要瞭解操作方式,請參閱 Web UI 說明文件。
5. 動態探索 MCP 伺服器端點
在這個步驟中,您將變更代理,改為探索 Agent Registry 中註冊的動物園 MCP 伺服器端點,而非硬式編碼端點網址。
您可以在 GitHub 找到這個步驟的所有檔案。
您可以使用 ADK 與 Agent Registry 的整合功能,尋找及擷取伺服器的網址。如要導入,您需要下列資訊:
- MCP 伺服器註冊的 Agent Registry 專案編號和位置 (
global)。 - 您用來註冊 MCP 伺服器的服務 ID (
zoo-mcp-server)。
更新代理以使用代理登錄
- 從 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 伺服器的身分 URN (MCP_SERVER_URN)。GOOGLE_CLOUD_PROJECT、GOOGLE_GENAI_USE_ENTERPRISE和GOOGLE_CLOUD_LOCATION:ADK 會使用這些變數設定 Vertex AI 存取權。位置也會用於尋找 MCP 伺服器。- 不再需要:代理會從 Agent Registry 擷取 MCP 伺服器端點網址,而非從環境讀取,因此不再使用
MCP_SERVER_URL。
在代理程式登錄檔中,每個已註冊的 MCP 伺服器都有專屬 ID,儲存在AgentRegistry如何識別及探索 MCP 伺服器mcpServerId欄位中,格式為統一資源名稱 (URN)。您可以在步驟 3 的gcloud agent-registry mcp-servers list指令輸出內容中查看。代理程式會從環境變數重新建構這個 ID: 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(),後者會從伺服器的註冊介面 (https://...run.app/mcp) 讀取端點網址,並傳回連至該網址的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 伺服器的工具時,前置字串可避免名稱衝突。最後一行將連線逾時時間從預設的 5 秒提高,為 Cloud Run 冷啟動預留空間。授權如何隨著
AgentRegistry而改變AgentRegistry會使用 ADC 驗證自己的 API 呼叫 (例如list_mcp_servers()),因此只有在呼叫探索到的 MCP 伺服器時,才需要標頭提供者。由於AgentRegistry會在內部建立McpToolset,因此您需要將標頭供應器傳遞至AgentRegistry建構函式。在上一個步驟中,目標對象是您預先知道的MCP_SERVER_URL值。現在,網址只有在探索後才會顯示。ADK 會在代理向 MCP 伺服器傳送要求時呼叫標頭提供者,而非在建立AgentRegistry時。這樣一來,代理程式就能先建立提供者,並在get_mcp_toolset()傳回後,將提供者繫結至探索到的端點。bind()方法取得憑證的方式與上一個步驟中的建構函式相同,目標對象則取自探索到的端點網址,不含/mcp路徑: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 網頁介面: 然後在瀏覽器中開啟指令輸出的網址。如果您使用 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 伺服器網址。不過,您仍須知道專案編號、儲存庫位置,以及用於註冊 MCP 伺服器的服務 ID,且探索作業會在代理初始化期間以靜態方式執行。
6. 在動態探索中,使用語意搜尋功能尋找 MCP 伺服器
在這個步驟中,您會將啟動時的 URN 精確比對,替換為執行階段語意搜尋,藉此改善 MCP 伺服器動態探索功能。這樣一來,您就不必知道伺服器部署的專案編號或伺服器的確切身分 (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之外)。
從完全比對轉向語意搜尋
agent.py現在會定義探索工具函式discover_zoo_mcp_servers,並使用search_type="SEMANTIC"呼叫registry.search_mcp_servers(),而不是在模組初始化期間呼叫registry.list_mcp_servers(),並將每個項目與建構的MCP_SERVER_URN進行比較: 這樣一來,代理就能在執行階段使用自然語言描述所需功能 (例如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"),搜尋 Agent Registry,並將找到的伺服器資源名稱儲存在 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):在每次模型對話前評估工作階段狀態。如果尚未探索到任何伺服器,系統會附加_DISCOVERY_PROMPT,指示代理先呼叫discover_zoo_mcp_servers。探索成功後,系統會更新提示,列出有效的 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,並將供應商繫結至該伺服器的端點。供應商與上一個步驟相同,並含有_bound旗標,如果在bind()之前使用,就會失敗。保留和重新整理邏輯
為避免每次都查詢代理程式註冊資料庫,同時定期重新整理探索到的端點,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 網頁介面: 然後在瀏覽器中開啟指令輸出的網址。如果您使用 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:刪除專案
如果您為這個程式碼實驗室建立了專屬專案,最簡單的避免計費方式就是刪除專案:
gcloud projects delete "${GOOGLE_CLOUD_PROJECT}" --quiet
選項 2:刪除個別資源
如果您使用的是現有專案,且想保留該專案,請刪除在本程式碼實驗室中建立的個別資源:
- 從 Agent Registry 刪除 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 - 如果
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. 恭喜
在本程式碼實驗室,您學會如何使用 Google Cloud Agent Registry 和 Agent Development Kit (ADK),動態探索及使用 MCP 伺服器:
- 註冊 MCP 伺服器:將安全的 MCP 伺服器部署至 Cloud Run,並在 Agent Registry 中註冊。
- 依身分探索 MCP 伺服器:從 Agent Registry 解析 MCP 伺服器端點,而非對網址進行硬式編碼。
- 透過語意搜尋探索 MCP 伺服器:讓代理在執行階段使用自然語言搜尋,尋找並掛接 MCP 工具,並保留及重新整理。