1. מבוא
בשיעור ה-Codelab הזה תלמדו איך להשתמש ב-Google Cloud Agent Registry ובערכה לפיתוח סוכנים (ADK) כדי לגלות ולהפעיל באופן דינמי שרתי Model Context Protocol (MCP) רשומים בזמן הריצה, בלי צורך בנקודות קצה ובהגדרות כלים בתוך הקוד.
בשיעור ה-Lab הזה משתמשים ב-ADK ובשפת התכנות Python כדי ליצור אפליקציה. אפשר ליישם את אותם עקרונות בשפות תכנות אחרות ובמסגרות אחרות לפיתוח נציגים.
הפעולות שתבצעו:
- הפעלה והגדרה של Google Cloud Agent Registry בפרויקט
- פריסת שרת MCP ב-Cloud Run ורישום שלו ב-Agent Registry
- פיתוח סוכן ADK שקורא לשרת ה-MCP באמצעות כתובת URL בתוך הקוד
- משנים את הסוכן כדי למצוא את שרת ה-MCP ב-Agent Registry לפי הזהות שלו
- שינוי הסוכן כדי לגלות שרתי MCP בזמן הריצה באמצעות חיפוש סמנטי
הדרישות
- דפדפן אינטרנט מודרני (כמו Chrome)
- פרויקט ב-Google Cloud שהחיוב מופעל בו
- חשבון עם הרשאות ליצור ולנהל משאבים ולהגדיר כללי מדיניות של IAM בפרויקט בענן של Google
2. הגדרת הסביבה
ההוראות ב-Codelab הזה מבוססות על ההנחה שאתם מריצים פקודות ב-Cloud Shell במסוף Google Cloud. סביבת Cloud Shell היא סביבה של שורת פקודה שפועלת ב-Google Cloud, וכוללת כלים נחוצים שנטענים מראש. אם התקנתם בסביבה המקומית את ה-CLI של gcloud, את git ואת Python 3, אתם יכולים להריץ את הפקודות גם שם.
יצירת פרויקט בענן של Google
- במסוף Google Cloud, בדף לבחירת הפרויקט, יוצרים פרויקט ב-Google Cloud.
- הקפידו לוודא שהחיוב מופעל בפרויקט שלכם ב-Cloud. כך בודקים אם החיוב מופעל בפרויקט
הפעלת מסוף Cloud Shell
- בבורר המשאבים, בוחרים את הפרויקט בענן של Google שיצרתם.
- לוחצים על Activate Cloud Shell
בחלק העליון של מסוף Google Cloud.
- אחרי שמתחברים ל-Cloud Shell, מאמתים את הזהות:
gcloud auth list - מוודאים שהפרויקט מוגדר:
gcloud config get project - אם הפרויקט לא מוגדר כמו שצריך או אם אתם משתמשים בסביבה במחשב המקומי במקום ב-Cloud Shell, צריך להגדיר את מזהה הפרויקט:
export GOOGLE_CLOUD_PROJECT=<YOUR_PROJECT_ID> gcloud config set project "${GOOGLE_CLOUD_PROJECT}"
בחירת פרויקט בענן ב-Google Cloud והגדרה של משתני סביבה
- מפעילים את ממשקי ה-API הנדרשים של Google Cloud:
gcloud services enable \ agentregistry.googleapis.com \ aiplatform.googleapis.com \ cloudbuild.googleapis.com \ run.googleapis.com \ --project="${GOOGLE_CLOUD_PROJECT}"
הגדרת סביבת Python
- יוצרים ומפעילים סביבה וירטואלית ומתקינים את יחסי התלות הנדרשים:
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
בשלב הזה יוצרים ורושמים שרת Zoo MCP. השרת חושף שלושה כלים: list_species – הצגת רשימה של כל המינים שחיים בגן החיות, get_animals_by_species – הצגת רשימה של בעלי החיים ממין ספציפי, ו-get_animal_details – קבלת מידע נוסף על בעל חיים ספציפי.
כל הקבצים לשלב הזה נמצאים ב-GitHub.
יצירת שרת ה-MCP
- הגדרת תיקייה ופרויקט 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 - מורידים את קוד שרת ה-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.
- יצירת
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 - יוצרים חשבון שירות ייעודי לשרת:
gcloud iam service-accounts create zoo-mcp-server-sa \ --display-name="Zoo MCP Server Service Account" - פורסים את השרת באזור
us-west1. אם מופיעה ההודעה הבאה, מאשרים את היצירה של מאגר Artifact Registry חדש. ההודעה הזו תופיע אם אין לכם מאגר Artifact Registry בפרויקט באזור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-unauthenticatedus-west1:Deploying from source requires an Artifact Registry Docker repository to store built containers. A repository named [cloud-run-source-deploy] in region [us-west1] will be created. Do you want to continue (Y/n)? - רושמים את שרת ה-MCP ב-Agent Registry במיקום "גלובלי":
הפקודות האלה יוצרות את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 כדי לרשום את השרת. - מוודאים שהסוכן מופיע במרשם:
יופיע פלט YAML עם פרטי שרת ה-MCP, כמו בדוגמה הבאה:gcloud agent-registry mcp-servers list \ --location=global \ --filter="display_name=zoo-mcp-server"--- attributes: agentregistry.googleapis.com/system/RuntimeReference: uri: //agentregistry.googleapis.com/projects/[PROJECT_NUMBER]/locations/global/services/zoo-mcp-server createTime: '<create time datetime in ISO 8601>' displayName: zoo-mcp-server interfaces: - protocolBinding: JSONRPC url: https://zoo-mcp-server-[PROJECT_NUMBER].us-west1.run.app/mcp mcpServerId: urn:mcp:projects-[PROJECT_NUMBER]:projects:[PROJECT_NUMBER]:locations:global:agentregistry:services:zoo-mcp-server name: projects/[PROJECT_ID]/locations/global/mcpServers/agentregistry-00000000-0000-0000-1234-56789abcdef tools: - annotations: idempotentHint: true readOnlyHint: true description: |- Returns every species in the zoo. Use it first for zoo-wide questions such as counting all animals or listing everything the zoo has. name: list_species - annotations: idempotentHint: true readOnlyHint: true description: |- Retrieves all animals of a specific species from the zoo. Can also be used to collect the base data for aggregate queries of animals of a specific species - like counting the number of penguins or finding the oldest lion. name: get_animals_by_species - annotations: idempotentHint: true readOnlyHint: true description: Retrieves the details of a specific animal by its name. name: get_animal_details updateTime: '<last updated datetime in ISO 8601>'
4. יצירת סוכן ADK בסיסי לשימוש בשרת Zoo MCP
בשלב הזה, יוצרים סוכן ADK שיענה על בקשות של משתמשים לגבי גן החיות בעזרת כתובת ה-URL של שרת ה-MCP של גן החיות שהוגדרה בתוך הקוד. אם אתם משתמשים בכתובת URL בתוך הקוד, אתם צריכים לדעת את כתובת ה-URL של שרת ה-MCP לפני פריסת הסוכן.
קובצי המקור לשלב הזה זמינים ב-GitHub.
יצירת אפליקציית הסוכן
יוצרים פיגום של פרויקט ה-ADK ושל סוכן גן החיות שמשתמש בשרת ה-MCP של גן החיות.
- מגדירים תיקייה ופרויקט לסוכן 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 - יוצרים את תיקיית חבילת הסוכן ומורידים את הקובץ
__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 - יוצרים את הקובץ
.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}" EOFzoo_agent/.env, כך שלא תצטרכו ליצור מחדש את הקובץ מאוחר יותר. בשלב הזה, נעשה שימוש במשתנים הבאים:-
GOOGLE_GENAI_USE_ENTERPRISE,GOOGLE_CLOUD_PROJECTו-GOOGLE_CLOUD_LOCATION: משמשים באופן משתמע את ADK ואת Google GenAI SDK כדי לנתב קריאות למודל Gemini דרך Vertex AI בפרויקט ובמיקום (global) ב-Google Cloud. הם גם מאחסנים את המיקום של Agent Registry שבו רשום שרת ה-MCP. -
GOOGLE_CLOUD_PROJECT_NUMBER: לא משתמשים בו בשלב הזה. הוא מאוכלס מראש לשימוש בשלב הבא. -
MCP_SERVER_URL: קורא ישירות ב-zoo_agent/agent.pyכדי להגדיר אתMcpToolsetעם כתובת ה-URL ב-Cloud Run בתוך הקוד של שרת Zoo MCP.
-
- מורידים את הקובץ
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() כדי לקבל טוקן של מזהה לכתובת ה-URL של השירות משרת המטא-נתונים או ממפתח של חשבון שירות. אם זה נכשל, הוא משתמש בטוקן של מזהה המשתמש ב-Application Default 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}"}
הנציג מעביר מופע של המחלקה אל McpToolset בתור הארגומנט header_provider. 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),
)
הקהל המבוקש הוא כתובת ה-URL של השירות (MCP_SERVER_URL), וכתובת ה-URL של החיבור כוללת את הנתיב /mcp שבו שרת FastMCP מפעיל את פרוטוקול ה-MCP. זמן הקצוב לתפוגה של 30 שניות משאיר מקום להפעלות במצב התחלתי (cold start) של Cloud Run.
בדיקת הסוכן באמצעות סביבת הפיתוח של ADK בסביבה המקומית
לפני שבודקים את הנציג, צריך לוודא שחשבון המשתמש יכול להתקשר לשרת ה-MCP.
- טרמינל מקומי בלבד (דלגו על השלב הזה ב-Cloud Shell): הפעילו את ADC עם פרטי הכניסה של המשתמש:
פועלים לפי ההוראות כדי להשלים את ההפעלה של ADC.gcloud auth application-default login - (אופציונלי) נותנים לחשבון המשתמש הרשאה לשלוח קריאות לשרת ה-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' - מריצים את הנציג באמצעות הפקודה
adk run: או מריצים את ממשק האינטרנט של ADK באמצעות הפקודהadk run zoo_agentadk web: ואז פותחים בדפדפן את כתובת ה-URL שהפקודה מדפיסה.אם משתמשים ב-Cloud Shell, מוסיפים את הארגומנטadk web --port 8080--allow_originsכדי לאפשר את פתיחת ממשק האינטרנט מ-Cloud Shell: אחרי שמריצים את הפקודה, מחפשים את הסמל Web Previewadk web --port 8080 --allow_origins="regex:https://.*\.cloudshell\.dev"
בסרגל הכלים של Cloud Shell (בצד ימין). לוחצים עליו ובוחרים באפשרות Preview on port 8080. ב-Cloud Shell תיפתח כרטיסייה חדשה בדפדפן שבה יוצג ממשק האינטרנט של ADK. - מתחילים להתכתב בצ'אט עם הנציג. לדוגמה, אפשר לשאול על בעלי חיים בגן החיות:
הסוכן קורא לפונקציהHow many animals are in the zoo?list_speciesואז לפונקציהget_animals_by_speciesלכל מין כדי לחשב את התשובה.
ממשק האינטרנט של ADK מאפשר לכם לבדוק איך הסוכן פועל, כולל הקריאות שלו לשרת MCP של Zoo. במאמרי העזרה של ממשק המשתמש באינטרנט מוסבר איך עושים את זה.
5. לגלות באופן דינמי את נקודת הקצה של שרת ה-MCP
בשלב הזה, אתם משנים את הסוכן כדי לגלות את נקודת הקצה של שרת ה-MCP של גן החיות שרשומה ב-Agent Registry, במקום להגדיר את כתובת ה-URL של נקודת הקצה באופן קבוע בקוד.
כל הקבצים לשלב הזה נמצאים ב-GitHub.
אתם משתמשים בשילוב של ADK עם Agent Registry כדי למצוא ולאחזר את כתובת ה-URL של השרת. כדי להטמיע את התכונה, אתם צריכים את הפרטים הבאים:
- מספר הפרויקט והמיקום של Agent Registry שבו שרת ה-MCP רשום (
global). - מזהה השירות שבו השתמשתם כדי לרשום את שרת ה-MCP (
zoo-mcp-server).
עדכון הסוכן לשימוש ב-Agent Registry
- מורידים את הקובץ
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כי הנציג מאחזר את כתובת ה-URL של נקודת הקצה של שרת ה-MCP מ-Agent Registry במקום לקרוא אותה מהסביבה.
איך פועלים הזיהוי והגילוי של שרת MCP ב-
ב-Agent Registry, לכל שרת MCP רשום יש מזהה ייחודי שמאוחסן בשדהAgentRegistrymcpServerIdשלו, בפורמט של שם משאב אחיד (URN). אפשר לראות אותו בפלט של הפקודהgcloud agent-registry mcp-servers listבשלב 3. ה-agent משחזר את המזהה הזה ממשתני הסביבה: ה-URN מקודד את מספר הפרויקט של המרשם (PROJECT_ID = os.environ["GOOGLE_CLOUD_PROJECT"] PROJECT_NUMBER = os.environ["GOOGLE_CLOUD_PROJECT_NUMBER"] LOCATION = os.environ.get("GOOGLE_CLOUD_LOCATION", "global") MCP_SERVER_URN = f"urn:mcp:projects-{PROJECT_NUMBER}:projects:{PROJECT_NUMBER}:locations:{LOCATION}:agentregistry:services:zoo-mcp-server"projects-{PROJECT_NUMBER}), את מספר הפרויקט והמיקום שבו השרת רשום (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(), שקורא את כתובת ה-URL של נקודת הקצה מהממשק הרשום של השרת (https://...run.app/mcp) ומחזירMcpToolsetשמחובר לכתובת ה-URL הזו: header_provider = _GoogleAuthHeaders() registry = AgentRegistry( project_id=PROJECT_ID, location=LOCATION, header_provider=header_provider, ) mcp_server_name = next( ( s["name"] for s in registry.list_mcp_servers().get("mcpServers", []) if s.get("mcpServerId") == MCP_SERVER_URN ), None, ) if not mcp_server_name: raise RuntimeError(f"MCP server '{MCP_SERVER_URN}' not found in Agent Registry.") zoo_mcp_toolset = registry.get_mcp_toolset(mcp_server_name=mcp_server_name) header_provider.bind(zoo_mcp_toolset.connection_params.url) zoo_mcp_toolset.connection_params.timeout = 30.0get_mcp_toolset()מוסיף לשמות הכלים את השם המוצג של השרת כתחילית, כך שהנציג רואה את הכלים בתורzoo_mcp_server_list_species,zoo_mcp_server_get_animals_by_speciesו-zoo_mcp_server_get_animal_details. הקידומת מונעת התנגשויות בין שמות כשסוכן משתמש בכלים מכמה שרתי MCP. השורה האחרונה מגדילה את זמן הקצוב לתפוגה של החיבור מברירת המחדל של 5 שניות, כדי להשאיר מקום להפעלות קרות של Cloud Run.איך ההרשאה משתנה עם
AgentRegistryAgentRegistryמאמת את הקריאות שלו ל-API, כמוlist_mcp_servers(), באמצעות ADC, ולכן ספק הכותרת נדרש רק לקריאות לשרת ה-MCP שזוהה. מכיוון ש-AgentRegistryיוצר אתMcpToolsetבאופן פנימי, אתם מעבירים את ספק הכותרת ל-constructor שלAgentRegistry.בשלב הקודם, הקהל היה הערך שלMCP_SERVER_URLשידעתם מראש. עכשיו כתובת ה-URL ידועה רק אחרי הגילוי. ADK קורא לספק הכותרות כשהסוכן שולח בקשה לשרת ה-MCP, ולא כשנוצרAgentRegistry. כך הסוכן יכול ליצור קודם את הספק ולקשר אותו לנקודת הקצה שזוהתה אחרי ש-get_mcp_toolset()מחזיר ערך. ה-methodbind()מקבלת את פרטי הכניסה באותו אופן כמו ה-constructor בשלב הקודם, כשהקהל נלקח מכתובת ה-URL של נקודת הקצה (endpoint) שזוהתה, בלי הנתיב/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(). -
- מריצים את הנציג כדי לבדוק את השינוי. משתמשים בפקודה
adk run: או מריצים את ממשק האינטרנט של ADK באמצעות הפקודהadk run zoo_agentadk web: ואז פותחים בדפדפן את כתובת ה-URL שהפקודה מדפיסה.אם משתמשים ב-Cloud Shell, מוסיפים את הארגומנטadk web --port 8080--allow_originsכדי לאפשר את פתיחת ממשק האינטרנט מ-Cloud Shell: שואלים את הסוכן את אותה שאלה כמו בשלב הקודם. התשובה זהה, אבל בממשק האינטרנט של ADK הקריאות לפונקציות מופיעות עכשיו בתורadk web --port 8080 --allow_origins="regex:https://.*\.cloudshell\.dev"zoo_mcp_server_list_speciesו-zoo_mcp_server_get_animals_by_species.
השינוי הזה מאפשר לכם לעקוף את הצורך בכתובת ה-URL המפורשת של שרת ה-MCP. עם זאת, אתם עדיין צריכים לדעת את מספר הפרויקט, את מיקום המרשם ואת מזהה השירות שמשמש לרישום שרת ה-MCP, והזיהוי מתבצע באופן סטטי במהלך האיניציאליזציה של הנציג.
6. שימוש בחיפוש סמנטי לשרת MCP בזיהוי דינמי
בשלב הזה, אתם משפרים את הגילוי הדינמי של שרת ה-MCP על ידי החלפת ההתאמה המדויקת של ה-URN בזמן ההפעלה בחיפוש סמנטי בזמן הריצה. בגישה הזו לא צריך לדעת את מספר הפרויקט שבו השרת פרוס או את הזהות המדויקת של השרת (MCP_SERVER_URN), וגילוי הכלים משולב ישירות בתהליך העבודה הרגיל של הסוכן בזמן הריצה, במקום לטפל בו בזמן האיניציאליזציה.
כל הקבצים לשלב הזה נמצאים ב-GitHub.
עדכון הנציג לשימוש בגילוי סמנטי בזמן הריצה
- מורידים את קובץ
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": כך הסוכן יכול לחפש ב-Agent Registry בזמן הריצה באמצעות תיאור בשפה טבעית של היכולות שהוא צריך (לדוגמה,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") ולשמור את שמות המשאבים של השרת שנמצאו במצב הסשן של ADK (tool_context.state).איך גילוי דינמי משולב בתהליך העבודה של Agentic Workflows
כדי לטעון כלים משרתים שמתגלים בזמן הריצה ולהנחות את הנציג בתהליך הגילוי ואז ההפעלה,agent.pyמשלב הטמעה מותאמת אישית שלBaseToolset(RegistryDiscoveredToolset) עם ספק הוראות דינמי (zoo_guide_instruction):- ערכת כלים דינמית (
RegistryDiscoveredToolset): מחלקת משנהBaseToolsetומגדירה אתself._use_invocation_cache = Falseכך ש-ADK יעריך מחדש את הכלים הזמינים מיד אחרי ש-discover_zoo_mcp_serversיעדכן את מצב הסשן, במקום לשמור במטמון רשימת כלים ריקה בהתחלה. כשמפעילים אתget_tools(), היא קוראת את שמות השרתים שזוהו מהמצב, יוצרתMcpToolsetמאומת לכל שרת ב-Thread עובד ומחזירה את כל הכלים הפעילים. אם לא מצליחים להגיע לשרת אחד, זה לא משפיע על השרתים האחרים. _build_toolset()מבצע פעולות קלט/פלט חוסמות, ולכן_tools_of()מריץ אותו ב-thread עובד.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] - הוראה דינמית לסוכן (
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 Registry בכל פעם, ועדיין לרענן את נקודות הקצה שזוהו מדי פעם,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(24 שעות). כל עוד הרשומה עדכנית,RegistryDiscoveredToolsetעושה שימוש חוזר בחיבוריMcpToolsetשנשמרו במטמון (self._toolsets),zoo_guide_instructionמשמיט את בקשת הגילוי, וכל קריאה חוזרת ל-discover_zoo_mcp_serversמחזירה מיד את"already_discovered". אחרי שפג תוקף ה-TTL של 24 השעות,_fresh_discovery()מחזיר אתNone, מה שגורם ל-RegistryDiscoveredToolset.get_tools()לסגור סשנים קיימים של לקוחות MCP (await self.close()) ול-zoo_guide_instructionלבקש מהנציג לבצע גילוי חדש.
- מפעילים את הנציג כדי לבדוק את תהליך העבודה של החיפוש הסמנטי. משתמשים בפקודה
adk run: או מריצים את ממשק האינטרנט של ADK באמצעות הפקודהadk run zoo_agentadk web: ואז פותחים בדפדפן את כתובת ה-URL שהפקודה מדפיסה.אם משתמשים ב-Cloud Shell, מוסיפים את הארגומנטadk web --port 8080--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, כי רשומת הגילוי במצב הסשן עדיין עדכנית.
7. הסרת המשאבים
אפשרות 1: מחיקת הפרויקט
אם יצרתם פרויקט ייעודי ל-Codelab הזה, הדרך הכי קלה לבטל את החיוב היא למחוק את הפרויקט:
gcloud projects delete "${GOOGLE_CLOUD_PROJECT}" --quiet
אפשרות 2: מחיקת משאבים ספציפיים
אם השתמשתם בפרויקט קיים ואתם רוצים לשמור אותו, מחקו את המשאבים הספציפיים שיצרתם במהלך ה-Codelab הזה:
- מוחקים את הרישום של שרת ה-MCP מ-Agent Registry. מכיוון שרשמתם את השרת באופן ידני, מחיקת השירות של Cloud Run לא מסירה את הרישום:
gcloud agent-registry services delete zoo-mcp-server \ --location=global \ --project="${GOOGLE_CLOUD_PROJECT}" \ --quiet - מוחקים את שירות שרת ה-MCP של Cloud Run:
gcloud run services delete zoo-mcp-server \ --region=us-west1 \ --project="${GOOGLE_CLOUD_PROJECT}" \ --quiet - מוחקים את חשבון השירות שנוצר בשביל שרת ה-MCP:
gcloud iam service-accounts delete \ "zoo-mcp-server-sa@${GOOGLE_CLOUD_PROJECT}.iam.gserviceaccount.com" \ --project="${GOOGLE_CLOUD_PROJECT}" \ --quiet - אם המאגר
cloud-run-source-deployב-Artifact Registry נוצר במיוחד במהלך ה-Codelab הזה, צריך למחוק אותו כדי להימנע מחיובים על אחסון קובצי אימג' של קונטיינרים:gcloud artifacts repositories delete cloud-run-source-deploy \ --location=us-west1 \ --project="${GOOGLE_CLOUD_PROJECT}" \ --quiet - אם רוצים, מסירים את ספריית הפרויקט המקומית שנוצרה ב-Cloud Shell:
cd ~ && rm -rf adk-dynamic-discovery
8. מזל טוב
בשיעור ה-Codelab הזה למדתם איך לגלות שרתי MCP באופן דינמי ולהשתמש בהם באמצעות Google Cloud Agent Registry והערכה לפיתוח סוכנים (ADK):
- רשמתם שרת MCP: פרסתם שרת MCP מאובטח ב-Cloud Run ורשמתם אותו ב-Agent Registry.
- זיהוי שרת MCP לפי זהות: פתרון נקודת הקצה של שרת ה-MCP מ-Agent Registry במקום להגדיר את כתובת ה-URL שלו באופן קשיח.
- חיפוש סמנטי של שרתי MCP: הסוכן יכול למצוא ולהתקין כלי MCP בזמן הריצה באמצעות חיפוש בשפה טבעית, עם שמירה ורענון.