1. Introducción
En este codelab, aprenderás a usar el Registro de agentes de Google Cloud y el Kit de desarrollo de agentes (ADK) para descubrir y, luego, invocar de forma dinámica servidores del Protocolo de contexto del modelo (MCP) registrados en el entorno de ejecución, lo que elimina los extremos y las configuraciones de herramientas codificados de forma rígida.
En este lab, se usan el ADK y el lenguaje de programación Python para compilar una aplicación. Los mismos principios se pueden aplicar en otros lenguajes de programación y con otros frameworks de desarrollo de agentes.
Actividades
- Habilita y configura Google Cloud Agent Registry en tu proyecto
- Implementar un servidor de MCP en Cloud Run y registrarlo en Agent Registry
- Compilar un agente del ADK que llame al servidor de MCP con una URL codificada
- Cambiar el agente para que descubra el servidor de MCP en Agent Registry por su identidad
- Cambiar el agente para que descubra servidores de MCP en el entorno de ejecución con la búsqueda semántica
Requisitos
- Un navegador web moderno (como Chrome)
- Un proyecto de Google Cloud con la facturación habilitada.
- Una cuenta con permisos para crear y administrar recursos, y configurar políticas de IAM en el proyecto de Google Cloud
2. Configura tu entorno
En las instrucciones de este codelab, se supone que ejecutas comandos en Cloud Shell en la consola de Google Cloud. Cloud Shell es un entorno de línea de comandos que se ejecuta en Google Cloud y que viene precargado con las herramientas necesarias. Si tienes la CLI de gcloud, git y Python 3 instalados en tu entorno local, también puedes ejecutar los comandos allí.
Crea un proyecto de Google Cloud
- En la página del selector de proyectos de la consola de Google Cloud, crea un proyecto de Google Cloud.
- Asegúrate de que la facturación esté habilitada para tu proyecto de Cloud. Obtén información para verificar si la facturación está habilitada en un proyecto.
Inicia una terminal de Cloud Shell
- Con el selector de recursos, selecciona el proyecto de Google Cloud que creaste.
- Haz clic en Activar Cloud Shell
en la parte superior de la consola de Google Cloud.
- Una vez que te conectes a Cloud Shell, verifica tu autenticación:
gcloud auth list - Confirma que tu proyecto esté configurado:
gcloud config get project - Si tu proyecto no está configurado como se espera o si usas un entorno en tu máquina local en lugar de Cloud Shell, configura el ID del proyecto:
export GOOGLE_CLOUD_PROJECT=<YOUR_PROJECT_ID> gcloud config set project "${GOOGLE_CLOUD_PROJECT}"
Selecciona un proyecto de Google Cloud y configura variables de entorno
- Habilita las APIs de Google Cloud requeridas:
gcloud services enable \ agentregistry.googleapis.com \ aiplatform.googleapis.com \ cloudbuild.googleapis.com \ run.googleapis.com \ --project="${GOOGLE_CLOUD_PROJECT}"
Configura el entorno de Python
- Crea y activa un entorno virtual, y, luego, instala las dependencias necesarias:
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. Crear y registrar un servidor de MCP de Zoo
En este paso, crearás y registrarás un servidor de MCP de Zoo. El servidor expone tres herramientas: list_species, que enumera todas las especies que viven en el zoológico; get_animals_by_species, que enumera los animales de una especie específica, y get_animal_details, que obtiene información adicional sobre un animal específico.
Puedes encontrar todos los archivos de este paso en GitHub.
Crea el servidor de MCP
- Configura una carpeta y un proyecto de Python para el servidor de 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 - Descarga el código del servidor de 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
Implementar el servidor de MCP en Cloud Run y registrarlo en Agent Registry
A continuación, crearás Dockerfile para el servidor y lo implementarás en 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 una cuenta de servicio designada para el servidor:
gcloud iam service-accounts create zoo-mcp-server-sa \ --display-name="Zoo MCP Server Service Account" - Implementa el servidor en la región
us-west1. Si ves el siguiente mensaje, confirma la creación de un nuevo repositorio de Artifact Registry. Este mensaje se mostrará si no tienes un repositorio de Artifact Registry en tu proyecto para la regióngcloud 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 el servidor de MCP en Agent Registry en la ubicación "global":
Estos comandos generanfastmcp 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 el manifiesto de la herramienta y llaman a la API de Agent Registry para registrar el servidor. - Verifica que el agente aparezca en el registro:
Verás un resultado en formato YAML con la información del servidor de MCP, como el siguiente: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 del ADK básico para usar el servidor de MCP del zoológico
En este paso, crearás un agente del ADK para responder las solicitudes de los usuarios sobre el zoológico con la ayuda de la URL codificada del servidor de MCP del zoológico. La codificación rígida te obliga a conocer la URL del servidor de MCP ANTES de que se implemente el agente.
Puedes encontrar los archivos de origen de este paso en GitHub.
Crea la aplicación de agente
Crear un esquema del proyecto del ADK y el agente del zoológico que usa el servidor de MCP del zoológico
- Configura una carpeta y un proyecto para el agente de 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 carpeta del paquete del agente y descarga el archivo
__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 el archivo
.envy propágalo con las variables de entorno que se usan en este paso y en los siguientes: Este comando escribe todas las variables de entorno necesarias en este paso y los pasos de descubrimiento posteriores enMCP_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, por lo que no es necesario volver a crear el archivo más adelante. En este paso, se usan las siguientes variables:GOOGLE_GENAI_USE_ENTERPRISE,GOOGLE_CLOUD_PROJECTyGOOGLE_CLOUD_LOCATION: El ADK y el SDK de GenAI de Google los usan de forma implícita para enrutar las llamadas al modelo de Gemini a través de Vertex AI en tu proyecto y ubicación de Google Cloud (global). También almacena la ubicación del registro de agentes en el que se registra el servidor de MCP.GOOGLE_CLOUD_PROJECT_NUMBER: No se usa en este paso; se completa previamente aquí para usarlo en el siguiente paso.MCP_SERVER_URL: Lee directamente enzoo_agent/agent.pypara configurar elMcpToolsetcon la URL de Cloud Run codificada del servidor de MCP del zoológico.
- Descarga
agent.pycon el código del agente del zoológico que se conecta al servidor de MCP del zoológico: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
Cómo funciona la autorización del servidor de MCP
Debido a que el servicio de Cloud Run zoo-mcp-server se implementó con las marcas --invoker-iam-check y --no-allow-unauthenticated, cada solicitud HTTP al servidor de MCP debe incluir un token de ID firmado por Google en el encabezado Authorization. El token debe identificar a un emisor que tenga el permiso run.routes.invoke en el servicio.
En zoo_agent/agent.py, la clase _GoogleAuthHeaders obtiene credenciales de token de ID de una de dos maneras. Primero, llama a fetch_id_token_credentials() para obtener un token de ID para la URL del servicio desde el servidor de metadatos o una clave de cuenta de servicio. Si esto falla, usa el token de ID de tus credenciales predeterminadas de la aplicación (ADC) de usuario. Cada vez que se llama a la instancia, se actualiza el token si es necesario y se devuelve el encabezado 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}"}
El agente pasa una instancia de la clase a McpToolset como el argumento header_provider. El ADK llama al proveedor de encabezados y adjunta los encabezados que devuelve a cada solicitud al servidor de MCP. Como el cliente HTTP lo crea el ADK, el mismo código funciona independientemente de la versión del SDK de MCP para Python y su biblioteca HTTP que esté instalada:
zoo_mcp_toolset = McpToolset(
connection_params=StreamableHTTPConnectionParams(
url=f"{MCP_SERVER_URL}/mcp",
timeout=30.0,
),
header_provider=_GoogleAuthHeaders(MCP_SERVER_URL),
)
El público solicitado es la URL del servicio (MCP_SERVER_URL), mientras que la URL de conexión incluye la ruta de acceso /mcp en la que el servidor de FastMCP entrega el protocolo de MCP. El tiempo de espera de 30 segundos deja espacio para los inicios en frío de Cloud Run.
Prueba el agente con la zona de pruebas del ADK en tu entorno local
Antes de probar el agente, asegúrate de que tu cuenta de usuario pueda llamar al servidor de MCP.
- Solo para la terminal local (omite este paso en Cloud Shell): Inicializa las ADC con tus credenciales de usuario:
Sigue las instrucciones para completar la inicialización de ADC.gcloud auth application-default login - Opcional: Otorga permiso a tu cuenta de usuario para llamar al servidor de 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' - Ejecuta tu agente con el comando
adk run: o ejecuta la interfaz web de ADK con el comandoadk run zoo_agentadk web: y, luego, abre la URL que imprime el comando en el navegador.Si usas Cloud Shell, agrega el argumentoadk web --port 8080--allow_originspara permitir que se abra la interfaz web desde Cloud Shell: Después de ejecutar el comando, busca el ícono de Vista previa en la Webadk web --port 8080 --allow_origins="regex:https://.*\.cloudshell\.dev"
en la barra de herramientas de Cloud Shell (lado derecho). Haz clic en él y selecciona Vista previa en el puerto 8080. Cloud Shell abrirá una nueva pestaña del navegador que mostrará la interfaz web de ADK. - Comienza a chatear con el agente. Por ejemplo, pregúntale sobre los animales del zoológico:
El agente llama aHow many animals are in the zoo?list_speciesy, luego, aget_animals_by_speciespara cada especie con el objetivo de calcular la respuesta.
La interfaz web del ADK te permite explorar cómo funciona el agente, incluidas sus llamadas al servidor de MCP del zoológico. Consulta la documentación de la IU web para obtener más información.
5. Descubre de forma dinámica el extremo del servidor de MCP
En este paso, cambiarás el agente para que descubra el extremo del servidor de MCP de Zoo registrado en Agent Registry en lugar de codificar su URL de extremo.
Puedes encontrar todos los archivos de este paso en GitHub.
Usa la integración del ADK con Agent Registry para buscar y recuperar la URL del servidor. Para implementarlo, necesitas la siguiente información:
- Es el número de proyecto y la ubicación de Agent Registry en el que está registrado tu servidor de MCP (
global). - El ID de servicio que usaste para registrar el servidor de MCP (
zoo-mcp-server).
Actualiza el agente para usar el Registro de agentes
- Descarga el archivo
agent.pyactualizado desde GitHub para anular la primera versión del 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.pyVariables de entorno en este paso
Con esta actualización, el agente se basa en las siguientes variables de entorno dezoo_agent/.env:GOOGLE_CLOUD_PROJECT_NUMBER: Ahora es obligatorio para construir el URN de identidad del servidor de MCP (MCP_SERVER_URN).GOOGLE_CLOUD_PROJECT,GOOGLE_GENAI_USE_ENTERPRISEyGOOGLE_CLOUD_LOCATION: El ADK los usa para configurar el acceso a Vertex AI. La ubicación también se usa para descubrir el servidor de MCP.- Ya no es obligatorio: Ya no se usa
MCP_SERVER_URLporque el agente recupera la URL del extremo del servidor de MCP de Agent Registry en lugar de leerla del entorno.
Cómo funcionan la identidad y el descubrimiento del servidor de MCP en
En el Registro de agentes, cada servidor de MCP registrado tiene un identificador único almacenado en su campoAgentRegistrymcpServerId, con el formato de un nombre de recurso uniforme (URN). Puedes verlo en el resultado del comandogcloud agent-registry mcp-servers listen el paso 3. El agente reconstruye este identificador a partir de las variables de entorno: El URN codifica el número de proyecto del registro (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}), el número de proyecto y la ubicación en la que está registrado el servidor (projects:{PROJECT_NUMBER}:locations:{LOCATION}), y el ID de servicio que usaste en el comandogcloud agent-registry services create(agentregistry:services:zoo-mcp-server).El agente enumera los servidores de MCP registrados conregistry.list_mcp_servers()y hace coincidir elmcpServerIdde cada servidor conMCP_SERVER_URN. Luego, pasa elname(projects/.../locations/.../mcpServers/...) del recurso del servidor coincidente aregistry.get_mcp_toolset(), que lee la URL del extremo de la interfaz registrada del servidor (https://...run.app/mcp) y devuelve unMcpToolsetconectado a esa 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()antepone el nombre visible del servidor a los nombres de las herramientas, por lo que el agente ve las herramientas comozoo_mcp_server_list_species,zoo_mcp_server_get_animals_by_speciesyzoo_mcp_server_get_animal_details. El prefijo evita colisiones de nombres cuando un agente usa herramientas de varios servidores de MCP. La última línea aumenta el tiempo de espera de la conexión de su valor predeterminado de 5 segundos para dejar espacio para los inicios en frío de Cloud Run.Cómo cambia la autorización con
AgentRegistryAgentRegistryautentica sus propias llamadas a la API, comolist_mcp_servers(), con ADC, por lo que el proveedor de encabezados solo es necesario para las llamadas al servidor de MCP descubierto. ComoAgentRegistrycrea elMcpToolsetde forma interna, pasas el proveedor de encabezados al constructor deAgentRegistry.En el paso anterior, el público era el valor deMCP_SERVER_URLque conocías de antemano. Ahora, la URL solo se conoce después del descubrimiento. El ADK llama al proveedor de encabezados cuando el agente envía una solicitud al servidor de MCP, no cuando se creaAgentRegistry. Esto permite que el agente cree primero el proveedor y lo vincule al extremo descubierto después de que se devuelvaget_mcp_toolset(). El métodobind()obtiene las credenciales de la misma manera que el constructor en el paso anterior, con el público tomado de la URL del extremo descubierto sin la ruta de acceso/mcp: El métododef 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__es el mismo que en el paso anterior, excepto que genera un error si se llama antes debind(). - Ejecuta tu agente para probar el cambio. Usa el comando
adk run: o ejecuta la interfaz web de ADK con el comandoadk run zoo_agentadk web: y, luego, abre la URL que imprime el comando en el navegador.Si usas Cloud Shell, agrega el argumentoadk web --port 8080--allow_originspara permitir que se abra la interfaz web desde Cloud Shell: Hazle al agente la misma pregunta que en el paso anterior. La respuesta es la misma, pero en la interfaz web del ADK, las llamadas a herramientas ahora aparecen comoadk web --port 8080 --allow_origins="regex:https://.*\.cloudshell\.dev"zoo_mcp_server_list_speciesyzoo_mcp_server_get_animals_by_species.
El cambio te permitió omitir la necesidad de la URL explícita del servidor de MCP. Sin embargo, aún necesitas conocer el número de proyecto, la ubicación del registro y el ID de servicio que se usó para registrar el servidor de MCP, y el descubrimiento se realiza de forma estática durante la inicialización del agente.
6. Usa la búsqueda semántica para el servidor de MCP en el descubrimiento dinámico
En este paso, mejorarás el descubrimiento dinámico del servidor de MCP reemplazando la coincidencia exacta de URN en el inicio por la búsqueda semántica en el entorno de ejecución. Este enfoque elimina la necesidad de conocer el número de proyecto en el que se implementa el servidor o la identidad exacta del servidor (MCP_SERVER_URN), y, además, integra el descubrimiento de herramientas directamente en el flujo de trabajo de entorno de ejecución estándar del agente, en lugar de controlarlo en la inicialización.
Puedes encontrar todos los archivos de este paso en GitHub.
Actualiza el agente para usar el descubrimiento semántico en el entorno de ejecución
- Descarga el archivo
agent.pyactualizado de GitHub para reemplazar la implementación existente: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.pyVariables de entorno en este paso
Como el agente ya no construye un URN de servidor de MCP, la configuración requerida se simplifica aún más:GOOGLE_CLOUD_PROJECT,GOOGLE_GENAI_USE_ENTERPRISEyGOOGLE_CLOUD_LOCATION: El ADK los usa para configurar el acceso a Vertex AI. La ubicación también se usa para descubrir el servidor de MCP.- Ya no es obligatorio: Ya no se necesita
GOOGLE_CLOUD_PROJECT_NUMBER(además deMCP_SERVER_URL, que ya se quitó en el paso anterior).
Cambia de la concordancia exacta a la búsqueda semántica
En lugar de llamar aregistry.list_mcp_servers()durante la inicialización del módulo y comparar cada entrada con unMCP_SERVER_URNconstruido,agent.pyahora define una función de herramienta de descubrimientodiscover_zoo_mcp_serversque llama aregistry.search_mcp_servers()consearch_type="SEMANTIC": Esto permite que el agente busque en el Registro de agentes durante el entorno de ejecución con una descripción en lenguaje natural de las capacidades que necesita (por ejemplo,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") y guarda los nombres de los recursos del servidor descubiertos en el estado de la sesión del ADK (tool_context.state).Cómo se integra el descubrimiento dinámico en el flujo de trabajo del agente
Para activar herramientas de servidores descubiertos en el entorno de ejecución y guiar al agente a través del flujo de descubrimiento y, luego, invocación,agent.pycombina una implementación personalizada deBaseToolset(RegistryDiscoveredToolset) con un proveedor de instrucciones dinámico (zoo_guide_instruction):- Conjunto de herramientas dinámico (
RegistryDiscoveredToolset): Crea subclases deBaseToolsety estableceself._use_invocation_cache = Falsepara que el ADK vuelva a evaluar las herramientas disponibles inmediatamente después de quediscover_zoo_mcp_serversactualice el estado de la sesión, en lugar de almacenar en caché una lista de herramientas inicialmente vacía. Cuando se llama aget_tools(), lee los nombres de los servidores descubiertos desde el estado, compila unMcpToolsetautenticado para cada servidor en un subproceso de trabajador y devuelve todas las herramientas activas. Si no se puede acceder a un servidor, los demás no se verán afectados._build_toolset()realiza una E/S de bloqueo, por lo que_tools_of()la ejecuta en un subproceso de trabajo.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] - Instrucción dinámica del agente (
zoo_guide_instruction): Evalúa el estado de la sesión antes de cada turno del modelo. Cuando aún no se descubrió ningún servidor, se agrega_DISCOVERY_PROMPTpara indicarle al agente que llame primero adiscover_zoo_mcp_servers. Una vez que el descubrimiento se realiza correctamente, actualiza la instrucción para enumerar los servidores de MCP activos y le indica al agente que use las herramientas del zoológico activadas directamente sin volver a ejecutar el descubrimiento: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." )
Autorización para servidores descubiertos de forma dinámica
La búsqueda semántica puede devolver varios servidores, y el ADK llama a un proveedor de encabezados sin indicarle a qué servidor se dirige una solicitud. Por lo tanto,_build_toolset()crea un_GoogleAuthHeadersy unAgentRegistrydedicados para cada servidor descubierto y vincula el proveedor al extremo de ese servidor. El proveedor es el mismo que en el paso anterior, con una marca_boundque hace que falle si se usa antes debind().Lógica de retención y actualización
Para evitar consultar el registro de agentes en cada turno y, al mismo tiempo, actualizar periódicamente los extremos descubiertos,agent.pyimplementa la retención de estado con un tiempo de actividad (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- Retención con alcance de aplicación: Los nombres de los servidores descubiertos y una marca de tiempo de
discovered_atse almacenan en el estado de la sesión con la claveapp:zoo_mcp_servers. El uso de los permisos de prefijoapp:de ADK limita el alcance del registro de descubrimiento almacenado en caché durante la ejecución de la aplicación. - Validación de TTL y actualización automática:
_fresh_discovery()verifica que el registro guardado exista y que su antigüedad no supere lasDISCOVERY_TTL_SECONDS(24 horas). Mientras el registro esté actualizado,RegistryDiscoveredToolsetreutilizará las conexionesMcpToolsetalmacenadas en caché (self._toolsets),zoo_guide_instructionomitirá el mensaje de descubrimiento y cualquier llamada repetida adiscover_zoo_mcp_serversdevolverá"already_discovered"de inmediato. Una vez que vence el TTL de 24 horas,_fresh_discovery()devuelveNone, lo que hace queRegistryDiscoveredToolset.get_tools()cierre las sesiones de cliente de MCP existentes (await self.close()) y quezoo_guide_instructionle solicite al agente que realice un descubrimiento nuevo.
- Ejecuta tu agente para probar el flujo de trabajo de descubrimiento semántico. Usa el comando
adk run: o ejecuta la interfaz web de ADK con el comandoadk run zoo_agentadk web: y, luego, abre la URL que imprime el comando en el navegador.Si usas Cloud Shell, agrega el argumentoadk web --port 8080--allow_originspara permitir que se abra la interfaz web desde Cloud Shell: Cuando le hagas una pregunta al agente sobre el zoológico (por ejemplo,adk web --port 8080 --allow_origins="regex:https://.*\.cloudshell\.dev"How many animals are in the zoo?), observa que primero invocadiscover_zoo_mcp_servers, activa de forma dinámica las herramientas del servidor de MCP descubierto y, luego, llama a las herramientas del zoológico, comozoo_mcp_server_list_speciesyzoo_mcp_server_get_animals_by_species, para responder tu pregunta.Haz una pregunta de seguimiento, por ejemplo,Which is the oldest lion?. El agente responde con las herramientas del zoológico ya activadas y no vuelve a llamar adiscover_zoo_mcp_servers, porque el registro de descubrimiento en el estado de la sesión aún está actualizado.
7. Limpia
Opción 1: Borra el proyecto
Si creaste un proyecto dedicado para este codelab, la forma más fácil de eliminar la facturación es borrar el proyecto:
gcloud projects delete "${GOOGLE_CLOUD_PROJECT}" --quiet
Opción 2: Borra los recursos individuales
Si usaste un proyecto existente y quieres conservarlo, borra los recursos individuales que creaste durante este codelab:
- Borrar el registro del servidor de MCP de Agent Registry Como registraste el servidor de forma manual, borrar el servicio de Cloud Run no quita el registro:
gcloud agent-registry services delete zoo-mcp-server \ --location=global \ --project="${GOOGLE_CLOUD_PROJECT}" \ --quiet - Borra el servicio del servidor de MCP de Cloud Run:
gcloud run services delete zoo-mcp-server \ --region=us-west1 \ --project="${GOOGLE_CLOUD_PROJECT}" \ --quiet - Borra la cuenta de servicio creada para el servidor de MCP:
gcloud iam service-accounts delete \ "zoo-mcp-server-sa@${GOOGLE_CLOUD_PROJECT}.iam.gserviceaccount.com" \ --project="${GOOGLE_CLOUD_PROJECT}" \ --quiet - Si el repositorio de Artifact Registry
cloud-run-source-deployse creó específicamente durante este codelab, bórralo para evitar cargos por almacenamiento de imágenes de contenedor:gcloud artifacts repositories delete cloud-run-source-deploy \ --location=us-west1 \ --project="${GOOGLE_CLOUD_PROJECT}" \ --quiet - De manera opcional, quita el directorio del proyecto local creado en Cloud Shell:
cd ~ && rm -rf adk-dynamic-discovery
8. Felicitaciones
En este codelab, aprendiste a descubrir y usar de forma dinámica servidores de MCP con el Registro de agentes de Google Cloud y el Kit de desarrollo de agentes (ADK):
- Registraste un servidor de MCP: Implementaste un servidor de MCP seguro en Cloud Run y lo registraste en Agent Registry.
- Descubriste un servidor de MCP por identidad: Resolviste el extremo del servidor de MCP desde Agent Registry en lugar de codificar su URL de forma fija.
- Descubrimiento semántico de servidores de MCP: Permite que el agente encuentre y active herramientas de MCP en el entorno de ejecución con la búsqueda en lenguaje natural, con retención y actualización.