Agent Registry ve ADK ile Dinamik MCP Sunucusu Keşfi

1. Giriş

Bu codelab'de, çalışma zamanında kayıtlı Model Context Protocol (MCP) sunucularını dinamik olarak keşfetmek ve çağırmak için Google Cloud Agent Registry ve Agent Development Kit'i (ADK) nasıl kullanacağınızı öğreneceksiniz. Böylece, sabit kodlu uç noktaları ve araç yapılandırmalarını ortadan kaldırabilirsiniz.

Bu laboratuvarda, uygulama oluşturmak için ADK ve Python programlama dili kullanılır. Aynı ilkeler diğer programlama dillerinde ve diğer temsilci geliştirme çerçevelerinde de uygulanabilir.

Yapacaklarınız

  • Projenizde Google Cloud Agent Registry'yi etkinleştirme ve yapılandırma
  • Cloud Run'a MCP sunucusu dağıtma ve Agent Registry'ye kaydetme
  • Sabit kodlu bir URL kullanarak MCP sunucusunu çağıran bir ADK temsilcisi oluşturma
  • Temsilciyi, Agent Registry'deki MCP sunucusunu kimliğine göre bulacak şekilde değiştirin.
  • Temsilciyi, semantik aramayı kullanarak çalışma zamanında MCP sunucularını keşfedecek şekilde değiştirme

İhtiyacınız olanlar

  • Modern bir web tarayıcısı (ör. Chrome)
  • Faturalandırmanın etkin olduğu bir Google Cloud projesi
  • Google Cloud projesinde kaynak oluşturma ve yönetme ile IAM politikalarını yapılandırma izinlerine sahip bir hesap

2. Ortamınızı ayarlama

Bu codelab'deki talimatlarda, komutları Google Cloud Console'daki Cloud Shell'de çalıştırdığınız varsayılır. Cloud Shell, Google Cloud'da çalışan ve gerekli araçların önceden yüklendiği bir komut satırı ortamıdır. Yerel ortamınızda gcloud CLI, git ve Python 3 yüklüyse komutları orada da çalıştırabilirsiniz.

Google Cloud projesi oluşturma

  1. Google Cloud Console'da, proje seçici sayfasında bir Google Cloud projesi oluşturun.
  2. Cloud projeniz için faturalandırmanın etkinleştirildiğinden emin olun. Bir projede faturalandırmanın etkin olup olmadığını nasıl kontrol edeceğinizi öğrenin.

Cloud Shell terminali başlatma

  1. Kaynak seçiciyi kullanarak oluşturduğunuz Google Cloud projesini seçin.
  2. Google Cloud konsolunun üst kısmından Activate Cloud Shell (Cloud Shell'i etkinleştir) Cloud Shell'i etkinleştir simgesi simgesini tıklayın.
  3. Cloud Shell'e bağlandıktan sonra kimlik doğrulamanızı doğrulayın:
    gcloud auth list
    
  4. Projenizin yapılandırıldığını doğrulayın:
    gcloud config get project
    
  5. Projeniz beklendiği gibi ayarlanmadıysa veya Cloud Shell yerine yerel makinenizde bir ortam kullanıyorsanız proje kimliğini ayarlayın:
    export GOOGLE_CLOUD_PROJECT=<YOUR_PROJECT_ID>
    gcloud config set project "${GOOGLE_CLOUD_PROJECT}"
    

Google Cloud projesi seçme ve ortam değişkenlerini ayarlama

  1. Gerekli Google Cloud API'lerini etkinleştirin:
    gcloud services enable \
      agentregistry.googleapis.com \
      aiplatform.googleapis.com \
      cloudbuild.googleapis.com \
      run.googleapis.com \
      --project="${GOOGLE_CLOUD_PROJECT}"
    

Python ortamını ayarlama

  1. Sanal ortam oluşturup etkinleştirin ve gerekli bağımlılıkları yükleyin:
    mkdir -p adk-dynamic-discovery && cd adk-dynamic-discovery
    python3 -m venv .venv
    source .venv/bin/activate
    pip install "fastmcp<5.0.0" "google-adk[mcp,a2a,agent-identity]>=2.5.0,<3.0.0"
    

3. Zoo MCP sunucusu oluşturma ve kaydetme

Bu adımda bir Zoo MCP sunucusu oluşturup kaydedeceksiniz. Sunucu üç araç sunar: list_species (hayvanat bahçesinde yaşayan tüm türleri listeler), get_animals_by_species (belirli bir türün hayvanlarını listeler) ve get_animal_details (belirli bir hayvan hakkında ek bilgi alır).

Bu adımla ilgili tüm dosyaları GitHub'da bulabilirsiniz.

MCP sunucusunu oluşturma

  1. MCP sunucusu için bir klasör ve Python projesi oluşturun
    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. MCP sunucu kodunu indirin:
    curl -fsSL https://raw.githubusercontent.com/GoogleCloudPlatform/devrel-demos/main/codelabs/agent-registry-dynamic-discovery-with-adk/steps/step3/server.py -o server.py
    

MCP sunucusunu Cloud Run'a dağıtma ve Agent Registry'ye kaydetme

Ardından, sunucu için Dockerfile oluşturacak ve sunucuyu Cloud Run'a dağıtacaksınız.

  1. Dockerfile oluşturun:
    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. Sunucu için özel hizmet hesabı oluşturun:
    gcloud iam service-accounts create zoo-mcp-server-sa \
        --display-name="Zoo MCP Server Service Account"
    
  3. Sunucuyu us-west1 bölgesine dağıtın.
    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
    
    Aşağıdaki istemi görürseniz yeni bir Artifact Registry deposu oluşturma işlemini onaylayın. Projenizde us-west1 bölgesi için Artifact Registry deponuz yoksa bu istem gösterilir:
    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. MCP sunucusunu Agent Registry'de "global" konumda kaydedin:
    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"
    
    Bu komutlar, araç manifestiyle toolspec.json oluşturur ve sunucuyu kaydetmek için Agent Registry API'yi çağırır.
  5. Aracının kayıt defterinde göründüğünü doğrulayın:
    gcloud agent-registry mcp-servers list \
        --location=global \
        --filter="display_name=zoo-mcp-server"
    
    MCP sunucusu bilgilerini içeren YAML çıkışını görürsünüz:
    ---
    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. Zoo MCP sunucusunu kullanmak için temel bir ADK temsilcisi oluşturma

Bu adımda, sabit kodlu Zoo MCP sunucusu URL'sinin yardımıyla kullanıcıların hayvanat bahçesiyle ilgili isteklerini yanıtlamak için bir ADK aracısı oluşturacaksınız. Sabit kodlama, temsilci dağıtılmadan ÖNCE MCP sunucusunun URL'sini bilmenizi gerektirir.

Bu adımın kaynak dosyalarını GitHub'da bulabilirsiniz.

Temsilci uygulamasını oluşturma

ADK projesinin ve Zoo MCP sunucusunu kullanan Zoo temsilcisinin iskeletini oluşturun.

  1. Hayvanat bahçesi temsilcisi için bir klasör ve proje oluşturun:
    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. Aracı paketi klasörünü oluşturun ve __init__.py dosyasını indirin:
    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. .env dosyasını oluşturun ve bu adımda ve sonraki adımlarda kullanılan ortam değişkenleriyle doldurun:
    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
    
    Bu komut, bu adımda ve sonraki keşif adımlarında gerekli olan tüm ortam değişkenlerini zoo_agent/.env dosyasına yazar. Bu sayede, dosyayı daha sonra yeniden oluşturmanız gerekmez. Bu adımda aşağıdaki değişkenler kullanılır:
    • GOOGLE_GENAI_USE_ENTERPRISE, GOOGLE_CLOUD_PROJECT ve GOOGLE_CLOUD_LOCATION: ADK ve Google GenAI SDK tarafından, Gemini model çağrılarını Google Cloud projenizdeki ve konumunuzdaki (global) Vertex AI üzerinden yönlendirmek için dolaylı olarak kullanılır. Ayrıca, MCP sunucusunun kayıtlı olduğu Agent Registry konumunu da depolar.
    • GOOGLE_CLOUD_PROJECT_NUMBER: Bu adımda kullanılmaz. Bir sonraki adımda kullanılmak üzere burada önceden doldurulur.
    • MCP_SERVER_URL: McpToolset'yi Zoo MCP sunucusunun sabit kodlu Cloud Run URL'siyle yapılandırmak için doğrudan zoo_agent/agent.py dosyasını okuyun.
  4. Hayvanat bahçesi MCP sunucusuna bağlanan hayvanat bahçesi temsilcisinin kodunu içeren agent.py dosyasını indirin:
    curl -fsSL https://raw.githubusercontent.com/GoogleCloudPlatform/devrel-demos/main/codelabs/agent-registry-dynamic-discovery-with-adk/steps/step4/zoo_agent/agent.py -o zoo_agent/agent.py
    

MCP sunucusu yetkilendirmesinin işleyiş şekli

zoo-mcp-server Cloud Run hizmeti --invoker-iam-check ve --no-allow-unauthenticated işaretleriyle dağıtıldığından, MCP sunucusuna gönderilen her HTTP isteği Authorization üstbilgisinde Google tarafından imzalanmış bir kimlik jetonu içermelidir. Jeton, hizmette run.routes.invoke iznine sahip bir arayanı tanımlamalıdır.

zoo_agent/agent.py içinde, _GoogleAuthHeaders sınıfı kimlik jetonu kimlik bilgilerini iki yöntemden biriyle alır. İlk olarak, meta veri sunucusundan veya bir hizmet hesabı anahtarından hizmet URL'si için kimlik jetonu almak üzere fetch_id_token_credentials() çağrılır. Bu işlem başarısız olursa kullanıcı Uygulama Varsayılan Kimlik Bilgilerinizin (ADC) kimlik jetonunu kullanır. Örnek her çağrıldığında, gerekirse jetonu yeniler ve Authorization üstbilgisini döndürür:

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}"}

Temsilci, sınıfın bir örneğini header_provider bağımsız değişkeni olarak McpToolset yöntemine iletir. ADK, başlık sağlayıcıyı çağırır ve döndürdüğü başlıkları MCP sunucusuna gönderilen her isteğe ekler. HTTP istemcisi ADK tarafından oluşturulduğundan, MCP Python SDK'sının ve HTTP kitaplığının hangi sürümünün yüklü olduğuna bakılmaksızın aynı kod çalışır:

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

İstenen kitle, hizmet URL'sidir (MCP_SERVER_URL). Bağlantı URL'si ise FastMCP sunucusunun MCP protokolünü sunduğu /mcp yolunu içerir. 30 saniyelik zaman aşımı, Cloud Run'ın baştan başlatılması için yeterli bir süredir.

Yerel ortamınızda ADK Playground'u kullanarak temsilciyi test etme

Temsilciyi test etmeden önce kullanıcı hesabınızın MCP sunucusunu çağırabildiğinden emin olun.

  1. Yalnızca yerel terminal (Cloud Shell'de bu adımı atlayın): ADC'yi kullanıcı kimlik bilgilerinizle ilk kullanıma hazırlayın:
    gcloud auth application-default login
    
    ADC başlatma işlemini tamamlamak için talimatları uygulayın.
  2. (İsteğe bağlı) Kullanıcı hesabınıza uzak MCP sunucusunu çağırma izni verin:
    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. Temsilcinizi adk run komutuyla çalıştırın:
    adk run zoo_agent
    
    veya ADK web arayüzünü adk web komutuyla çalıştırın:
    adk web --port 8080
    
    ve ardından komutun tarayıcıda yazdırdığı URL'yi açın.Cloud Shell kullanıyorsanız web arayüzünün Cloud Shell'den açılmasına izin vermek için --allow_origins bağımsız değişkenini ekleyin:
    adk web --port 8080 --allow_origins="regex:https://.*\.cloudshell\.dev"
    
    Komutu çalıştırdıktan sonra Cloud Shell araç çubuğunda (sağ tarafta) Web ÖnizlemeWeb önizlemesi simgesi simgesini bulun. Bu simgeyi tıklayın ve Preview on port 8080'i (8080 bağlantı noktasında önizle) seçin. Cloud Shell, ADK web arayüzünü gösteren yeni bir tarayıcı sekmesi açar.
  4. Temsilciyle sohbet etmeye başlayın. Örneğin, hayvanat bahçesindeki hayvanlar hakkında soru sorabilirsiniz:
    How many animals are in the zoo?
    
    Temsilci, yanıtı hesaplamak için her tür için list_species ve ardından get_animals_by_species aracını çağırır.

ADK web arayüzü, temsilcinin nasıl çalıştığını (hayvanat bahçesi MCP sunucusuna yaptığı çağrılar dahil) keşfetmenize olanak tanır. Nasıl yapılacağını öğrenmek için Web kullanıcı arayüzü belgelerine bakın.

5. MCP sunucusu uç noktasını dinamik olarak keşfetme

Bu adımda, temsilciyi, uç nokta URL'sini sabit kodlamak yerine Agent Registry'de kayıtlı Zoo MCP sunucusu uç noktasını keşfedecek şekilde değiştirirsiniz.

Bu adımla ilgili tüm dosyaları GitHub'da bulabilirsiniz.

Sunucunun URL'sini bulmak ve almak için Agent Registry ile ADK entegrasyonunu kullanırsınız. Bu özelliği uygulamak için aşağıdaki bilgilere ihtiyacınız vardır:

  • MCP sunucunuzun kayıtlı olduğu Agent Registry'nin proje numarası ve konumu (global).
  • MCP sunucusunu kaydetmek için kullandığınız hizmet kimliği (zoo-mcp-server).

Temsilciyi, Temsilci Kayıt Defteri'ni kullanacak şekilde güncelleme

  1. Temsilcinin ilk sürümünü geçersiz kılmak için güncellenmiş agent.py dosyasını GitHub'dan indirin:
    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
    

    Bu adımdaki ortam değişkenleri

    Bu güncellemeyle birlikte temsilci, zoo_agent/.env üzerinden aşağıdaki ortam değişkenlerini kullanır:
    • GOOGLE_CLOUD_PROJECT_NUMBER: Artık MCP sunucusunun kimlik URN'sini (MCP_SERVER_URN) oluşturmak için gereklidir.
    • GOOGLE_CLOUD_PROJECT, GOOGLE_GENAI_USE_ENTERPRISE ve GOOGLE_CLOUD_LOCATION: ADK tarafından Vertex AI erişimini yapılandırmak için kullanılır. Konum, MCP sunucusunu bulmak için de kullanılır.
    • Artık gerekli değil: Temsilci, MCP sunucusu uç nokta URL'sini ortamdan okumak yerine Agent Registry'den aldığı için MCP_SERVER_URL artık kullanılmıyor.

    AgentRegistry hizmetinde MCP sunucusu kimliği ve keşfinin işleyiş şekli

    Agent Registry'de, kayıtlı her MCP sunucusunun mcpServerId alanında depolanan ve Tekdüzen Kaynak Adı (URN) olarak biçimlendirilmiş benzersiz bir tanımlayıcısı vardır. Bu değeri, 3. adımda gcloud agent-registry mcp-servers list komutunun çıkışında görebilirsiniz. Temsilci, bu tanımlayıcıyı ortam değişkenlerinden yeniden oluşturur:
    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"
    
    URN; kayıt defterinin proje numarasını (projects-{PROJECT_NUMBER}), sunucunun kayıtlı olduğu proje numarasını ve konumu (projects:{PROJECT_NUMBER}:locations:{LOCATION}) ve gcloud agent-registry services create komutunda kullandığınız hizmet kimliğini (agentregistry:services:zoo-mcp-server) kodlar. Temsilci, kayıtlı MCP sunucularını registry.list_mcp_servers() ile listeler ve her sunucunun mcpServerId değerini MCP_SERVER_URN ile eşleştirir. Ardından, eşleşen sunucunun name (projects/.../locations/.../mcpServers/...) kaynağını registry.get_mcp_toolset()'ye iletir. registry.get_mcp_toolset(), sunucunun kayıtlı arayüzünden (https://...run.app/mcp) uç nokta URL'sini okur ve bu URL'ye bağlı bir McpToolset döndürür:
    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(), araç adlarının önüne sunucunun görünen adını ekler. Böylece temsilci, araçları zoo_mcp_server_list_species, zoo_mcp_server_get_animals_by_species ve zoo_mcp_server_get_animal_details olarak görür. Önek, bir temsilci birden fazla MCP sunucusundaki araçları kullandığında ad çakışmalarını önler. Son satır, Cloud Run soğuk başlatmaları için yer açmak amacıyla bağlantı zaman aşımını varsayılan 5 saniyeden daha yüksek bir değere ayarlar.

    Yetkilendirme AgentRegistry ile nasıl değişir?

    AgentRegistry, list_mcp_servers() gibi kendi API çağrılarının kimliğini ADC ile doğrular. Bu nedenle, üstbilgi sağlayıcı yalnızca bulunan MCP sunucusuna yapılan çağrılar için gereklidir. AgentRegistry, McpToolset öğesini dahili olarak oluşturduğundan üstbilgi sağlayıcıyı AgentRegistry oluşturucusuna iletirsiniz.Önceki adımda kitle, önceden bildiğiniz MCP_SERVER_URL değeriydi. Artık URL yalnızca keşiften sonra bilinir. ADK, üstbilgi sağlayıcıyı AgentRegistry oluşturulduğunda değil, temsilci MCP sunucusuna istek gönderdiğinde çağırır. Bu sayede temsilci önce sağlayıcıyı oluşturabilir ve get_mcp_toolset() döndükten sonra keşfedilen uç noktaya bağlayabilir. bind() yöntemi, kimlik bilgilerini önceki adımdaki oluşturucuyla aynı şekilde alır. Kitle, /mcp yolu olmadan keşfedilen uç nokta URL'sinden alınır:
        def bind(self, endpoint_url: str) -> None:
            parsed = urlparse(endpoint_url)
            audience = f"{parsed.scheme}://{parsed.netloc}"
            try:
                self._credentials = id_token.fetch_id_token_credentials(audience, request=self._request)
            except exceptions.DefaultCredentialsError:
                self._credentials, _ = google.auth.default()
                self._id_token = True
                if not isinstance(self._credentials, credentials.Credentials):
                    raise
    
    __call__ yöntemi, bind() yönteminden önce çağrılırsa hata vermesi dışında önceki adımdakiyle aynıdır.
  2. Değişikliği test etmek için temsilcinizi çalıştırın. adk run komutunu kullanın:
    adk run zoo_agent
    
    veya ADK web arayüzünü adk web komutuyla çalıştırın:
    adk web --port 8080
    
    ve ardından komutun tarayıcıda yazdırdığı URL'yi açın.Cloud Shell kullanıyorsanız web arayüzünün Cloud Shell'den açılmasına izin vermek için --allow_origins bağımsız değişkenini ekleyin:
    adk web --port 8080 --allow_origins="regex:https://.*\.cloudshell\.dev"
    
    Temsilciye önceki adımda sorduğunuz soruyu sorun. Yanıt aynıdır ancak ADK web arayüzünde araç çağrıları artık zoo_mcp_server_list_species ve zoo_mcp_server_get_animals_by_species olarak görünür.

Bu değişiklik sayesinde, açık MCP sunucusu URL'sine gerek kalmadan işlem yapabilirsiniz. Ancak MCP sunucusunu kaydetmek için kullanılan proje numarasını, kayıt konumunu ve hizmet kimliğini bilmeniz gerekir. Keşif, temsilci başlatma sırasında statik olarak gerçekleştirilir.

6. Dinamik keşifte MCP sunucusu için semantik aramayı kullanma

Bu adımda, başlangıçtaki tam URN eşleşmesini çalışma zamanı anlamsal aramasıyla değiştirerek MCP sunucusunun dinamik keşfini iyileştirirsiniz. Bu yaklaşım, sunucunun dağıtıldığı proje numarasını veya sunucunun tam kimliğini (MCP_SERVER_URN) bilme ihtiyacını ortadan kaldırır. Ayrıca, araç keşfini başlatma sırasında ele almak yerine doğrudan temsilcinin standart çalışma zamanı iş akışına entegre eder.

Bu adımla ilgili tüm dosyaları GitHub'da bulabilirsiniz.

Temsilciyi, çalışma zamanında anlamsal keşfi kullanacak şekilde güncelleyin.

  1. Mevcut uygulamayı geçersiz kılmak için güncellenmiş agent.py dosyasını GitHub'dan indirin:
    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
    

    Bu adımdaki ortam değişkenleri

    Temsilci artık MCP sunucusu URN'si oluşturmadığından gerekli yapılandırma daha da basitleştirilmiştir:
    • GOOGLE_CLOUD_PROJECT, GOOGLE_GENAI_USE_ENTERPRISE ve GOOGLE_CLOUD_LOCATION: ADK tarafından Vertex AI erişimini yapılandırmak için kullanılır. Konum, MCP sunucusunu bulmak için de kullanılır.
    • Artık gerekli değil: GOOGLE_CLOUD_PROJECT_NUMBER artık gerekli değil (önceki adımda kaldırılan MCP_SERVER_URL'e ek olarak).
    agent.py, modül başlatma sırasında registry.list_mcp_servers() işlevini çağırıp her girişi oluşturulmuş bir MCP_SERVER_URN ile karşılaştırmak yerine, registry.search_mcp_servers() işlevini search_type="SEMANTIC" ile çağıran bir keşif aracı işlevi discover_zoo_mcp_servers tanımlar:
    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,
        }
    
    Bu sayede temsilci, çalışma zamanında ihtiyaç duyduğu özelliklerin doğal dildeki açıklamasını (ör. "zoo animals, exhibits, habitat, care, feeding schedule") kullanarak Agent Registry'de arama yapabilir ve bulunan sunucu kaynak adlarını ADK oturum durumuna (tool_context.state) kaydeder.

    Dinamik keşif, temsilci iş akışına nasıl entegre edilir?

    agent.py, çalışma zamanında keşfedilen sunuculardaki araçları bağlamak ve temsilciyi keşif ve ardından çağırma akışında yönlendirmek için özel bir BaseToolset uygulamasını (RegistryDiscoveredToolset) dinamik bir talimat sağlayıcıyla (zoo_guide_instruction) birleştirir:
    1. Dinamik araç seti (RegistryDiscoveredToolset): BaseToolset alt sınıflarını oluşturur ve self._use_invocation_cache = False değerini ayarlar. Böylece ADK, başlangıçta boş bir araç listesini önbelleğe almak yerine discover_zoo_mcp_servers oturum durumunu güncelledikten hemen sonra mevcut araçları yeniden değerlendirir. get_tools() çağrıldığında, keşfedilen sunucu adlarını durumdan okur, bir çalışan iş parçacığındaki her sunucu için kimliği doğrulanmış bir McpToolset oluşturur ve tüm etkin araçları döndürür. Bir sunucuya ulaşılamaması diğerlerini etkilemez. _build_toolset(), engelleyici G/Ç işlemi gerçekleştirdiğinden _tools_of() bunu bir çalışan iş parçacığında çalıştırır.
      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. Dinamik temsilci talimatı (zoo_guide_instruction): Her model sırasından önce oturum durumunu değerlendirir. Henüz sunucu bulunamadığında _DISCOVERY_PROMPT ekleyerek temsilciye önce discover_zoo_mcp_servers yöntemini çağırmasını söyler. Keşif başarılı olduktan sonra istemi güncelleyerek etkin MCP sunucularını listeler ve temsilciye, keşfi tekrar çalıştırmadan bağlı hayvanat bahçesi araçlarını doğrudan kullanmasını söyler:
      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."
          )
      

    Dinamik olarak keşfedilen sunucular için yetkilendirme

    Anlamsal arama birkaç sunucu döndürebilir ve ADK, bir isteğin hangi sunucuya gittiğini belirtmeden başlık sağlayıcıyı çağırır. Bu nedenle _build_toolset(), bulunan her sunucu için özel bir _GoogleAuthHeaders ve AgentRegistry oluşturur ve sağlayıcıyı bu sunucunun uç noktasına bağlar. Sağlayıcı, önceki adımda kullanılanla aynıdır ve bind() tarihinden önce kullanılırsa başarısız olmasına neden olan bir _bound işareti içerir.

    Saklama ve yenileme mantığı

    agent.py, keşfedilen uç noktaları düzenli olarak yenilerken her seferinde Temsilci Kayıt Defteri'ni sorgulamamak için yaşam süresi (TTL) ile durum saklama özelliğini uygular:
    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
    
    • Uygulama kapsamlı saklama: Bulunan sunucu adları ve discovered_at zaman damgası, oturum durumunda app:zoo_mcp_servers anahtarı altında saklanır. ADK'nin app: önekini kullanmak, önbelleğe alınmış keşif kaydını uygulama çalıştırması boyunca kapsar.
    • TTL doğrulaması ve otomatik yenileme: _fresh_discovery(), kayıtlı kaydın mevcut olduğunu ve yaşının DISCOVERY_TTL_SECONDS (24 saat) değerini aşmadığını doğrular. Kayıt güncelken RegistryDiscoveredToolset, önbelleğe alınmış McpToolset bağlantılarını (self._toolsets) yeniden kullanır, zoo_guide_instruction keşif istemini atlar ve discover_zoo_mcp_servers için yapılan tüm tekrarlanan çağrılar hemen "already_discovered" değerini döndürür. 24 saatlik TTL süresi dolduğunda _fresh_discovery(), None değerini döndürür. Bu durum, RegistryDiscoveredToolset.get_tools()'nin mevcut MCP istemci oturumlarını (await self.close()) kapatmasına ve zoo_guide_instruction'nin temsilciyi yeni bir keşif yapmaya yönlendirmesine neden olur.
  2. Anlamsal keşif iş akışını test etmek için temsilcinizi çalıştırın. adk run komutunu kullanın:
    adk run zoo_agent
    
    veya ADK web arayüzünü adk web komutuyla çalıştırın:
    adk web --port 8080
    
    ve ardından komutun tarayıcıda yazdırdığı URL'yi açın.Cloud Shell kullanıyorsanız web arayüzünün Cloud Shell'den açılmasına izin vermek için --allow_origins bağımsız değişkenini ekleyin:
    adk web --port 8080 --allow_origins="regex:https://.*\.cloudshell\.dev"
    
    Temsilciye hayvanat bahçesiyle ilgili bir soru sorduğunuzda (örneğin, How many animals are in the zoo?) önce discover_zoo_mcp_servers aracını çağırdığını, keşfedilen MCP sunucusundaki araçları dinamik olarak bağladığını ve ardından sorunuzu yanıtlamak için zoo_mcp_server_list_species ve zoo_mcp_server_get_animals_by_species gibi hayvanat bahçesi araçlarını çağırdığını göreceksiniz.Which is the oldest lion? gibi bir ek soru sorun. Temsilci, önceden eklenmiş hayvanat bahçesi araçlarını kullanarak yanıt verir ve oturum durumundaki keşif kaydı hâlâ güncel olduğu için discover_zoo_mcp_servers aracını tekrar çağırmaz.

7. Temizleme

1. seçenek: Projeyi silme

Bu codelab için özel bir proje oluşturduysanız faturalandırmayı ortadan kaldırmanın en kolay yolu projeyi silmektir:

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

2. seçenek: Kaynakları tek tek silme

Mevcut bir projeyi kullandıysanız ve projeyi saklamak istiyorsanız bu codelab sırasında oluşturulan kaynakları tek tek silin:

  1. MCP sunucusu kaydını Agent Registry'den silin. Sunucuyu manuel olarak kaydettiğiniz için Cloud Run hizmetini silmek kaydı kaldırmaz:
    gcloud agent-registry services delete zoo-mcp-server \
        --location=global \
        --project="${GOOGLE_CLOUD_PROJECT}" \
        --quiet
    
  2. Cloud Run MCP sunucu hizmetini silin:
    gcloud run services delete zoo-mcp-server \
        --region=us-west1 \
        --project="${GOOGLE_CLOUD_PROJECT}" \
        --quiet
    
  3. MCP sunucusu için oluşturulan hizmet hesabını silin:
    gcloud iam service-accounts delete \
        "zoo-mcp-server-sa@${GOOGLE_CLOUD_PROJECT}.iam.gserviceaccount.com" \
        --project="${GOOGLE_CLOUD_PROJECT}" \
        --quiet
    
  4. cloud-run-source-deploy Artifact Registry deposu bu codelab sırasında oluşturulduysa kapsayıcı görüntüsü depolama ücreti ödememek için depoyu silin:
    gcloud artifacts repositories delete cloud-run-source-deploy \
        --location=us-west1 \
        --project="${GOOGLE_CLOUD_PROJECT}" \
        --quiet
    
  5. İsterseniz Cloud Shell'de oluşturulan yerel proje dizinini kaldırabilirsiniz:
    cd ~ && rm -rf adk-dynamic-discovery
    

8. Tebrikler

Bu codelab'de, Google Cloud Agent Registry ve Agent Development Kit (ADK) ile MCP sunucularını dinamik olarak keşfetmeyi ve kullanmayı öğrendiniz:

  1. MCP sunucusu kaydetme: Cloud Run'a güvenli bir MCP sunucusu dağıttınız ve bunu Agent Registry'ye kaydettiniz.
  2. Kimliğe göre MCP sunucusu keşfedildi: MCP sunucusu uç noktası, URL'si sabit kodlanmak yerine Agent Registry'den çözümlendi.
  3. Anlamsal olarak keşfedilen MCP sunucuları: Temsilcinin, doğal dilde arama kullanarak çalışma zamanında MCP araçlarını bulup bağlamasına olanak tanır. Bu araçlar saklanır ve yenilenir.

Sonraki adımlar