1. Introduzione
In questo codelab, imparerai a utilizzare Google Cloud Agent Registry e l'Agent Development Kit (ADK) per scoprire e richiamare dinamicamente i server Model Context Protocol (MCP) registrati in fase di runtime, eliminando gli endpoint e le configurazioni degli strumenti impostati come hardcoded.
Questo lab utilizza l'ADK e il linguaggio di programmazione Python per creare un'applicazione. Gli stessi principi possono essere applicati in altri linguaggi di programmazione e utilizzando altri framework di sviluppo degli agenti.
In questo lab proverai a:
- Abilita e configura Google Cloud Agent Registry nel tuo progetto
- Esegui il deployment di un server MCP su Cloud Run e registralo in Agent Registry
- Crea un agente ADK che chiama il server MCP utilizzando un URL impostato come hardcoded
- Modifica l'agente per rilevare il server MCP in Agent Registry in base alla sua identità
- Modifica l'agente per rilevare i server MCP in fase di runtime utilizzando la ricerca semantica
Che cosa ti serve
- Un browser web moderno (ad esempio Chrome)
- Un progetto cloud Google Cloud con la fatturazione abilitata
- Un account con le autorizzazioni per creare e gestire risorse e configurare policy IAM nel progetto Google Cloud
2. Configura l'ambiente
Le istruzioni di questo codelab presuppongono che tu esegua i comandi in Cloud Shell nella console Google Cloud. Cloud Shell è un ambiente a riga di comando in esecuzione in Google Cloud che viene caricato preventivamente con gli strumenti necessari. Se hai installato gcloud CLI, git e Python 3 nel tuo ambiente locale, puoi eseguire i comandi anche lì.
Crea un progetto Google Cloud
- Nella console Google Cloud, nella pagina di selezione del progetto, crea un progetto Google Cloud.
- Verifica che la fatturazione sia attivata per il tuo progetto Cloud. Scopri come verificare se la fatturazione è abilitata per un progetto.
Avvia un terminale Cloud Shell
- Utilizzando il selettore di risorse, seleziona il progetto Google Cloud che hai creato.
- Fai clic su Attiva Cloud Shell
nella parte superiore della console Google Cloud.
- Una volta eseguita la connessione a Cloud Shell, verifica l'autenticazione:
gcloud auth list - Verifica che il progetto sia configurato:
gcloud config get project - Se il progetto non è impostato come previsto o se utilizzi un ambiente sulla tua macchina locale anziché Cloud Shell, configura l'ID progetto:
export GOOGLE_CLOUD_PROJECT=<YOUR_PROJECT_ID> gcloud config set project "${GOOGLE_CLOUD_PROJECT}"
Seleziona un progetto Google Cloud e imposta le variabili di ambiente
- Abilita le API Google Cloud richieste:
gcloud services enable \ agentregistry.googleapis.com \ aiplatform.googleapis.com \ cloudbuild.googleapis.com \ run.googleapis.com \ --project="${GOOGLE_CLOUD_PROJECT}"
Configura l'ambiente Python
- Crea e attiva un ambiente virtuale e installa le dipendenze richieste:
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. Crea e registra un server MCP Zoo
In questo passaggio, creerai e registrerai un server MCP Zoo. Il server espone tre strumenti: list_species, che elenca tutte le specie che vivono nello zoo, get_animals_by_species, che elenca gli animali di una specie specifica, e get_animal_details, che fornisce informazioni aggiuntive su un animale specifico.
Puoi trovare tutti i file per questo passaggio su GitHub.
Crea il server MCP
- Configura una cartella e un progetto Python per il server MCP
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 - Scarica il codice del server 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
Esegui il deployment del server MCP su Cloud Run e registralo in Agent Registry
Successivamente, creerai Dockerfile per il server ed eseguirai il deployment del server in Cloud Run.
- Crea
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 - Crea un service account designato per il server:
gcloud iam service-accounts create zoo-mcp-server-sa \ --display-name="Zoo MCP Server Service Account" - Esegui il deployment del server nella regione
us-west1. Se vedi il seguente prompt, conferma la creazione di un nuovo repository Artifact Registry. Questo prompt verrà visualizzato se non hai un repository Artifact Registry nel tuo progetto per la regionegcloud 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: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)? - Registra il server MCP in Agent Registry nella località "global":
Questi comandi generanofastmcp 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.jsoncon il manifest dello strumento e chiamano l'API Agent Registry per registrare il server. - Verifica che l'agente venga visualizzato nel registry:
Vedrai un output YAML con le informazioni sul server MCP, simile al seguente: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. Crea un agente ADK di base per utilizzare il server MCP Zoo
In questo passaggio, crei un agente ADK per rispondere alle richieste degli utenti sullo zoo con l'aiuto dell'URL del server MCP Zoo impostato come hardcoded. L'impostazione come hardcoded ti obbliga a conoscere l'URL del server MCP PRIMA del deployment dell'agente.
Puoi trovare i file di origine per questo passaggio su GitHub.
Crea l'applicazione agente
Crea un'impalcatura del progetto ADK e dell'agente Zoo che utilizza il server MCP Zoo.
- Configura una cartella e un progetto per l'agente 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 - Crea la cartella del pacchetto agente e scarica il file
__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 - Crea il file
.enve compilalo con le variabili di ambiente utilizzate in questo passaggio e in quelli successivi: Questo comando scrive tutte le variabili di ambiente richieste in questo passaggio e nei passaggi di discovery successivi inMCP_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, in modo da non dover ricreare il file in un secondo momento. In questo passaggio vengono utilizzate le seguenti variabili:GOOGLE_GENAI_USE_ENTERPRISE,GOOGLE_CLOUD_PROJECTeGOOGLE_CLOUD_LOCATION: utilizzate implicitamente dall'ADK e dall'SDK Google GenAI per instradare le chiamate al modello Gemini tramite Vertex AI nel progetto cloud e nella località Google Cloud (global). Memorizza anche la località per Agent Registry in cui è registrato il server MCP.GOOGLE_CLOUD_PROJECT_NUMBER: non utilizzato in questo passaggio; è precompilato qui per l'utilizzo nel passaggio successivo.MCP_SERVER_URL: leggi direttamente inzoo_agent/agent.pyper configurareMcpToolsetcon l'URL Cloud Run impostato come hardcoded del server MCP Zoo.
- Scarica
agent.pycon il codice per l'agente Zoo che si connette al server MCP Zoo: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
Come funziona l'autorizzazione del server MCP
Poiché il servizio Cloud Run zoo-mcp-server è stato sottoposto a deployment con i flag --invoker-iam-check e --no-allow-unauthenticated, ogni richiesta HTTP al server MCP deve includere un token ID firmato da Google nell'intestazione Authorization. Il token deve identificare un chiamante che dispone dell'autorizzazione run.routes.invoke sul servizio.
In zoo_agent/agent.py, la classe _GoogleAuthHeaders ottiene le credenziali del token ID in uno dei due modi seguenti. Innanzitutto chiama fetch_id_token_credentials() per ottenere un token ID per l'URL del servizio dal server dei metadati o da una chiave del service account. Se l'operazione non riesce, utilizza il token ID delle tue Credenziali predefinite dell'applicazione (ADC) utente. Ogni volta che viene chiamata l'istanza, il token viene aggiornato, se necessario, e viene restituita l'intestazione 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}"}
L'agente passa un'istanza della classe a McpToolset come argomento header_provider. ADK chiama il provider di intestazioni e allega le intestazioni restituite a ogni richiesta al server MCP. Poiché il client HTTP viene creato da ADK, lo stesso codice funziona indipendentemente dalla versione dell'SDK Python MCP e dalla relativa libreria HTTP installata:
zoo_mcp_toolset = McpToolset(
connection_params=StreamableHTTPConnectionParams(
url=f"{MCP_SERVER_URL}/mcp",
timeout=30.0,
),
header_provider=_GoogleAuthHeaders(MCP_SERVER_URL),
)
Il pubblico richiesto è l'URL del servizio (MCP_SERVER_URL), mentre l'URL di connessione include il percorso /mcp in cui il server FastMCP gestisce il protocollo MCP. Il timeout di 30 secondi lascia spazio agli avvii a freddo di Cloud Run.
Testa l'agente utilizzando il playground ADK nel tuo ambiente locale
Prima di testare l'agente, assicurati che il tuo account utente possa chiamare il server MCP.
- Solo terminale locale (salta questo passaggio in Cloud Shell): inizializza ADC con le tue credenziali utente:
Segui le istruzioni per completare l'inizializzazione di ADC.gcloud auth application-default login - (Facoltativo) Concedi al tuo account utente l'autorizzazione per chiamare il server MCP remoto:
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' - Esegui l'agente con il comando
adk run: oppure esegui l'interfaccia web ADK con il comandoadk run zoo_agentadk web: e poi apri l'URL stampato dal comando nel browser.Se utilizzi Cloud Shell, aggiungi l'argomentoadk web --port 8080--allow_originsper consentire l'apertura dell'interfaccia web da Cloud Shell: Dopo aver eseguito il comando, individua l'icona Anteprima webadk web --port 8080 --allow_origins="regex:https://.*\.cloudshell\.dev"
nella barra degli strumenti di Cloud Shell (lato destro). Fai clic sull'icona e seleziona Anteprima sulla porta 8080. Cloud Shell aprirà una nuova scheda del browser che mostra l'interfaccia web di ADK. - Inizia a chattare con l'agente. Ad esempio, chiedi informazioni sugli animali dello zoo:
L'agente effettua una chiamata agentica aHow many animals are in the zoo?list_speciese poi aget_animals_by_speciesper ogni specie per calcolare la risposta.
L'interfaccia web dell'ADK ti consente di esplorare il funzionamento dell'agente, incluse le sue chiamate al server MCP dello zoo. Per scoprire come, consulta la documentazione della UI web.
5. Rileva dinamicamente l'endpoint del server MCP
In questo passaggio, modifichi l'agente in modo che rilevi l'endpoint del server MCP Zoo registrato in Agent Registry anziché impostare come hardcoded l'URL dell'endpoint.
Puoi trovare tutti i file per questo passaggio su GitHub.
Utilizza l'integrazione dell'ADK con Agent Registry per trovare e recuperare l'URL del server. Per implementarlo, devi disporre delle seguenti informazioni:
- Numero di progetto e località di Agent Registry in cui è registrato il server MCP (
global). - L'ID servizio che hai utilizzato per registrare il server MCP (
zoo-mcp-server).
Aggiorna l'agente per utilizzare Agent Registry
- Scarica il file
agent.pyaggiornato da GitHub per eseguire l'override della prima versione dell'agente: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.pyVariabili di ambiente in questo passaggio
Con questo aggiornamento, l'agente si basa sulle seguenti variabili di ambiente dizoo_agent/.env:GOOGLE_CLOUD_PROJECT_NUMBER: ora è obbligatorio per creare l'URN dell'identità del server MCP (MCP_SERVER_URN).GOOGLE_CLOUD_PROJECT,GOOGLE_GENAI_USE_ENTERPRISEeGOOGLE_CLOUD_LOCATION: utilizzate da ADK per configurare l'accesso a Vertex AI. La località viene utilizzata anche per rilevare il server MCP.- Non più obbligatorio:
MCP_SERVER_URLnon viene più utilizzato perché l'agente recupera l'URL dell'endpoint del server MCP da Agent Registry anziché leggerlo dall'ambiente.
Come funzionano l'identità e il rilevamento del server MCP in
In Agent Registry, ogni server MCP registrato ha un identificatore univoco memorizzato nel campoAgentRegistrymcpServerId, formattato come Uniform Resource Name (URN). Puoi visualizzarlo nell'output del comandogcloud agent-registry mcp-servers listnel passaggio 3. L'agente ricostruisce questo identificatore dalle variabili di ambiente: L'URN codifica il numero di progetto del registry (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}), il numero di progetto e la località in cui è registrato il server (projects:{PROJECT_NUMBER}:locations:{LOCATION}) e l'ID servizio utilizzato nel comandogcloud agent-registry services create(agentregistry:services:zoo-mcp-server).L'agente elenca i server MCP registrati conregistry.list_mcp_servers()e confronta ilmcpServerIddi ogni server conMCP_SERVER_URN. Quindi, passa la risorsa del server corrispondentename(projects/.../locations/.../mcpServers/...) aregistry.get_mcp_toolset(), che legge l'URL dell'endpoint dall'interfaccia registrata del server (https://...run.app/mcp) e restituisce unMcpToolsetconnesso a quell'URL: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()aggiunge il nome visualizzato del server come prefisso ai nomi degli strumenti, quindi l'agente vede gli strumenti comezoo_mcp_server_list_species,zoo_mcp_server_get_animals_by_speciesezoo_mcp_server_get_animal_details. Il prefisso impedisce i conflitti di nomi quando un agente utilizza strumenti di più server MCP. L'ultima riga aumenta il timeout della connessione rispetto al valore predefinito di 5 secondi per lasciare spazio agli avvii a freddo di Cloud Run.Come cambia l'autorizzazione con
AgentRegistryAgentRegistryautentica le proprie chiamate API, ad esempiolist_mcp_servers(), con ADC, pertanto il provider di intestazioni è necessario solo per le chiamate al server MCP rilevato. PoichéAgentRegistrycreaMcpToolsetinternamente, passi il provider di intestazioni al costruttoreAgentRegistry.Nel passaggio precedente, il pubblico era il valoreMCP_SERVER_URLche conoscevi in anticipo. Ora l'URL è noto solo dopo il discovery. L'ADK chiama il provider di intestazioni quando l'agente invia una richiesta al server MCP, non quando viene creatoAgentRegistry. In questo modo, l'agente può creare prima il provider e associarlo all'endpoint rilevato dopo la restituzione diget_mcp_toolset(). Il metodobind()ottiene le credenziali nello stesso modo del costruttore nel passaggio precedente, con il pubblico preso dall'URL dell'endpoint rilevato senza il percorso/mcp: Il metododef 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__è lo stesso del passaggio precedente, ma genera un errore se viene chiamato prima dibind(). - Esegui l'agente per testare la modifica. Utilizza il comando
adk run: oppure esegui l'interfaccia web ADK con il comandoadk run zoo_agentadk web: e poi apri l'URL stampato dal comando nel browser.Se utilizzi Cloud Shell, aggiungi l'argomentoadk web --port 8080--allow_originsper consentire l'apertura dell'interfaccia web da Cloud Shell: Fai all'agente la stessa domanda del passaggio precedente. La risposta è la stessa, ma nell'interfaccia web di ADK le chiamate allo strumento ora vengono visualizzate comeadk web --port 8080 --allow_origins="regex:https://.*\.cloudshell\.dev"zoo_mcp_server_list_speciesezoo_mcp_server_get_animals_by_species.
La modifica ti ha permesso di evitare di dover specificare l'URL esplicito del server MCP. Tuttavia, devi comunque conoscere il numero del progetto, la località del registry e l'ID servizio utilizzato per registrare il server MCP. Inoltre, il rilevamento viene eseguito in modo statico durante l'inizializzazione dell'agente.
6. Utilizza la ricerca semantica per il server MCP nel rilevamento dinamico
In questo passaggio, migliorerai il rilevamento dinamico del server MCP sostituendo la corrispondenza esatta dell'URN all'avvio con la ricerca semantica di runtime. Questo approccio elimina la necessità di conoscere il numero del progetto in cui è stato eseguito il deployment del server o l'identità esatta del server (MCP_SERVER_URN) e integra il rilevamento degli strumenti direttamente nel workflow di runtime standard dell'agente anziché gestirlo durante l'inizializzazione.
Puoi trovare tutti i file per questo passaggio su GitHub.
Aggiorna l'agente per utilizzare il discovery semantico in fase di runtime
- Scarica il file
agent.pyaggiornato da GitHub per sovrascrivere l'implementazione esistente: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.pyVariabili di ambiente in questo passaggio
Poiché l'agente non crea più un URN del server MCP, la configurazione richiesta è ulteriormente semplificata:GOOGLE_CLOUD_PROJECT,GOOGLE_GENAI_USE_ENTERPRISEeGOOGLE_CLOUD_LOCATION: utilizzate da ADK per configurare l'accesso a Vertex AI. La località viene utilizzata anche per rilevare il server MCP.- Non più obbligatorio:
GOOGLE_CLOUD_PROJECT_NUMBERnon è più necessario (oltre aMCP_SERVER_URL, che è già stato rimosso nel passaggio precedente).
Passa dalla corrispondenza esatta alla ricerca semantica
Anziché chiamareregistry.list_mcp_servers()durante l'inizializzazione del modulo e confrontare ogni voce con unMCP_SERVER_URNcostruito,agent.pyora definisce una funzione dello strumento di rilevamentodiscover_zoo_mcp_serversche chiamaregistry.search_mcp_servers()consearch_type="SEMANTIC": In questo modo, l'agente può eseguire ricerche in Agent Registry in fase di runtime utilizzando una descrizione in linguaggio naturale delle funzionalità di cui ha bisogno (ad esempio,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") e salvare i nomi delle risorse del server rilevate nello stato della sessione ADK (tool_context.state).Come viene integrato il discovery dinamico nel workflow dell'agente
Per montare gli strumenti dai server rilevati in fase di runtime e guidare l'agente attraverso il flusso di rilevamento e chiamata,agent.pycombina un'implementazioneBaseToolsetpersonalizzata (RegistryDiscoveredToolset) con un provider di istruzioni dinamiche (zoo_guide_instruction):- Set di strumenti dinamico (
RegistryDiscoveredToolset): sottoclassiBaseToolsete impostaself._use_invocation_cache = Falsein modo che l'ADK rivaluti gli strumenti disponibili immediatamente dopo chediscover_zoo_mcp_serversaggiorna lo stato della sessione, anziché memorizzare nella cache un elenco di strumenti inizialmente vuoto. Quando viene chiamatoget_tools(), legge i nomi dei server rilevati dallo stato, crea unMcpToolsetautenticato per ogni server in un thread di lavoro e restituisce tutti gli strumenti attivi. L'impossibilità di raggiungere un server non influisce sugli altri._build_toolset()esegue l'I/O di blocco, quindi_tools_of()lo esegue in un thread di lavoro.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] - Istruzione dinamica dell'agente (
zoo_guide_instruction): valuta lo stato della sessione prima di ogni turno del modello. Se non sono ancora stati rilevati server, aggiunge_DISCOVERY_PROMPTper indicare all'agente di chiamare primadiscover_zoo_mcp_servers. Una volta completato il rilevamento, aggiorna il prompt per elencare i server MCP attivi e indica all'agente di utilizzare direttamente gli strumenti dello zoo montati senza eseguire nuovamente il rilevamento: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." )
Autorizzazione per i server rilevati dinamicamente
La ricerca semantica può restituire diversi server e l'ADK chiama un provider di intestazioni senza indicargli a quale server viene inviata una richiesta. Pertanto,_build_toolset()crea un_GoogleAuthHeaderse unAgentRegistrydedicati per ogni server rilevato e associa il provider all'endpoint del server. Il provider è lo stesso del passaggio precedente, con un flag_boundche ne causa l'errore se viene utilizzato prima dibind().Logica di conservazione e aggiornamento
Per evitare di eseguire query su Agent Registry a ogni turno, pur aggiornando periodicamente gli endpoint rilevati,agent.pyimplementa la conservazione dello stato con un TTL (Time To Live):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- Conservazione con ambito applicazione: i nomi dei server rilevati e un timestamp
discovered_atvengono archiviati nello stato della sessione con la chiaveapp:zoo_mcp_servers. L'utilizzo degli ambiti con prefissoapp:di ADK consente di applicare l'ambito del record di rilevamento memorizzato nella cache all'esecuzione dell'applicazione. - Convalida TTL e aggiornamento automatico:
_fresh_discovery()verifica che il record salvato esista e che la sua età non superiDISCOVERY_TTL_SECONDS(24 ore). Mentre il record è aggiornato,RegistryDiscoveredToolsetriutilizza le connessioniMcpToolsetmemorizzate nella cache (self._toolsets),zoo_guide_instructionomette il prompt di rilevamento e qualsiasi chiamata ripetuta adiscover_zoo_mcp_serversrestituisce immediatamente"already_discovered". Una volta scaduto il TTL di 24 ore,_fresh_discovery()restituisceNone, causando la chiusura delle sessioni client MCP esistenti (await self.close()) da parte diRegistryDiscoveredToolset.get_tools()e la richiesta all'agente di eseguire un nuovo rilevamento da parte dizoo_guide_instruction.
- Esegui l'agente per testare il workflow di discovery semantica. Utilizza il comando
adk run: oppure esegui l'interfaccia web ADK con il comandoadk run zoo_agentadk web: e poi apri l'URL stampato dal comando nel browser.Se utilizzi Cloud Shell, aggiungi l'argomentoadk web --port 8080--allow_originsper consentire l'apertura dell'interfaccia web da Cloud Shell: Quando fai una domanda all'agente sullo zoo (ad esempio,adk web --port 8080 --allow_origins="regex:https://.*\.cloudshell\.dev"How many animals are in the zoo?), osserva che prima richiamadiscover_zoo_mcp_servers, monta dinamicamente gli strumenti dal server MCP scoperto e poi chiama gli strumenti dello zoo, comezoo_mcp_server_list_speciesezoo_mcp_server_get_animals_by_species, per rispondere alla tua domanda.Fai una domanda aggiuntiva, ad esempioWhich is the oldest lion?. L'agente risponde utilizzando gli strumenti dello zoo già montati e non chiama di nuovodiscover_zoo_mcp_servers, perché il record di rilevamento nello stato della sessione è ancora aggiornato.
7. Esegui la pulizia
Opzione 1: elimina il progetto
Se hai creato un progetto dedicato per questo codelab, il modo più semplice per eliminare la fatturazione è eliminare il progetto:
gcloud projects delete "${GOOGLE_CLOUD_PROJECT}" --quiet
Opzione 2: elimina singole risorse
Se hai utilizzato un progetto esistente e vuoi conservarlo, elimina le singole risorse create durante questo codelab:
- Elimina la registrazione del server MCP da Agent Registry. Poiché hai registrato il server manualmente, l'eliminazione del servizio Cloud Run non rimuove la registrazione:
gcloud agent-registry services delete zoo-mcp-server \ --location=global \ --project="${GOOGLE_CLOUD_PROJECT}" \ --quiet - Elimina il servizio server MCP di Cloud Run:
gcloud run services delete zoo-mcp-server \ --region=us-west1 \ --project="${GOOGLE_CLOUD_PROJECT}" \ --quiet - Elimina il service account creato per il server MCP:
gcloud iam service-accounts delete \ "zoo-mcp-server-sa@${GOOGLE_CLOUD_PROJECT}.iam.gserviceaccount.com" \ --project="${GOOGLE_CLOUD_PROJECT}" \ --quiet - Se il repository Artifact Registry
cloud-run-source-deployè stato creato appositamente durante questo codelab, eliminalo per evitare addebiti per l'archiviazione delle immagini container:gcloud artifacts repositories delete cloud-run-source-deploy \ --location=us-west1 \ --project="${GOOGLE_CLOUD_PROJECT}" \ --quiet - Facoltativamente, rimuovi la directory del progetto locale creata in Cloud Shell:
cd ~ && rm -rf adk-dynamic-discovery
8. Complimenti
In questo codelab hai imparato a rilevare e utilizzare dinamicamente i server MCP con Google Cloud Agent Registry e l'Agent Development Kit (ADK):
- Registrato un server MCP: hai eseguito il deployment di un server MCP protetto su Cloud Run e lo hai registrato in Agent Registry.
- Rilevamento di un server MCP in base all'identità: l'endpoint del server MCP è stato risolto da Agent Registry anziché impostare l'URL come hardcoded.
- Server MCP rilevati semanticamente: consenti all'agente di trovare e montare gli strumenti MCP in fase di runtime utilizzando la ricerca in linguaggio naturale, con conservazione e aggiornamento.