Descoberta dinâmica de servidores MCP com o Agent Registry e o ADK

1. Introdução

Neste codelab, você vai aprender a usar o Registro de Agente do Google Cloud e o Kit de Desenvolvimento de Agente (ADK) para descobrir e invocar dinamicamente servidores do Protocolo de Contexto de Modelo (MCP) registrados no ambiente de execução, eliminando endpoints e configurações de ferramentas codificados.

Este laboratório usa o ADK e a linguagem de programação Python para criar um aplicativo. Os mesmos princípios podem ser aplicados em outras linguagens de programação e usando outros frameworks de desenvolvimento de agentes.

Atividades deste laboratório

  • Ativar e configurar o Google Cloud Agent Registry no seu projeto
  • Implantar um servidor MCP no Cloud Run e registrá-lo no Agent Registry
  • Criar um agente do ADK que chama o servidor MCP usando um URL fixado no código
  • Mudar o agente para descobrir o servidor MCP no Agent Registry pela identidade dele
  • Mudar o agente para descobrir servidores MCP no ambiente de execução usando a pesquisa semântica

O que é necessário

  • Um navegador da Web moderno (como o Chrome)
  • Tenha um projeto do Google Cloud com o faturamento ativado.
  • Uma conta com permissões para criar e gerenciar recursos e configurar políticas do IAM no projeto na nuvem do Google Cloud

2. Configurar o ambiente

As instruções neste codelab pressupõem que você execute comandos no Cloud Shell no console do Google Cloud. O Cloud Shell é um ambiente de linha de comando executado no Google Cloud que vem pré-carregado com as ferramentas necessárias. Se você tiver a CLI da gcloud, o git e o Python 3 instalados no ambiente local, também será possível executar os comandos nele.

Criar um projeto do Google Cloud

  1. No Console do Google Cloud, na página do seletor de projetos, crie um projeto do Google Cloud.
  2. Verifique se o faturamento está ativado para seu projeto do Cloud. Saiba como verificar se o faturamento está ativado em um projeto.

Iniciar um terminal do Cloud Shell

  1. Usando o seletor de recursos, selecione o projeto do Google Cloud que você criou.
  2. Clique em Ativar o Cloud Shell Ícone "Ativar Cloud Shell" na parte de cima do console do Google Cloud.
  3. Depois de se conectar ao Cloud Shell, verifique sua autenticação:
    gcloud auth list
    
  4. Confirme se o projeto está configurado:
    gcloud config get project
    
  5. Se o projeto não estiver definido como esperado ou se você usar um ambiente na máquina local em vez do Cloud Shell, configure o ID do projeto:
    export GOOGLE_CLOUD_PROJECT=<YOUR_PROJECT_ID>
    gcloud config set project "${GOOGLE_CLOUD_PROJECT}"
    

Selecionar um projeto na nuvem do Google Cloud e definir variáveis de ambiente

  1. Ative as APIs do Google Cloud necessárias:
    gcloud services enable \
      agentregistry.googleapis.com \
      aiplatform.googleapis.com \
      cloudbuild.googleapis.com \
      run.googleapis.com \
      --project="${GOOGLE_CLOUD_PROJECT}"
    

Configure o ambiente Python

  1. Crie e ative um ambiente virtual e instale as dependências necessárias:
    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. Criar e registrar um servidor MCP do Zoo

Nesta etapa, você vai criar e registrar um servidor MCP do zoológico. O servidor expõe três ferramentas: list_species, que lista todas as espécies que vivem no zoológico; get_animals_by_species, que lista os animais de uma espécie específica; e get_animal_details, que recebe mais informações sobre um animal específico.

Todos os arquivos desta etapa estão no GitHub.

Criar o servidor MCP

  1. Configurar uma pasta e um projeto Python para o servidor 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. Faça o download do código do servidor 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
    

Implantar o servidor MCP no Cloud Run e registrar no Agent Registry

Em seguida, você vai criar Dockerfile para o servidor e implantá-lo no Cloud Run.

  1. Crie 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. Crie uma conta de serviço designada para o servidor:
    gcloud iam service-accounts create zoo-mcp-server-sa \
        --display-name="Zoo MCP Server Service Account"
    
  3. Implante o servidor na região 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
    
    Se o prompt a seguir aparecer, confirme a criação de um novo repositório do Artifact Registry. Essa solicitação vai aparecer se você não tiver um repositório do Artifact Registry no projeto para a região 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. Registre o servidor MCP no Agent Registry no local "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"
    
    Esses comandos geram toolspec.json com o manifesto da ferramenta e chamam a API Agent Registry para registrar o servidor.
  5. Verifique se o agente aparece no registro:
    gcloud agent-registry mcp-servers list \
        --location=global \
        --filter="display_name=zoo-mcp-server"
    
    Você vai ver uma saída YAML com as informações do servidor MCP, como esta:
    ---
    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. Criar um agente básico do ADK para usar o servidor MCP do zoológico

Nesta etapa, você vai criar um agente do ADK para responder às solicitações dos usuários sobre o zoológico usando o URL fixado no código do servidor MCP do zoológico. A codificação exige que você saiba o URL do servidor MCP ANTES da implantação do agente.

Os arquivos de origem desta etapa estão no GitHub.

Criar o aplicativo do agente

Criar um scaffold do projeto do ADK e do agente do zoológico que usa o servidor MCP do zoológico.

  1. Configure uma pasta e um projeto para o agente do 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. Crie a pasta do pacote do agente e faça o download do arquivo __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. Crie o arquivo .env e preencha-o com as variáveis de ambiente que serão usadas nesta etapa e nas seguintes:
    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
    
    Esse comando grava todas as variáveis de ambiente necessárias nesta etapa e nas etapas de descoberta subsequentes em zoo_agent/.env, para que você não precise recriar o arquivo mais tarde. Nesta etapa, as seguintes variáveis são usadas:
    • GOOGLE_GENAI_USE_ENTERPRISE, GOOGLE_CLOUD_PROJECT e GOOGLE_CLOUD_LOCATION: usados implicitamente pelo ADK e pelo SDK do Google GenAI para rotear chamadas de modelo do Gemini pela Vertex AI no projeto na nuvem e no local do Google Cloud (global). Ele também armazena o local do Agent Registry em que o servidor MCP está registrado.
    • GOOGLE_CLOUD_PROJECT_NUMBER: não é usado nesta etapa. Ele é pré-preenchido aqui para uso na próxima etapa.
    • MCP_SERVER_URL: leia diretamente em zoo_agent/agent.py para configurar o McpToolset com o URL do Cloud Run fixado no código do servidor MCP do zoológico.
  4. Faça o download de agent.py com o código do agente do zoológico que se conecta ao servidor MCP do 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
    

Como funciona a autorização do servidor MCP

Como o serviço do Cloud Run zoo-mcp-server foi implantado com as flags --invoker-iam-check e --no-allow-unauthenticated, cada solicitação HTTP para o servidor MCP precisa incluir um token de ID assinado pelo Google no cabeçalho Authorization. O token precisa identificar um autor da chamada que tenha a permissão run.routes.invoke no serviço.

No zoo_agent/agent.py, a classe _GoogleAuthHeaders recebe credenciais de token de ID de duas maneiras. Primeiro, ele chama fetch_id_token_credentials() para receber um token de ID para o URL do serviço do servidor de metadados ou uma chave de conta de serviço. Se isso falhar, ele vai usar o token de ID do usuário do Application Default Credentials (ADC). Sempre que a instância é chamada, ela atualiza o token, se necessário, e retorna o cabeçalho 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}"}

O agente transmite uma instância da classe para McpToolset como o argumento header_provider. O ADK chama o provedor de cabeçalhos e anexa os cabeçalhos retornados a cada solicitação ao servidor MCP. Como o cliente HTTP é criado pelo ADK, o mesmo código funciona independentemente da versão do SDK do MCP para Python e da biblioteca HTTP instalada:

zoo_mcp_toolset = McpToolset(
    connection_params=StreamableHTTPConnectionParams(
        url=f"{MCP_SERVER_URL}/mcp",
        timeout=30.0,
    ),
    header_provider=_GoogleAuthHeaders(MCP_SERVER_URL),
)

O público-alvo solicitado é o URL do serviço (MCP_SERVER_URL), enquanto o URL de conexão inclui o caminho /mcp em que o servidor FastMCP disponibiliza o protocolo MCP. O tempo limite de 30 segundos deixa espaço para inicializações a frio do Cloud Run.

Testar o agente usando o ADK Playground no ambiente local

Antes de testar o agente, verifique se a conta de usuário pode chamar o servidor MCP.

  1. Somente terminal local (pule esta etapa no Cloud Shell): inicialize o ADC com suas credenciais de usuário:
    gcloud auth application-default login
    
    Siga as instruções para concluir a inicialização do ADC.
  2. (Opcional) Conceda à sua conta de usuário permissão para chamar o servidor 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. Execute o agente com o comando adk run:
    adk run zoo_agent
    
    ou execute a interface da Web do ADK com o comando adk web:
    adk web --port 8080
    
    e abra o URL que o comando mostra no navegador.Se você usa o Cloud Shell, adicione o argumento --allow_origins para permitir a abertura da interface da Web no Cloud Shell:
    adk web --port 8080 --allow_origins="regex:https://.*\.cloudshell\.dev"
    
    Depois de executar o comando, encontre o ícone Visualização da WebÍcone de visualização na Web na barra de ferramentas do Cloud Shell (lado direito). Clique nele e selecione Visualizar na porta 8080. O Cloud Shell vai abrir uma nova guia do navegador com a interface da Web do ADK.
  4. Comece a conversar com o agente. Por exemplo, pergunte sobre os animais no zoológico:
    How many animals are in the zoo?
    
    O agente chama list_species e depois get_animals_by_species para cada espécie para calcular a resposta.

A interface da Web do ADK permite que você explore como o agente funciona, incluindo as chamadas para o servidor MCP do zoológico. Consulte a documentação da interface da Web para saber como fazer isso.

5. Descobrir dinamicamente o endpoint do servidor MCP

Nesta etapa, você vai mudar o agente para descobrir o endpoint do servidor MCP do zoológico registrado no Agent Registry, em vez de fixar o URL do endpoint no código.

Todos os arquivos desta etapa estão no GitHub.

Use a integração do ADK com o Agent Registry para encontrar e recuperar o URL do servidor. Para implementar, você precisa das seguintes informações:

  • Número do projeto e local do Agent Registry em que o servidor MCP está registrado (global).
  • O ID de serviço que você usou para registrar o servidor MCP (zoo-mcp-server).

Atualizar o agente para usar o Agent Registry

  1. Baixe o arquivo agent.py atualizado do GitHub para substituir a primeira versão do 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
    

    Variáveis de ambiente nesta etapa

    Com essa atualização, o agente depende das seguintes variáveis de ambiente de zoo_agent/.env:
    • GOOGLE_CLOUD_PROJECT_NUMBER: agora é obrigatório para criar o URN de identidade do servidor MCP (MCP_SERVER_URN).
    • GOOGLE_CLOUD_PROJECT, GOOGLE_GENAI_USE_ENTERPRISE e GOOGLE_CLOUD_LOCATION: usados pelo ADK para configurar o acesso à Vertex AI. O local também é usado para descobrir o servidor MCP.
    • Não é mais necessário: MCP_SERVER_URL não é mais usado porque o agente recupera o URL do endpoint do servidor MCP do Agent Registry em vez de ler do ambiente.

    Como funcionam a identidade e a descoberta do servidor MCP no AgentRegistry

    No registro de agentes, cada servidor MCP registrado tem um identificador exclusivo armazenado no campo mcpServerId, formatado como um nome de recurso uniforme (URN). Ele aparece na saída do comando gcloud agent-registry mcp-servers list na Etapa 3. O agente reconstrói esse identificador com base nas variáveis de ambiente:
    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"
    
    O URN codifica o número do projeto do registro (projects-{PROJECT_NUMBER}), o número do projeto e o local em que o servidor está registrado (projects:{PROJECT_NUMBER}:locations:{LOCATION}) e o ID do serviço que você usou no comando gcloud agent-registry services create (agentregistry:services:zoo-mcp-server).O agente lista os servidores MCP registrados com registry.list_mcp_servers() e faz a correspondência do mcpServerId de cada servidor com MCP_SERVER_URN. Em seguida, ele transmite o name (projects/.../locations/.../mcpServers/...) do recurso do servidor correspondente para registry.get_mcp_toolset(), que lê o URL do endpoint na interface registrada do servidor (https://...run.app/mcp) e retorna um McpToolset conectado a esse 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() prefixa os nomes das ferramentas com o nome de exibição do servidor, para que o agente veja as ferramentas como zoo_mcp_server_list_species, zoo_mcp_server_get_animals_by_species e zoo_mcp_server_get_animal_details. O prefixo evita colisões de nomes quando um agente usa ferramentas de vários servidores MCP. A última linha aumenta o tempo limite de conexão do padrão de 5 segundos para deixar espaço para as inicializações a frio do Cloud Run.

    Como a autorização muda com o AgentRegistry

    O AgentRegistry autentica as próprias chamadas de API, como list_mcp_servers(), com o ADC. Portanto, o provedor de cabeçalho só é necessário para as chamadas ao servidor MCP descoberto. Como AgentRegistry cria o McpToolset internamente, você transmite o provedor de cabeçalho para o construtor AgentRegistry.Na etapa anterior, o público-alvo era o valor MCP_SERVER_URL que você já conhecia. Agora, o URL só é conhecido após a descoberta. O ADK chama o provedor de cabeçalho quando o agente envia uma solicitação ao servidor MCP, não quando AgentRegistry é criado. Isso permite que o agente crie o provedor primeiro e o vincule ao endpoint descoberto depois que get_mcp_toolset() retornar. O método bind() recebe as credenciais da mesma forma que o construtor na etapa anterior, com o público-alvo extraído do URL do endpoint descoberto sem o caminho /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
    
    O método __call__ é igual ao da etapa anterior, mas gera um erro se for chamado antes de bind().
  2. Execute o agente para testar a mudança. Use o comando adk run:
    adk run zoo_agent
    
    ou execute a interface da Web do ADK com o comando adk web:
    adk web --port 8080
    
    e abra o URL que o comando mostra no navegador.Se você usa o Cloud Shell, adicione o argumento --allow_origins para permitir a abertura da interface da Web no Cloud Shell:
    adk web --port 8080 --allow_origins="regex:https://.*\.cloudshell\.dev"
    
    Faça ao agente a mesma pergunta da etapa anterior. A resposta é a mesma, mas na interface da Web do ADK as chamadas de ferramenta agora aparecem como zoo_mcp_server_list_species e zoo_mcp_server_get_animals_by_species.

A mudança permitiu que você ignorasse a necessidade do URL explícito do servidor MCP. No entanto, você ainda precisa saber o número do projeto, o local do registro e o ID do serviço usado para registrar o servidor MCP. Além disso, a descoberta é realizada de forma estática durante a inicialização do agente.

6. Usar a pesquisa semântica para o servidor MCP na descoberta dinâmica

Nesta etapa, você vai melhorar a descoberta dinâmica do servidor MCP substituindo a correspondência exata de URN na inicialização pela pesquisa semântica em tempo de execução. Essa abordagem elimina a necessidade de saber o número do projeto em que o servidor está implantado ou a identidade exata do servidor (MCP_SERVER_URN) e integra a descoberta de ferramentas diretamente ao fluxo de trabalho de execução padrão do agente, em vez de lidar com isso na inicialização.

Todos os arquivos desta etapa estão no GitHub.

Atualizar o agente para usar a descoberta semântica no ambiente de execução

  1. Faça o download do arquivo agent.py atualizado do GitHub para substituir a implementação atual:
    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
    

    Variáveis de ambiente nesta etapa

    Como o agente não cria mais um URN de servidor MCP, a configuração necessária é ainda mais simplificada:
    • GOOGLE_CLOUD_PROJECT, GOOGLE_GENAI_USE_ENTERPRISE e GOOGLE_CLOUD_LOCATION: usados pelo ADK para configurar o acesso à Vertex AI. O local também é usado para descobrir o servidor MCP.
    • Não é mais necessário: GOOGLE_CLOUD_PROJECT_NUMBER não é mais necessário (além de MCP_SERVER_URL, que já foi removido na etapa anterior).
    Em vez de chamar registry.list_mcp_servers() durante a inicialização do módulo e comparar cada entrada com um MCP_SERVER_URN construído, agent.py agora define uma função de ferramenta de descoberta discover_zoo_mcp_servers que chama registry.search_mcp_servers() com 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,
        }
    
    Isso permite que o agente pesquise o Agent Registry em tempo de execução usando uma descrição em linguagem natural dos recursos necessários (por exemplo, "zoo animals, exhibits, habitat, care, feeding schedule") e salve os nomes dos recursos do servidor descobertos no estado da sessão do ADK (tool_context.state).

    Como a descoberta dinâmica é integrada ao fluxo de trabalho do agente

    Para montar ferramentas de servidores descobertos no momento da execução e guiar o agente pelo fluxo de descoberta e invocação, o agent.py combina uma implementação personalizada de BaseToolset (RegistryDiscoveredToolset) com um provedor de instruções dinâmicas (zoo_guide_instruction):
    1. Conjunto de ferramentas dinâmico (RegistryDiscoveredToolset): subclasses BaseToolset e conjuntos self._use_invocation_cache = False para que o ADK reavalie as ferramentas disponíveis imediatamente após discover_zoo_mcp_servers atualizar o estado da sessão, em vez de armazenar em cache uma lista de ferramentas inicialmente vazia. Quando get_tools() é chamado, ele lê os nomes de servidores descobertos do estado, cria um McpToolset autenticado para cada servidor em uma linha de execução de worker e retorna todas as ferramentas ativas. Se um servidor não for alcançado, os outros não serão afetados. _build_toolset() realiza uma E/S de bloqueio, então _tools_of() a executa em uma linha de execução de worker.
      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. Instrução de agente dinâmico (zoo_guide_instruction): avalia o estado da sessão antes de cada turno do modelo. Quando nenhum servidor é descoberto, ele anexa _DISCOVERY_PROMPT instruindo o agente a chamar discover_zoo_mcp_servers primeiro. Depois que a descoberta é concluída, ele atualiza o comando para listar os servidores MCP ativos e instrui o agente a usar as ferramentas do zoológico montadas diretamente, sem executar a descoberta novamente:
      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."
          )
      

    Autorização para servidores descobertos dinamicamente

    A pesquisa semântica pode retornar vários servidores, e o ADK chama um provedor de cabeçalho sem informar para qual servidor uma solicitação é enviada. Portanto, o _build_toolset() cria um _GoogleAuthHeaders e um AgentRegistry dedicados para cada servidor descoberto e vincula o provedor ao endpoint desse servidor. O provedor é o mesmo da etapa anterior, com uma flag _bound que faz com que ele falhe se for usado antes de bind().

    Lógica de retenção e atualização

    Para evitar consultar o registro de agentes em cada turno e, ao mesmo tempo, atualizar os endpoints descobertos periodicamente, o agent.py implementa a retenção de estado com um time to live (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
    
    • Retenção com escopo de aplicativo: os nomes de servidores descobertos e um carimbo de data/hora discovered_at são armazenados no estado da sessão na chave app:zoo_mcp_servers. O uso dos escopos de prefixo app: do ADK armazena em cache o registro de descoberta em toda a execução do aplicativo.
    • Validação de TTL e atualização automática: o _fresh_discovery() verifica se o registro salvo existe e se a idade dele não excede DISCOVERY_TTL_SECONDS (24 horas). Enquanto o registro estiver atualizado, o RegistryDiscoveredToolset vai reutilizar as conexões McpToolset armazenadas em cache (self._toolsets), o zoo_guide_instruction vai omitir o prompt de descoberta e qualquer chamada repetida para discover_zoo_mcp_servers vai retornar imediatamente "already_discovered". Quando o TTL de 24 horas expira, _fresh_discovery() retorna None, fazendo com que RegistryDiscoveredToolset.get_tools() feche as sessões de cliente MCP atuais (await self.close()) e zoo_guide_instruction solicite que o agente faça uma nova descoberta.
  2. Execute o agente para testar o fluxo de trabalho de descoberta semântica. Use o comando adk run:
    adk run zoo_agent
    
    ou execute a interface da Web do ADK com o comando adk web:
    adk web --port 8080
    
    e abra o URL que o comando mostra no navegador.Se você usa o Cloud Shell, adicione o argumento --allow_origins para permitir a abertura da interface da Web no Cloud Shell:
    adk web --port 8080 --allow_origins="regex:https://.*\.cloudshell\.dev"
    
    Quando você faz uma pergunta ao agente sobre o zoológico (por exemplo, How many animals are in the zoo?), ele primeiro invoca discover_zoo_mcp_servers, monta dinamicamente as ferramentas do servidor MCP descoberto e, em seguida, chama as ferramentas do zoológico, como zoo_mcp_server_list_species e zoo_mcp_server_get_animals_by_species, para responder à sua pergunta.Faça uma pergunta complementar, por exemplo, Which is the oldest lion?. O agente responde usando as ferramentas do zoológico já montadas e não chama discover_zoo_mcp_servers novamente, porque o registro de descoberta no estado da sessão ainda é recente.

7. Limpar

Opção 1: excluir o projeto

Se você criou um projeto dedicado para este codelab, a maneira mais fácil de eliminar o faturamento é excluir o projeto:

gcloud projects delete "${GOOGLE_CLOUD_PROJECT}" --quiet

Opção 2: excluir recursos individuais

Se você usou um projeto atual e quer mantê-lo, exclua os recursos individuais criados durante este codelab:

  1. Exclua o registro do servidor MCP do Agent Registry. Como você registrou o servidor manualmente, excluir o serviço do Cloud Run não remove o registro:
    gcloud agent-registry services delete zoo-mcp-server \
        --location=global \
        --project="${GOOGLE_CLOUD_PROJECT}" \
        --quiet
    
  2. Exclua o serviço do servidor MCP do Cloud Run:
    gcloud run services delete zoo-mcp-server \
        --region=us-west1 \
        --project="${GOOGLE_CLOUD_PROJECT}" \
        --quiet
    
  3. Exclua a conta de serviço criada para o servidor MCP:
    gcloud iam service-accounts delete \
        "zoo-mcp-server-sa@${GOOGLE_CLOUD_PROJECT}.iam.gserviceaccount.com" \
        --project="${GOOGLE_CLOUD_PROJECT}" \
        --quiet
    
  4. Se o repositório cloud-run-source-deploy do Artifact Registry foi criado especificamente durante este codelab, exclua-o para evitar cobranças de armazenamento de imagens de contêiner:
    gcloud artifacts repositories delete cloud-run-source-deploy \
        --location=us-west1 \
        --project="${GOOGLE_CLOUD_PROJECT}" \
        --quiet
    
  5. Se quiser, remova o diretório do projeto local criado no Cloud Shell:
    cd ~ && rm -rf adk-dynamic-discovery
    

8. Parabéns

Neste codelab, você aprendeu a descobrir e usar dinamicamente servidores MCP com o Google Cloud Agent Registry e o Kit de Desenvolvimento de Agente (ADK):

  1. Registrou um servidor MCP: implantou um servidor MCP seguro no Cloud Run e o registrou no Agent Registry.
  2. Descobriu um servidor MCP por identidade: resolveu o endpoint do servidor MCP no Agent Registry em vez de fixar o URL no código.
  3. Servidores MCP descobertos semanticamente: permita que o agente encontre e monte ferramentas MCP no ambiente de execução usando a pesquisa com linguagem natural, com retenção e atualização.

Próximas etapas