کشف پویای سرور MCP با Agent Registry و ADK

۱. مقدمه

در این codelab، یاد خواهید گرفت که چگونه از Google Cloud Agent Registry و Agent Development Kit (ADK) برای کشف و فراخوانی پویا سرورهای Model Context Protocol (MCP) ثبت‌شده در زمان اجرا استفاده کنید و نیاز به پیکربندی‌های ابزار و نقاط پایانی کدگذاری‌شده را ازبین ببرید.

این آزمایشگاه از ADK و زبان برنامه‌نویسی Python برای ساختن برنامه استفاده می‌کند. همین اصول را می‌توان در زبان‌های برنامه‌نویسی دیگر و بااستفاده از چارچوب‌های توسعه عامل دیگر اعمال کرد.

آنچه انجام خواهید داد

  • فعال و پیکربندی کردن Google Cloud Agent Registry در پروژه شما
  • مستقر کردن سرور MCP در Cloud Run و ثبت کردن آن در Agent Registry
  • ساختن عامل ADK که سرور MCP را بااستفاده از نشانی وب کدگذاری‌شده فراخوانی می‌کند
  • عامل را تغییر دهید تا سرور MCP را در «دفتر ثبت عامل» براساس هویت آن کاوش کند
  • تغییر دادن عامل برای کشف سرورهای MCP در زمان اجرا بااستفاده از جستجوی معنایی

آنچه نیاز خواهید داشت

  • مرورگر وب مدرن (مانند Chrome)
  • پروژه Google Cloud با صورت‌حساب فعال‌شده
  • حسابی با اجازه‌های ایجاد و مدیریت منابع و پیکربندی خط‌مشی‌های IAM در پروژه Google Cloud

۲. راه‌اندازی محیط

دستورالعمل‌های این codelab فرض می‌کنند که شما فرمان‌ها را در Cloud Shell در کنسول Google Cloud اجرا می‌کنید. ‫Cloud Shell محیطی با خط فرمان است که در Google Cloud اجرا می‌شود و ابزارهای لازم ازقبل در آن بار شده است. اگر gcloud CLI، git و Python 3 را در محیط محلی خود نصب کرده‌اید، می‌توانید فرمان‌ها را در آنجا نیز اجرا کنید.

ایجاد پروژه Google Cloud

  1. در کنسول Google Cloud، در صفحه انتخابگر پروژه، پروژه Google Cloud ایجاد کنید.
  2. مطمئن شوید که صورت‌حساب برای پروژه Cloud شما فعال باشد. با نحوه بررسی فعال بودن صورت‌حساب در پروژه آشنا شوید.

راه‌اندازی پایانه Cloud Shell

  1. بااستفاده از انتخابگر منبع پروژه Google Cloud را که ایجاد کرده‌اید انتخاب کنید.
  2. روی فعال‌سازی Cloud Shell نماد فعال کردن Cloud Shell در بالای کنسول Google Cloud کلیک کنید.
  3. پس‌از اتصال به Cloud Shell، اصالت‌سنجی‌تان را درستی‌سنجی کنید:
    gcloud auth list
    
  4. تأیید کنید که پروژه‌تان پیکربندی شده است:
    gcloud config get project
    
  5. اگر پروژه شما طبق انتظار تنظیم نشده است یا اگر به‌جای Cloud Shell از محیطی در دستگاه محلی‌تان استفاده می‌کنید، شناسه پروژه را تنظیم کنید:
    export GOOGLE_CLOUD_PROJECT=<YOUR_PROJECT_ID>
    gcloud config set project "${GOOGLE_CLOUD_PROJECT}"
    

انتخاب پروژه Google Cloud و تنظیم متغیرهای محیطی

  1. میاناهای برنامه‌سازی کاربردی Google Cloud موردنیاز را فعال کنید:
    gcloud services enable \
      agentregistry.googleapis.com \
      aiplatform.googleapis.com \
      cloudbuild.googleapis.com \
      run.googleapis.com \
      --project="${GOOGLE_CLOUD_PROJECT}"
    

راه‌اندازی محیط Python

  1. محیط مجازی ایجاد و فعال کنید و وابستگی‌های لازم را نصب کنید:
    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"
    

۳. ایجاد و ثبت کردن سرور Zoo MCP

در این مرحله، سرور Zoo MCP را ایجاد و ثبت می‌کنید. سرور سه ابزار را ارائه می‌دهد: list_species ‒ فهرست کردن همه گونه‌هایی که در باغ‌وحش زندگی می‌کنند، get_animals_by_species ‒ فهرست کردن حیوانات یک گونه خاص، و get_animal_details ‒ دریافت اطلاعات بیشتر درباره یک حیوان خاص.

می‌توانید همه فایل‌های این مرحله را در GitHub پیدا کنید.

سرور MCP را ایجاد کنید

  1. راه‌اندازی پوشه و پروژه Python برای سرور 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. کد سرور 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
    

مستقر کردن سرور MCP در Cloud Run و ثبت کردن در Agent Registry

سپس، Dockerfile را برای سرور ایجاد می‌کنید و سرور را در Cloud Run مستقر می‌کنید.

  1. ایجاد کردن 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. حساب سرویس تعیین‌شده‌ای برای سرور ایجاد کنید:
    gcloud iam service-accounts create zoo-mcp-server-sa \
        --display-name="Zoo MCP Server Service Account"
    
  3. سرور را در منطقه 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
    
    اگر پیام‌واره زیر را می‌بینید، ایجاد مخزن Artifact Registry جدید را تأیید کنید. اگر مخزن Artifact Registry در پروژه شما برای منطقه 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. ثبت کردن سرور MCP در «دفتر ثبت عامل» در مکان «جهانی»:
    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"
    
    این فرمان‌ها toolspec.json را با مانیفست ابزار تولید می‌کنند و برای ثبت سرور، Agent Registry API را فراخوانی می‌کنند.
  5. تأیید کنید که نماینده در دفتر ثبت ظاهر می‌شود:
    gcloud agent-registry mcp-servers list \
        --location=global \
        --filter="display_name=zoo-mcp-server"
    
    برونداد YAML را با اطلاعات سرور MCP به‌این‌صورت خواهید دید:
    ---
    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>'
    

۴. ایجاد کردن عامل ADK پایه برای استفاده از سرور Zoo MCP

در این مرحله، یک عامل ADK ایجاد می‌کنید تا با کمک نشانی وب کدگذاری‌شده سرور Zoo MCP به درخواست‌های کاربر درباره باغ‌وحش پاسخ دهد. کدگذاری سخت شما را ملزم می‌کند که URL سرور MCP را پیش‌از استقرار عامل بدانید.

فایل‌های منبع این مرحله را می‌توانید در GitHub پیدا کنید.

ایجاد برنامه عامل

یک داربست از پروژه ADK و عامل Zoo که از سرور Zoo MCP استفاده می‌کند ایجاد کنید.

  1. پوشه و پروژه‌ای برای عامل 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. پوشه بسته عامل را ایجاد کنید و فایل __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. فایل .env را ایجاد کنید و آن را با متغیرهای محیطی که در این مرحله و مراحل بعدی استفاده می‌شوند پر کنید:
    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
    
    این فرمان همه متغیرهای محیطی موردنیاز در این مرحله و مراحل اکتشاف بعدی را در zoo_agent/.env می‌نویسد، بنابراین نیازی نیست بعداً فایل را دوباره ایجاد کنید. در این مرحله، از متغیرهای زیر استفاده می‌شود:
    • GOOGLE_GENAI_USE_ENTERPRISE، GOOGLE_CLOUD_PROJECT، و GOOGLE_CLOUD_LOCATION: به‌طور ضمنی توسط ADK و Google GenAI SDK برای مسیریابی تماس‌های مدل Gemini ازطریق Vertex AI در پروژه و مکان Google Cloud شما (global) استفاده می‌شود. همچنین مکان Agent Registry را که سرور MCP در آن ثبت شده است ذخیره می‌کند.
    • ‫GOOGLE_CLOUD_PROJECT_NUMBER: در این مرحله استفاده نمی‌شود؛ در اینجا ازپیش تکمیل شده است تا در مرحله بعدی استفاده شود.
    • ‫MCP_SERVER_URL: مستقیماً در zoo_agent/agent.py بخوانید تا McpToolset را با URL کدگذاری‌شده Cloud Run سرور Zoo MCP پیکربندی کنید.
  4. agent.py را با کد مربوط به عامل Zoo که به سرور MCP‏ Zoo متصل می‌شود بارگیری کنید:
    curl -fsSL https://raw.githubusercontent.com/GoogleCloudPlatform/devrel-demos/main/codelabs/agent-registry-dynamic-discovery-with-adk/steps/step4/zoo_agent/agent.py -o zoo_agent/agent.py
    

نحوه عملکرد صدور مجوز سرور MCP

چون سرویس Cloud Run zoo-mcp-server با پرچم‌های --invoker-iam-check و --no-allow-unauthenticated مستقر شده است، هر درخواست HTTP به سرور MCP باید شامل یک شناسه کاربری امضاشده توسط Google در سرصفحه Authorization باشد. این توکن باید تماس‌گیرنده‌ای را شناسایی کند که در سرویس اجازه run.routes.invoke دارد.

در zoo_agent/agent.py، کلاس _GoogleAuthHeaders اعتبارنامه توکن شناسه را به یکی از دو روش دریافت می‌کند. ابتدا fetch_id_token_credentials() را فراخوانی می‌کند تا یک شناسه برای نشانی وب سرویس از سرور فراداده یا کلید حساب سرویس دریافت کند. اگر این کار ناموفق باشد، از شناسه ژتون اعتبارنامه پیش‌فرض برنامه (ADC) کاربر شما استفاده می‌کند. هربار که نمونه فراخوانی می‌شود، درصورت لزوم کد را بازآوری می‌کند و سرایند 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}"}

عامل نمونه‌ای از کلاس را به‌عنوان آرگومان header_provider به McpToolset ارسال می‌کند. ‫ADK ارائه‌دهنده سرایند را فراخوانی می‌کند و سرایندهایی را که برمی‌گرداند به هر درخواست به سرور MCP پیوست می‌کند. چون کارخواه HTTP توسط ADK ایجاد می‌شود، همان کد بدون توجه به اینکه کدام نسخه از MCP Python SDK و کتابخانه HTTP آن نصب شده است کار می‌کند:

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

مخاطب درخواست‌شده نشانی وب سرویس (MCP_SERVER_URL) است، درحالی‌که نشانی وب اتصال شامل مسیر /mcp است که سرور FastMCP پروتکل MCP را ارائه می‌دهد. مهلت ۳۰ ثانیه‌ای برای شروع سرد Cloud Run فضا ایجاد می‌کند.

آزمایش کردن عامل بااستفاده از زمین بازی ADK در محیط محلی

قبل‌از آزمایش کردن نماینده، مطمئن شوید که حساب کاربری‌تان می‌تواند سرور MCP را فراخوانی کند.

  1. فقط پایانه محلی (از این مرحله در Cloud Shell رد شوید): ADC را با اطلاعات کاربری کاربرتان مقداردهی اولیه کنید:
    gcloud auth application-default login
    
    دستورالعمل‌ها را برای تکمیل مقداردهی اولیه ADC دنبال کنید.
  2. (اختیاری) به حساب کاربری‌تان اجازه دهید سرور MCP راه دور را فراخوانی کند:
    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. عاملتان را با فرمان adk run اجرا کنید:
    adk run zoo_agent
    
    یا میانای وب ADK را با فرمان adk web اجرا کنید:
    adk web --port 8080
    
    و سپس نشانی وبی را که فرمان در مرورگر چاپ می‌کند باز کنید.اگر از Cloud Shell استفاده می‌کنید، آرگومان --allow_origins را اضافه کنید تا باز کردن میانای وب از Cloud Shell مجاز شود:
    adk web --port 8080 --allow_origins="regex:https://.*\.cloudshell\.dev"
    
    پس‌از اجرای فرمان، نماد پیش‌نمایش وبنماد «پیش‌نمایش وب» را در نوار ابزار Cloud Shell (سمت راست) پیدا کنید. روی آن کلیک کنید و پیش‌نمایش در درگاه ۸۰۸۰ را انتخاب کنید. Cloud Shell زبانه مرورگر جدیدی باز می‌کند که میانای وب ADK را نمایش می‌دهد.
  4. گپ زدن با نماینده را شروع کنید. برای مثال، درباره حیوانات باغ‌وحش از آن بپرسید:
    How many animals are in the zoo?
    
    عامل list_species و سپس get_animals_by_species را برای هر گونه فراخوانی می‌کند تا پاسخ را محاسبه کند.

میانای وب ADK به شما امکان می‌دهد نحوه عملکرد عامل را کاوش کنید، ازجمله تماس‌های آن با سرور MCP باغ‌وحش. برای آشنایی با نحوه انجام این کار، به اسناد میانای کاربری وب مراجعه کنید.

۵. نقطه پایان سرور MCP را به‌طور پویا پیدا کنید

در این مرحله، عامل را تغییر می‌دهید تا نقطه پایان سرور Zoo MCP ثبت‌شده در Agent Registry را به‌جای کدگذاری سخت نشانی وب نقطه پایان آن کشف کند.

می‌توانید همه فایل‌های این مرحله را در GitHub پیدا کنید.

برای پیدا کردن و بازیابی نشانی وب سرور، از ادغام ADK با Agent Registry استفاده می‌کنید. برای پیاده‌سازی آن به اطلاعات زیر نیاز دارید:

  • شماره پروژه و مکان Agent Registry که سرور MCP شما در آن ثبت شده است (global).
  • شناسه سرویسی که برای ثبت سرور MCP استفاده کردید (zoo-mcp-server).

به‌روزرسانی نماینده برای استفاده از «دفتر ثبت نماینده»

  1. فایل agent.py به‌روزشده را از GitHub بارگیری کنید تا نسخه اول نماینده را لغو کنید:
    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
    

    متغیرهای محیط در این مرحله

    با این به‌روزرسانی، عامل به متغیرهای محیطی زیر از zoo_agent/.env متکی است:
    • GOOGLE_CLOUD_PROJECT_NUMBER: اکنون برای ساختن URN هویت سرور MCP‏ (MCP_SERVER_URN) لازم است.
    • ‫GOOGLE_CLOUD_PROJECT،‏ GOOGLE_GENAI_USE_ENTERPRISE، و GOOGLE_CLOUD_LOCATION: ADK از این نقش‌ها برای پیکربندی دسترسی به Vertex AI استفاده می‌کند. از مکان برای پیدا کردن سرور MCP نیز استفاده می‌شود.
    • دیگر لازم نیست: MCP_SERVER_URL دیگر استفاده نمی‌شود زیرا عامل نشانی وب نقطه پایانی سرور MCP را از «دفتر ثبت عامل» بازیابی می‌کند و آن را از محیط نمی‌خواند.

    نحوه عملکرد هویت و کاوش سرور MCP در AgentRegistry

    در «دفتر ثبت عامل»، هر سرور MCP ثبت‌شده دارای یک شناسه منحصربه‌فرد است که در فیلد mcpServerId آن ذخیره شده و به‌صورت «نام منبع یکسان» (URN) قالب‌بندی شده است. می‌توانید آن را در خروجی فرمان gcloud agent-registry mcp-servers list در «مرحله ۳» ببینید. عامل این شناسه را از متغیرهای محیطی بازسازی می‌کند:
    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 شماره پروژه دفتر ثبت (projects-{PROJECT_NUMBER})، شماره پروژه و مکانی که سرور در آن ثبت شده است (projects:{PROJECT_NUMBER}:locations:{LOCATION})، و شناسه سرویسی را که در فرمان gcloud agent-registry services create استفاده کرده‌اید (agentregistry:services:zoo-mcp-server) کدگذاری می‌کند. عامل سرورهای MCP ثبت‌شده را با registry.list_mcp_servers() فهرست می‌کند و mcpServerId هر سرور را با MCP_SERVER_URN مطابقت می‌دهد. سپس منبع سرور منطبق name (projects/.../locations/.../mcpServers/...) را به registry.get_mcp_toolset() ارسال می‌کند، که نشانی وب نقطه پایان را از میانای ثبت‌شده سرور (https://...run.app/mcp) می‌خواند و یک McpToolset متصل به آن نشانی وب برمی‌گرداند:
    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() نام نمایشی سرور را به‌عنوان پیشوند به نام ابزارها اضافه می‌کند، بنابراین نماینده ابزارها را به‌صورت zoo_mcp_server_list_species، zoo_mcp_server_get_animals_by_species، و zoo_mcp_server_get_animal_details می‌بیند. این پیشوند از تداخل نام‌ها در زمانی که عامل از ابزارهای چند سرور MCP استفاده می‌کند جلوگیری می‌کند. خط آخر زمان اتمام اتصال را از مقدار پیش‌فرض ۵ ثانیه افزایش می‌دهد تا فضایی برای راه‌اندازی سرد Cloud Run باقی بماند.

    نحوه تغییر مجوز با AgentRegistry

    AgentRegistry فراخوانی‌های میانای برنامه‌سازی کاربردی خود را، مانند list_mcp_servers()، با ADC اصالت‌سنجی می‌کند، بنابراین ارائه‌دهنده سرصفحه فقط برای فراخوانی‌های سرور MCP کشف‌شده لازم است. چون AgentRegistry به‌طور داخلی McpToolset را ایجاد می‌کند، ارائه‌دهنده سرصفحه را به سازنده AgentRegistry ارسال می‌کنید.در مرحله قبل، مخاطب مقدار MCP_SERVER_URL بود که ازقبل می‌دانستید. اکنون نشانی وب فقط پس‌از کاوش شناخته می‌شود. وقتی عامل درخواستی به سرور MCP ارسال می‌کند، ADK ارائه‌دهنده سرایند را فراخوانی می‌کند، نه وقتی که AgentRegistry ایجاد می‌شود. این کار به نماینده اجازه می‌دهد ابتدا ارائه‌دهنده را ایجاد کند و پس‌از بازگشت get_mcp_toolset() آن را به نقطه پایانی کشف‌شده متصل کند. روش bind() اطلاعات کاربری را به همان روش سازنده در مرحله قبلی دریافت می‌کند، با مخاطبی که از نشانی وب نقطه پایانی کشف‌شده بدون مسیر /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
    
    روش __call__ همانند مرحله قبلی است، با این تفاوت که اگر قبل‌از bind() فراخوانی شود، خطایی ایجاد می‌کند.
  2. نماینده‌تان را اجرا کنید تا تغییر را آزمایش کنید. از فرمان adk run استفاده کنید:
    adk run zoo_agent
    
    یا میانای وب ADK را با فرمان adk web اجرا کنید:
    adk web --port 8080
    
    و سپس نشانی وبی را که فرمان در مرورگر چاپ می‌کند باز کنید.اگر از Cloud Shell استفاده می‌کنید، آرگومان --allow_origins را اضافه کنید تا باز کردن میانای وب از Cloud Shell مجاز شود:
    adk web --port 8080 --allow_origins="regex:https://.*\.cloudshell\.dev"
    
    همان سؤال مرحله قبل را از نماینده بپرسید. پاسخ یکسان است، اما در میانای وب ADK، فراخوانی‌های ابزار اکنون به‌صورت zoo_mcp_server_list_species و zoo_mcp_server_get_animals_by_species نشان داده می‌شوند.

این تغییر به شما امکان می‌دهد نیاز به URL صریح سرور MCP را دور بزنید. بااین‌حال، همچنان باید شماره پروژه، مکان ثبت، و شناسه سرویس مورداستفاده برای ثبت سرور MCP را بدانید و فرایند کشف به‌صورت ایستا درطول مقداردهی اولیه کارگزار انجام می‌شود.

‫۶. استفاده از جستجوی معنایی برای سرور MCP در اکتشاف پویا

در این مرحله، کاوش پویای سرور MCP را با جایگزین کردن تطابق دقیق URN در راه‌اندازی با جستجوی معنایی زمان اجرا بهبود می‌بخشید. این رویکرد نیاز به دانستن شماره پروژه محل استقرار سرور یا هویت دقیق سرور (MCP_SERVER_URN) را ازبین می‌برد و کاوش ابزار را مستقیماً در گردش کار زمان اجرای استاندارد عامل ادغام می‌کند، به‌جای اینکه آن را در مرحله مقداردهی اولیه مدیریت کند.

می‌توانید همه فایل‌های این مرحله را در GitHub پیدا کنید.

به‌روزرسانی عامل برای استفاده از کاوش معنایی در زمان اجرا

  1. فایل agent.py به‌روزشده را از GitHub بارگیری کنید تا روی پیاده‌سازی موجود بازنویسی شود:
    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
    

    متغیرهای محیط در این مرحله

    چون نماینده دیگر URN سرور MCP را نمی‌سازد، پیکربندی موردنیاز بیشتر ساده می‌شود:
    • ‫GOOGLE_CLOUD_PROJECT،‏ GOOGLE_GENAI_USE_ENTERPRISE، و GOOGLE_CLOUD_LOCATION: ADK از این نقش‌ها برای پیکربندی دسترسی به Vertex AI استفاده می‌کند. از مکان برای پیدا کردن سرور MCP نیز استفاده می‌شود.
    • دیگر لازم نیست: GOOGLE_CLOUD_PROJECT_NUMBER دیگر لازم نیست (علاوه‌بر MCP_SERVER_URL که قبلاً در مرحله قبل برداشته شده است).
    به‌جای فراخوانی registry.list_mcp_servers() درطول مقداردهی اولیه ماژول و مقایسه هر ورودی با MCP_SERVER_URN ساخته‌شده، agent.py اکنون تابع ابزار اکتشاف discover_zoo_mcp_servers را تعریف می‌کند که registry.search_mcp_servers() را با 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,
        }
    
    این کار به عامل اجازه می‌دهد تا در زمان اجرا بااستفاده از شرح زبان طبیعی قابلیت‌های موردنیازش (برای مثال، "zoo animals, exhibits, habitat, care, feeding schedule") در Agent Registry جستجو کند و نام‌های منبع سرور کشف‌شده را در وضعیت جلسه ADK ذخیره کند (tool_context.state).

    نحوه ادغام کاوش پویا در گردش کار نماینده

    برای سوار کردن ابزارها از سرورهای کشف‌شده در زمان اجرا و راهنمایی کردن نماینده در جریان کشف و سپس فراخوانی، agent.py پیاده‌سازی BaseToolset سفارشی (RegistryDiscoveredToolset) را با ارائه‌دهنده دستورالعمل پویا (zoo_guide_instruction) ترکیب می‌کند:
    1. مجموعه ابزار پویا (RegistryDiscoveredToolset): زیرکلاس‌های BaseToolset و مجموعه‌های self._use_invocation_cache = False به‌طوری‌که ADK بلافاصله پس‌از اینکه discover_zoo_mcp_servers وضعیت جلسه را به‌روز کرد، ابزارهای دردسترس را مجدداً ارزیابی می‌کند، به‌جای اینکه فهرست ابزار خالی اولیه را ذخیره کند. وقتی get_tools() فراخوانی می‌شود، نام‌های سرور کشف‌شده را از وضعیت می‌خواند، یک McpToolset اصالت‌سنجی‌شده برای هر سرور در رشته عامل می‌سازد، و همه ابزارهای فعال را برمی‌گرداند. اگر یک سرور دردسترس نباشد، بر سرورهای دیگر تأثیری نمی‌گذارد. ‫_build_toolset() عملیات ورودی/خروجی مسدودکننده انجام می‌دهد، بنابراین _tools_of() آن را در رشته عامل اجرا می‌کند.
      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. دستورالعمل پویای نماینده (zoo_guide_instruction): وضعیت جلسه را قبل‌از هر نوبت مدل ارزیابی می‌کند. وقتی هنوز هیچ سروری شناسایی نشده است، _DISCOVERY_PROMPT را اضافه می‌کند و به نماینده دستور می‌دهد ابتدا discover_zoo_mcp_servers را فراخوانی کند. پس‌از موفقیت‌آمیز بودن کاوش، پیام‌واره را به‌روز می‌کند تا سرورهای فعال MCP را فهرست کند و به عامل دستور می‌دهد که مستقیماً از ابزارهای باغ‌وحش سوارشده استفاده کند، بدون اینکه کاوش را دوباره اجرا کند:
      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."
          )
      

    صدور مجوز برای سرورهای کشف‌شده به‌صورت پویا

    جستجوی معنایی می‌تواند چندین سرور را برگرداند و ADK یک ارائه‌دهنده سرصفحه را بدون اینکه به آن بگوید درخواست به کدام سرور می‌رود فراخوانی می‌کند. بنابراین، _build_toolset() برای هر سرور کشف‌شده یک _GoogleAuthHeaders و AgentRegistry اختصاصی ایجاد می‌کند و ارائه‌دهنده را به نقطه پایانی آن سرور متصل می‌کند. ارائه‌دهنده همان ارائه‌دهنده مرحله قبلی است، با پرچم _bound که باعث می‌شود اگر قبل‌از bind() استفاده شود، ناموفق باشد.

    منطق نگهداری و بازآوری

    برای جلوگیری از پرسمان «دفتر ثبت عامل» در هر نوبت و درعین‌حال بازآوری دوره‌ای نقاط پایانی کشف‌شده، agent.py حفظ وضعیت را با «زمان بقا» (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
    
    • حفظ داده در محدوده برنامه: نام‌های سرور کشف‌شده و مُهر زمان discovered_at در وضعیت جلسه تحت کلید app:zoo_mcp_servers ذخیره می‌شوند. استفاده از پیشوند app: ADK، سابقه کاوش ذخیره‌شده در حافظه نهان را در سراسر اجرای برنامه محدود می‌کند.
    • اعتبارسنجی TTL و بازآوری خودکار: _fresh_discovery() تأیید می‌کند که سابقه ذخیره‌شده وجود دارد و عمر آن از DISCOVERY_TTL_SECONDS (۲۴ ساعت) بیشتر نیست. تا زمانی که رکورد تازه باشد، RegistryDiscoveredToolset از اتصالات McpToolset ذخیره‌شده در حافظه نهان (self._toolsets) مجدداً استفاده می‌کند، zoo_guide_instruction پیام‌واره اکتشاف را حذف می‌کند، و هر تماس تکراری با discover_zoo_mcp_servers بلافاصله "already_discovered" را برمی‌گرداند. پس‌از منقضی شدن TTL‏ ۲۴ ساعته، _fresh_discovery() مقدار None را برمی‌گرداند که باعث می‌شود RegistryDiscoveredToolset.get_tools() جلسه‌های کارخواه MCP موجود (await self.close()) را ببندد و zoo_guide_instruction از نماینده بخواهد که کاوش جدیدی انجام دهد.
  2. عاملتان را اجرا کنید تا گردش کار کاوش معنایی را آزمایش کنید. از فرمان adk run استفاده کنید:
    adk run zoo_agent
    
    یا میانای وب ADK را با فرمان adk web اجرا کنید:
    adk web --port 8080
    
    و سپس نشانی وبی را که فرمان در مرورگر چاپ می‌کند باز کنید.اگر از Cloud Shell استفاده می‌کنید، آرگومان --allow_origins را اضافه کنید تا باز کردن میانای وب از Cloud Shell مجاز شود:
    adk web --port 8080 --allow_origins="regex:https://.*\.cloudshell\.dev"
    
    وقتی از عامل سؤالی درباره باغ‌وحش می‌پرسید (برای مثال، How many animals are in the zoo?)، مشاهده می‌کنید که ابتدا discover_zoo_mcp_servers را فراخوانی می‌کند، ابزارها را به‌طور پویا از سرور MCP کشف‌شده سوار می‌کند، و سپس ابزارهای باغ‌وحش، مانند zoo_mcp_server_list_species و zoo_mcp_server_get_animals_by_species، را برای پاسخ دادن به سؤال شما فراخوانی می‌کند.یک سؤال تکمیلی بپرسید، برای مثال Which is the oldest lion?. نماینده بااستفاده از ابزارهای باغ‌وحش که ازقبل سوار شده‌اند پاسخ می‌دهد و دوباره discover_zoo_mcp_servers را فراخوانی نمی‌کند، زیرا سابقه کاوش در وضعیت جلسه هنوز تازه است.

‫۷. مرتب‌سازی

گزینه ۱: حذف پروژه

اگر پروژه اختصاصی برای این codelab ایجاد کرده‌اید، ساده‌ترین راه برای حذف صورت‌حساب، حذف پروژه است:

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

گزینه ۲: حذف منابع به‌صورت تکی

اگر از پروژه موجودی استفاده کرده‌اید و می‌خواهید آن را نگه دارید، منابع تکی ایجادشده درطول این codelab را حذف کنید:

  1. ثبت سرور MCP را از «ثبت عامل» حذف کنید. چون سرور را به‌صورت دستی ثبت کرده‌اید، حذف کردن سرویس Cloud Run ثبت را برنمی‌دارد:
    gcloud agent-registry services delete zoo-mcp-server \
        --location=global \
        --project="${GOOGLE_CLOUD_PROJECT}" \
        --quiet
    
  2. سرویس سرور Cloud Run MCP را حذف کنید:
    gcloud run services delete zoo-mcp-server \
        --region=us-west1 \
        --project="${GOOGLE_CLOUD_PROJECT}" \
        --quiet
    
  3. حساب سرویس ایجادشده برای سرور MCP را حذف کنید:
    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 به‌طور خاص درطول این codelab ایجاد شده است، آن را حذف کنید تا از هزینه‌های ذخیره‌سازی تصویر محتوی جلوگیری شود:
    gcloud artifacts repositories delete cloud-run-source-deploy \
        --location=us-west1 \
        --project="${GOOGLE_CLOUD_PROJECT}" \
        --quiet
    
  5. درصورت تمایل، دایرکتوری پروژه محلی ایجادشده در Cloud Shell را بردارید:
    cd ~ && rm -rf adk-dynamic-discovery
    

‫۸. تبریک می‌گوییم

در این codelab، نحوه کشف و استفاده پویا از سرورهای MCP با Google Cloud Agent Registry و Agent Development Kit (ADK) را یاد گرفتید:

  1. ثبت سرور MCP: یک سرور MCP امن را در Cloud Run مستقر کردید و آن را در Agent Registry ثبت کردید.
  2. کشف سرور MCP براساس هویت: نقطه پایان سرور MCP را به‌جای کدگذاری ثابت نشانی وب آن، از «دفتر ثبت عامل» حل کرد.
  3. سرورهای MCP را به‌صورت معنایی کشف کرد: به عامل اجازه می‌دهد ابزارهای MCP را در زمان اجرا بااستفاده از جستجوی زبان طبیعی، با حفظ و بازآوری، پیدا و سوار کند.

مراحل بعدی