Detección dinámica de servidores de MCP con Agent Registry y el ADK

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

  1. En la página del selector de proyectos de la consola de Google Cloud, crea un proyecto de Google Cloud.
  2. 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

  1. Con el selector de recursos, selecciona el proyecto de Google Cloud que creaste.
  2. Haz clic en Activar Cloud Shell Ícono de Activar Cloud Shell en la parte superior de la consola de Google Cloud.
  3. Una vez que te conectes a Cloud Shell, verifica tu autenticación:
    gcloud auth list
    
  4. Confirma que tu proyecto esté configurado:
    gcloud config get project
    
  5. 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

  1. 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

  1. 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

  1. 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
    
  2. 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.

  1. 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
    
  2. 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"
    
  3. Implementa el servidor en la región us-west1.
    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-unauthenticated
    
    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ón us-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)?
    
  4. Registra el servidor de MCP en Agent Registry en la ubicación "global":
    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"
    
    Estos comandos generan toolspec.json con el manifiesto de la herramienta y llaman a la API de Agent Registry para registrar el servidor.
  5. Verifica que el agente aparezca en el registro:
    gcloud agent-registry mcp-servers list \
        --location=global \
        --filter="display_name=zoo-mcp-server"
    
    Verás un resultado en formato YAML con la información del servidor de MCP, como el siguiente:
    ---
    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

  1. 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
    
  2. 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
    
  3. Crea el archivo .env y propágalo con las variables de entorno que se usan en este paso y en los siguientes:
    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}"
    EOF
    
    Este comando escribe todas las variables de entorno necesarias en este paso y los pasos de descubrimiento posteriores en zoo_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_PROJECT y GOOGLE_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 en zoo_agent/agent.py para configurar el McpToolset con la URL de Cloud Run codificada del servidor de MCP del zoológico.
  4. Descarga agent.py con 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.

  1. Solo para la terminal local (omite este paso en Cloud Shell): Inicializa las ADC con tus credenciales de usuario:
    gcloud auth application-default login
    
    Sigue las instrucciones para completar la inicialización de ADC.
  2. 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'
    
  3. Ejecuta tu agente con el comando adk run:
    adk run zoo_agent
    
    o ejecuta la interfaz web de ADK con el comando adk web:
    adk web --port 8080
    
    y, luego, abre la URL que imprime el comando en el navegador.Si usas Cloud Shell, agrega el argumento --allow_origins para permitir que se abra la interfaz web desde Cloud Shell:
    adk web --port 8080 --allow_origins="regex:https://.*\.cloudshell\.dev"
    
    Después de ejecutar el comando, busca el ícono de Vista previa en la WebÍcono de vista previa en la Web 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.
  4. Comienza a chatear con el agente. Por ejemplo, pregúntale sobre los animales del zoológico:
    How many animals are in the zoo?
    
    El agente llama a list_species y, luego, a get_animals_by_species para 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

  1. Descarga el archivo agent.py actualizado 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.py
    

    Variables de entorno en este paso

    Con esta actualización, el agente se basa en las siguientes variables de entorno de zoo_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_ENTERPRISE y GOOGLE_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_URL porque 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 AgentRegistry

    En el Registro de agentes, cada servidor de MCP registrado tiene un identificador único almacenado en su campo mcpServerId, con el formato de un nombre de recurso uniforme (URN). Puedes verlo en el resultado del comando gcloud agent-registry mcp-servers list en el paso 3. El agente reconstruye este identificador a partir de las variables de entorno:
    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"
    
    El URN codifica el número de proyecto del registro (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 comando gcloud agent-registry services create (agentregistry:services:zoo-mcp-server).El agente enumera los servidores de MCP registrados con registry.list_mcp_servers() y hace coincidir el mcpServerId de cada servidor con MCP_SERVER_URN. Luego, pasa el name (projects/.../locations/.../mcpServers/...) del recurso del servidor coincidente a registry.get_mcp_toolset(), que lee la URL del extremo de la interfaz registrada del servidor (https://...run.app/mcp) y devuelve un McpToolset conectado 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.0
    
    get_mcp_toolset() antepone el nombre visible del servidor a los nombres de las herramientas, por lo que el agente ve las herramientas como zoo_mcp_server_list_species, zoo_mcp_server_get_animals_by_species y zoo_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 AgentRegistry

    AgentRegistry autentica sus propias llamadas a la API, como list_mcp_servers(), con ADC, por lo que el proveedor de encabezados solo es necesario para las llamadas al servidor de MCP descubierto. Como AgentRegistry crea el McpToolset de forma interna, pasas el proveedor de encabezados al constructor de AgentRegistry.En el paso anterior, el público era el valor de MCP_SERVER_URL que 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 crea AgentRegistry. Esto permite que el agente cree primero el proveedor y lo vincule al extremo descubierto después de que se devuelva get_mcp_toolset(). El método bind() 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:
        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
    
    El método __call__ es el mismo que en el paso anterior, excepto que genera un error si se llama antes de bind().
  2. Ejecuta tu agente para probar el cambio. Usa el comando adk run:
    adk run zoo_agent
    
    o ejecuta la interfaz web de ADK con el comando adk web:
    adk web --port 8080
    
    y, luego, abre la URL que imprime el comando en el navegador.Si usas Cloud Shell, agrega el argumento --allow_origins para permitir que se abra la interfaz web desde Cloud Shell:
    adk web --port 8080 --allow_origins="regex:https://.*\.cloudshell\.dev"
    
    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 como zoo_mcp_server_list_species y zoo_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

  1. Descarga el archivo agent.py actualizado 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.py
    

    Variables 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_ENTERPRISE y GOOGLE_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 de MCP_SERVER_URL, que ya se quitó en el paso anterior).
    En lugar de llamar a registry.list_mcp_servers() durante la inicialización del módulo y comparar cada entrada con un MCP_SERVER_URN construido, agent.py ahora define una función de herramienta de descubrimiento discover_zoo_mcp_servers que llama a registry.search_mcp_servers() con search_type="SEMANTIC":
    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,
        }
    
    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, "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.py combina una implementación personalizada de BaseToolset (RegistryDiscoveredToolset) con un proveedor de instrucciones dinámico (zoo_guide_instruction):
    1. Conjunto de herramientas dinámico (RegistryDiscoveredToolset): Crea subclases de BaseToolset y establece self._use_invocation_cache = False para que el ADK vuelva a evaluar las herramientas disponibles inmediatamente después de que discover_zoo_mcp_servers actualice el estado de la sesión, en lugar de almacenar en caché una lista de herramientas inicialmente vacía. Cuando se llama a get_tools(), lee los nombres de los servidores descubiertos desde el estado, compila un McpToolset autenticado 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]
      
    2. 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_PROMPT para indicarle al agente que llame primero a discover_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 _GoogleAuthHeaders y un AgentRegistry dedicados 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 _bound que hace que falle si se usa antes de bind().

    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.py implementa 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_at se almacenan en el estado de la sesión con la clave app:zoo_mcp_servers. El uso de los permisos de prefijo app: 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 las DISCOVERY_TTL_SECONDS (24 horas). Mientras el registro esté actualizado, RegistryDiscoveredToolset reutilizará las conexiones McpToolset almacenadas en caché (self._toolsets), zoo_guide_instruction omitirá el mensaje de descubrimiento y cualquier llamada repetida a discover_zoo_mcp_servers devolverá "already_discovered" de inmediato. Una vez que vence el TTL de 24 horas, _fresh_discovery() devuelve None, lo que hace que RegistryDiscoveredToolset.get_tools() cierre las sesiones de cliente de MCP existentes (await self.close()) y que zoo_guide_instruction le solicite al agente que realice un descubrimiento nuevo.
  2. Ejecuta tu agente para probar el flujo de trabajo de descubrimiento semántico. Usa el comando adk run:
    adk run zoo_agent
    
    o ejecuta la interfaz web de ADK con el comando adk web:
    adk web --port 8080
    
    y, luego, abre la URL que imprime el comando en el navegador.Si usas Cloud Shell, agrega el argumento --allow_origins para permitir que se abra la interfaz web desde Cloud Shell:
    adk web --port 8080 --allow_origins="regex:https://.*\.cloudshell\.dev"
    
    Cuando le hagas una pregunta al agente sobre el zoológico (por ejemplo, How many animals are in the zoo?), observa que primero invoca discover_zoo_mcp_servers, activa de forma dinámica las herramientas del servidor de MCP descubierto y, luego, llama a las herramientas del zoológico, como zoo_mcp_server_list_species y zoo_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 a discover_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:

  1. 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
    
  2. 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
    
  3. 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
    
  4. Si el repositorio de Artifact Registry cloud-run-source-deploy se 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
    
  5. 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):

  1. Registraste un servidor de MCP: Implementaste un servidor de MCP seguro en Cloud Run y lo registraste en Agent Registry.
  2. 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.
  3. 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.

Próximos pasos