תחילת העבודה עם MCP ו-A2A באמצעות ADK

1. סקירה כללית

סוכני AI הופכים לפופולריים יותר ויותר, ומשנים את האופן שבו אנחנו מבצעים משימות באופן אוטומטי ומקבלים החלטות. הם יכולים לפעול באופן אוטונומי, ללמוד ולתקשר עם הסביבה שלהם כדי להשיג יעדים.

בשיעור ה-Codelab הזה תלמדו איך ליצור מערכת מרובת סוכנים מקצה לקצה. תפתחו שרת MCP של מטבעות, תפתחו סוכן מטבעות שמשתמש בשרת ה-MCP ונחשף דרך פרוטוקול Agent2Agent‏ (A2A), תפתחו סוכן נסיעות עם סוכן משנה של מזג אוויר בתהליך שמשתמש בסוכן המטבעות דרך A2A, ולבסוף תפרסו את כל השירותים ב-Cloud Run.

ארכיטקטורה

טכנולוגיות ליבה

Model Context Protocol‏ (MCP)

‫Model Context Protocol (MCP) הוא פרוטוקול פתוח שמאפשר לאפליקציות לספק הקשר וכלים למודלים גדולים של שפה (LLM) בצורה סטנדרטית. אפשר לחשוב על MCP כמו יציאת USB-C לאפליקציות AI: הוא מספק דרך סטנדרטית לחבר מודלים של AI למקורות נתונים חיצוניים, לממשקי API ולכלים.

ב-Codelab הזה, currency_mcp_server חושף כלי get_exchange_rate באמצעות HTTP שניתן להעביר בסטרימינג (/mcp), שמגובה על ידי Frankfurter API הציבורי.

ערכה לפיתוח סוכנים (ADK)

הערכה לפיתוח סוכנים (ADK) היא מסגרת תזמור גמישה לפיתוח ולפריסה של סוכני AI. ה-ADK מתאים לכל המודלים, מתאים לכל הפריסות, ונועד לתאימות עם מסגרות אחרות. ה-ADK הופך את פיתוח הסוכנים לתהליך שדומה להנדסת תוכנה רגילה, ומאפשר ליצור, לבדוק ולתאם בקלות Agentic Workflows מורכבים.

ערכת ADK מטפלת בשיחות עם המשתמשים, מציגה את ממשק המשתמש האינטראקטיבי באינטרנט, מתחברת לכלי MCP דרך McpToolset ומנהלת את התקשורת בין הסוכנים.

פרוטוקול Agent2Agent‏ (A2A)

‫פרוטוקול Agent2Agent‏ (A2A) הוא תקן פתוח שנועד לאפשר תקשורת ושיתוף פעולה בין סוכני AI. כמו ש-MCP יוצר סטנדרט לחיבורים בין סוכנים לכלים, A2A יוצר סטנדרט לשיתוף פעולה בין סוכנים. בעולם שבו סוכנים נוצרים בסביבות אירוח, בארגונים ובמסגרות שונות, A2A מספק שפה משותפת לחיפוש ולהאצלת משימות.

‫ADK חושף את currency_agent כשירות A2A‏ (to_a2a) ומאפשר ל-travel_agent להשתמש בו מרחוק בתור AgentTool‏ (RemoteA2aAgent).

סוכני משנה מקומיים לעומת סוכני A2A מרוחקים

הארכיטקטורה הזו מדגישה דפוס חשוב בתכנון של מערכת עם כמה סוכנים:

  • ‫Local Agent Tool (weather_agent): פועל בתהליך בתוך ה-orchestrator כ-AgentTool, בלי זמן אחזור ברשת או תקורה של סריאליזציה של פרוטוקולים.
  • ‫כלי סוכן מרוחק (currency_agent): פועל כמיקרו-שירות עצמאי, שמופעל ברשת באמצעות פרוטוקול A2A.

מה תלמדו

  • איך יוצרים ובודקים שרת MCP מקומי באמצעות FastMCP ב-Streamable HTTP
  • איך יוצרים סוכן באמצעות ADK שמשתמש בכלים של MCP‏ (McpToolset)
  • איך חושפים סוכן ADK כשרת A2A‏ (to_a2a)
  • איך בודקים שרת A2A באמצעות לקוח A2A Python‏ (a2a.client)
  • איך יוצרים סוכן תזמור שמשלב כלים של סוכני משנה מקומיים עם סוכני A2A מרוחקים
  • איך להריץ ולבדוק את המערכת מרובת הסוכנים המלאה באופן מקומי באמצעות ממשק המשתמש של ADK באינטרנט
  • איך להעביר לקונטיינרים ולפרוס את שלושת הרכיבים ב-Google Cloud Run

הדרישות

  • דפדפן, כמו Chrome.
  • פרויקט ב-Google Cloud שהחיוב מופעל בו.

‫2. לפני שמתחילים

יצירת פרויקט

אם אין לכם פרויקט ב-Google Cloud, אתם צריכים ליצור פרויקט.

במסוף Google Cloud, בדף לבחירת הפרויקט בוחרים או יוצרים פרויקט ב-Google Cloud.

חשוב גם לוודא שהחיוב מופעל בפרויקט בענן. כך בודקים אם החיוב מופעל בפרויקט

הפעלת Cloud Shell

‫Google Cloud Shell היא סביבת פיתוח אינטראקטיבית מבוססת-דפדפן, שזמינה ישירות במסוף Google Cloud. זו הדרך הכי קלה להתחיל לעבוד עם Google Cloud בלי להתקין כלים באופן מקומי.

לוחצים על הקישור כדי להפעיל את Cloud Shell. אפשר לעבור בין Cloud Shell Terminal (כדי להפעיל פקודות בענן) לבין Editor (כדי לצפות בקבצים) בלחיצה על הכפתור המתאים ב-Cloud Shell.

אחרי שמתחברים ל-Cloud Shell, מוודאים שהאימות בוצע:

gcloud auth list

מוודאים שהפקודה gcloud מכירה את הפרויקט:

gcloud config list project

מגדירים את מזהה הפרויקט:

export PROJECT_ID=<YOUR_GOOGLE_CLOUD_PROJECT_ID>
gcloud config set project $PROJECT_ID

הפעלת ממשקי Cloud API

מפעילים את ממשקי ה-API הנדרשים ל-Cloud Run, ל-Cloud Build, ל-Artifact Registry ול-Vertex AI:

gcloud services enable run.googleapis.com \
                       cloudbuild.googleapis.com \
                       artifactregistry.googleapis.com \
                       aiplatform.googleapis.com

קבל את הקוד

משכפלים את המאגר ועוברים לספרייה python:

git clone https://github.com/meteatamel/getting-started-mcp-a2a-adk.git
cd getting-started-mcp-a2a-adk/python

מתקינים את יחסי התלות באמצעות uv

‫uv הוא מנהל פרויקטים וחבילות מהיר של Python. הוא מותקן מראש ב-Cloud Shell. אם אתם מריצים את הפקודה באופן מקומי, אתם יכולים להתקין אותה כך:

# macOS and Linux
curl -LsSf https://astral.sh/uv/install.sh | sh

# Windows (PowerShell)
# powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"

הספרייה של Python בנויה כ-uv מאוחד

Workspace. מתקינים את כל יחסי התלות בכל חברי סביבת העבודה (currency_mcp_server, ‏ currency_agent ו-travel_agent) בסביבה וירטואלית אחת, על ידי הפעלת הפקודה:

uv sync

הגדרת משתני סביבה

מגדירים משתני סביבה בקובץ .env בספרייה python:

אפשרות א': Gemini Enterprise‏ (Google Cloud או Cloud Shell)

echo "GOOGLE_GENAI_USE_ENTERPRISE=TRUE" >> .env \
&& echo "GOOGLE_CLOUD_PROJECT=$PROJECT_ID" >> .env \
&& echo "GOOGLE_CLOUD_LOCATION=global" >> .env

אפשרות ב': Google AI Studio (מפתח API מקומי)

אם אתם מעדיפים להשתמש במפתח API מ-Google AI Studio:

echo "GOOGLE_API_KEY=<your_api_key_here>"<>>/span>  .env \
&& echo "GOOGLE_GENAI_USE_ENTERPRISE=FALSE" >> .env

3. יצירת שרת MCP להמרת מטבעות

לפני שיוצרים את הנציגים, בודקים את שרת ה-MCP שחושף את הכלי להמרת מטבעות.

שרת MCP מאפשר לכם לכתוב תוכנות קלות משקל כדי לחשוף יכולות ככלים. סוכן (או כמה סוכנים) יכול לגשת לכלים האלה באמצעות Model Context Protocol סטנדרטי.

אפשר להשתמש בחבילת Python של FastMCP כדי ליצור שרת MCP שחושף כלי אחד בשם get_exchange_rate. הכלי get_exchange_rate קורא ל-Frankfurter API כדי לקבל את שער החליפין הנוכחי בין שני מטבעות.

הקוד של שרת ה-MCP נמצא ב-currency_mcp_server/server.py:

import asyncio
import logging
import os

import httpx
from fastmcp import FastMCP

logger = logging.getLogger(__name__)
logging.basicConfig(format="[%(levelname)s]: %(message)s", level=logging.INFO)

mcp = FastMCP("Currency MCP Server 💵")


@mcp.tool()
def get_exchange_rate(
    currency_from: str = "USD",
    currency_to: str = "EUR",
    currency_date: str = "latest",
):
    """Use this to get current exchange rate.

    Args:
        currency_from: The currency to convert from (e.g., "USD").
        currency_to: The currency to convert to (e.g., "EUR").
        currency_date: The date for the exchange rate or "latest". Defaults to "latest".

    Returns:
        A dictionary containing the exchange rate data, or an error message if the request fails.
    """
    logger.info(
        f"--- 🛠️ Tool: get_exchange_rate called for converting {currency_from} to {currency_to} ---"
    )
    try:
        response = httpx.get(
            f"https://api.frankfurter.dev/v1/{currency_date}",
            params={"from": currency_from, "to": currency_to},
        )
        response.raise_for_status()

        data = response.json()
        if "rates" not in data:
            logger.error(f"❌ rates not found in response: {data}")
            return {"error": "Invalid API response format."}
        logger.info(f"✅ API response: {data}")
        return data
    except httpx.HTTPError as e:
        logger.error(f"❌ API request failed: {e}")
        return {"error": f"API request failed: {e}"}
    except ValueError:
        logger.error("❌ Invalid JSON response from API")
        return {"error": "Invalid JSON response from API."}


if __name__ == "__main__":
    port = int(os.getenv("PORT", 8080))
    logger.info(f"🚀 MCP server started on port {port}")
    # Could also use 'sse' transport, host="0.0.0.0" required for Cloud Run.
    asyncio.run(
        mcp.run_async(
            transport="http",
            host="0.0.0.0",
            port=port,
        )
    )

הפעלת שרת ה-MCP באופן מקומי

מפעילים את שרת ה-MCP בטרמינל:

uv run python currency_mcp_server/server.py

השרת מתחיל ביציאה 8080 עם נקודת הקצה (endpoint) של ה-HTTP שניתן להעביר בסטרימינג, שזמינה בכתובת /mcp (http://localhost:8080/mcp).

בדיקת שרת ה-MCP

פותחים חלון טרמינל שני (משאירים את שרת ה-MCP פועל בחלון הראשון), עוברים לתיקייה python (cd getting-started-mcp-a2a-adk/python) ומריצים את סקריפט הבדיקה:

uv run python currency_mcp_server/test_server.py

סקריפט הבדיקה מתחבר לנקודת הקצה של ה-MCP באמצעות Client של FastMCP, מציג את הכלים ומפעיל את get_exchange_rate:

Connecting to MCP server at: http://localhost:8080/mcp
--- 🛠️  Tool found: get_exchange_rate ---
--- 🪛  Calling get_exchange_rate tool for USD to EUR ---
--- ✅  Success: {"amount":1.0,"base":"USD","date":"2026-09-29","rates":{"EUR":0.84919}} ---

מדהים! יש לכם שרת MCP פעיל שחושף כלי פעיל להמרת מטבעות. משאירים את הטרמינל פתוח לשלבים הבאים.

4. יצירת סוכן המטבעות באמצעות ADK

עכשיו הגיע הזמן ליצור את סוכן המטבעות באמצעות הערכה לפיתוח סוכנים (ADK).

ערכת ADK מספקת תמיכה מובנית בכלים של MCP באמצעות המחלקה McpToolset. הסוכן להמרת מטבעות מתחבר לשרת ה-MCP שלנו באמצעות StreamableHTTPConnectionParams.

כך נראית ההגדרה של סוכן המטבע ב-currency_agent/agent.py:

import logging
import os
from urllib.parse import urlparse

from dotenv import load_dotenv
from google.adk.agents import LlmAgent
from google.adk.a2a.utils.agent_to_a2a import to_a2a
from google.adk.tools.mcp_tool import McpToolset, StreamableHTTPConnectionParams

logger = logging.getLogger(__name__)
logging.basicConfig(format="[%(levelname)s]: %(message)s", level=logging.INFO)

load_dotenv()

SYSTEM_INSTRUCTION = (
    "You are a specialized assistant for currency conversions. "
    "Your sole purpose is to use the 'get_exchange_rate' tool to answer questions about currency exchange rates. "
    "If the user asks about anything other than currency conversion or exchange rates, "
    "politely state that you cannot help with that topic and can only assist with currency-related queries. "
    "Do not attempt to answer unrelated questions or use tools for other purposes."
)

mcp_server_url = os.getenv("MCP_SERVER_URL", "http://localhost:8080/mcp")
if not mcp_server_url.endswith("/mcp"):
    mcp_server_url = f"{mcp_server_url.rstrip('/')}/mcp"

logger.info(f"--- 🔧 Loading MCP tools from MCP Server at {mcp_server_url}... ---")
logger.info("--- 🤖 Creating ADK Currency Agent... ---")

root_agent = LlmAgent(
    model="gemini-3.8-flash",
    name="currency_agent",
    description="An agent that can help with currency conversions",
    instruction=SYSTEM_INSTRUCTION,
    tools=[
        McpToolset(
            connection_params=StreamableHTTPConnectionParams(
                url=mcp_server_url
            )
        )
    ],
)

שימו לב ש-McpToolset מזהה וטוען באופן אוטומטי את הכלי get_exchange_rate ישירות משרת ה-MCP הפעיל דרך HTTP.

בדיקת סוכן המטבעות באמצעות ממשק המשתמש של ADK באינטרנט

אתם יכולים לבדוק את הסוכן בנפרד באמצעות ממשק האינטרנט לפיתוח של ADK. בטרמינל השני, מריצים את הפקודה:

uv run adk web --allow_origins "*" currency_agent

פותחים את הדפדפן בכתובת http://localhost:8000 (או לוחצים על Web Preview ב-Cloud Shell).

סוכן המטבעות של ADK Web

אפשר לשאול את הסוכן:

"What is 250 USD in EUR?"

הנציג יפעיל את כלי ה-MCP של get_exchange_rate ויחזיר את הסכום שהומר. לפני שממשיכים, צריך לעצור את התהליך adk web באמצעות Ctrl+C.

5. פרוטוקול Agent2Agent‏ (A2A)

‫פרוטוקול Agent2Agent‏ (A2A) הוא תקן פתוח שנועד לאפשר תקשורת ושיתוף פעולה בין סוכני AI.

במערכות מרובות סוכנים מודרניות, הסוכנים נבנים בדרך כלל באמצעות מסגרות שונות (ADK, ‏ LangChain, ‏ CrewAI, ‏ AutoGen) ונפרסים בתשתיות שונות. A2A מפרק את המאגרים האלה על ידי הגדרת חוזה תקשורת סטנדרטי.

פרוטוקול A2A

מושגי מפתח של A2A

  • גילוי סוכנים (כרטיס סוכן): כל סוכן שתואם ל-A2A מפרסם את היכולות שלו, את מצבי הקלט/פלט הנתמכים, את דרישות האימות ואת המיומנויות שלו באמצעות קובץ JSON סטנדרטי שמתארח בכתובת /.well-known/agent-card.json.
  • מיומנויות של הסוכן (AgentSkill): יכולות או כלים ספציפיים שהסוכן יכול להפעיל. סוכנים אחרים קוראים את המיומנויות האלה כדי להחליט מתי להעביר משימות.
  • תקשורת JSON-RPC: הנציגים מחליפים הודעות, משימות ופריטי מידע מובְנים באמצעות פרוטוקולי תעבורה סטנדרטיים כמו HTTP JSON-RPC 2.0.

כרטיס סוכן A2A

בשלב הבא, תחשפו את סוכן המטבעות של ADK כשרת A2A כדי שסוכנים אחרים יוכלו לגלות אותו ולהקצות לו משימות של המרת מטבעות.

6. חשיפת סוכן המטבעות כשרת A2A

בעזרת ADK, קל להפוך כל LlmAgent לשרת שתואם ל-A2A באמצעות הפונקציה הבסיסית to_a2a().

בודקים את החלק התחתון של currency_agent/agent.py:

# Make the agent A2A-compatible
PORT = int(os.getenv("PORT", 8081))
AGENT_URL = os.getenv("AGENT_URL")

if AGENT_URL:
    parsed = urlparse(AGENT_URL)
    protocol = parsed.scheme or "https"
    host = parsed.hostname
    port = parsed.port or 443
else:
    protocol = "http"
    host = "localhost"
    port = PORT

a2a_app = to_a2a(root_agent, host=host, port=port, protocol=protocol)

if __name__ == "__main__":
    import uvicorn

    logger.info(f"🚀 Starting currency_agent on port {PORT}")
    uvicorn.run(a2a_app, host="0.0.0.0", port=PORT)

הכלי to_a2a() מבצע באופן אוטומטי את הפעולות הבאות:

  1. בודק את הכלים וההוראות של LlmAgent.
  2. יוצר AgentCard מסוג A2A שמתאר את המיומנויות שלו (למשל, currency_agent-get_exchange_rate).
  3. הנציג נארז באפליקציית FastAPI‏ (a2a_app) שתומכת בנקודות קצה של JSON-RPC.
  4. מגדיר באופן דינמי את url שפורסם בכרטיס הנציג על סמך AGENT_URL (או localhost).

הפעלת שרת A2A של סוכן המטבעות

בטרמינל השני, מפעילים את סוכן המטבע:

uv run python currency_agent/agent.py

השרת מופעל ביציאה 8081:

[INFO]: --- 🔧 Loading MCP tools from MCP Server at http://localhost:8080/mcp... ---
[INFO]: --- 🤖 Creating ADK Currency Agent... ---
[INFO]: 🚀 Starting currency_agent on port 8081
INFO:     Started server process
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:8081 (Press CTRL+C to quit)

בדיקת כרטיס הנציג

בדפדפן, נכנסים לנקודת הקצה של כרטיס הנציג: http://localhost:8081/.well-known/agent-card.json

יוצגו המטא-נתונים בפורמט JSON שנוצרו אוטומטית:

{
  "name": "currency_agent",
  "description": "An agent that can help with currency conversions",
  "supportedInterfaces": [
    {
      "url": "http://localhost:8081",
      "protocolBinding": "JSONRPC",
      "protocolVersion": "1.0"
    }
  ],
  "version": "0.0.1",
  "capabilities": {
    "streaming": true
  },
  "defaultInputModes": [
    "text/plain"
  ],
  "defaultOutputModes": [
    "text/plain"
  ],
  "skills": [
    {
      "id": "currency_agent",
      "name": "model",
      "description": "An agent that can help with currency conversions",
      "tags": [
        "llm"
      ]
    },
    {
      "id": "currency_agent-get_exchange_rate",
      "name": "get_exchange_rate",
      "description": "Use this to get current exchange rate.",
      "tags": [
        "llm",
        "tools"
      ]
    }
  ]
}

בדיקה באמצעות לקוח A2A

פותחים טרמינל שלישי, עוברים לתיקייה python (cd getting-started-mcp-a2a-adk/python) ובודקים את נקודת הקצה של A2A באמצעות הסקריפט הייעודי של לקוח הבדיקה של A2A:

uv run python currency_agent/test_a2aclient.py

הלקוח פותר את כרטיס הנציג באמצעות A2ACardResolver ושולח הודעת JSON-RPC:

🔄 Fetching the agent card at http://localhost:8081
✅ Successfully fetched the agent card
====================================================
                     AgentCard                      
====================================================
Name        : currency_agent
Description : An agent that can help with currency conversions
Interfaces  : [0] http://localhost:8081  (JSONRPC 1.0)
Skills      : currency_agent, currency_agent-get_exchange_rate
====================================================
🔄 Initializing a non-streaming client
Sending request: "how much is 100 USD in GBP?"
Response:
100 USD is currently equal to approximately 73.37 GBP...

סוכן המטבעות פועל עכשיו כשרת A2A שמתפקד באופן מלא.

7. יצירת סוכן הנסיעות

עכשיו תיצרו את סוכן הנסיעות שינהל את התהליך. הנציג הזה מדגים איך נציג ADK יכול לשלב בקלות שתי פרדיגמות שונות של כלים:

  1. ‫Local Agent Tool (weather_agent): סוכן משנה של ADK שפועל באותו זמן ריצה של Python, בלי זמן אחזור של הרשת או של הסדרה.
  2. כלי סוכן מרוחק (currency_agent): סוכן חיצוני שמחובר דרך הרשת באמצעות פרוטוקול A2A באמצעות RemoteA2aAgent.
                    +-----------------------------+
                    |        travel_agent         |
                    | (Orchestrator with Web UI)  |
                    +--------------+--------------+
                                   |
             +---------------------+---------------------+
             | (In-process call)                         | (A2A Network Hop)
             v                                           v
+-------------------------+                 +-------------------------+
|   weather_agent         |                 |   currency_agent        |
|  (Local AgentTool)      |                 |  (Remote A2A Agent)     |
|  Calls wttr.in API      |                 |  Calls Currency MCP Svc |
+-------------------------+                 +-------------------------+

1. סוכן המשנה "מזג האוויר המקומי"

כדאי להציץ ב-travel_agent/subagents/weather_agent.py:

import logging
import httpx
from google.adk.agents import LlmAgent

logger = logging.getLogger(__name__)

SYSTEM_INSTRUCTION = (
    "You are a specialized assistant for providing weather forecasts and conditions for travel destinations. "
    "Your sole purpose is to use the 'get_weather' tool to fetch weather information. "
    "Provide clear, concise summaries of current weather conditions, temperatures, and forecasts to help travelers pack and plan."
)


def get_weather(location: str = "London") -> dict:
    """Gets the current weather and forecast for a given location or city."""
    logger.info(f"--- 🌤️ Tool: get_weather called for {location} ---")
    try:
        response = httpx.get(
            f"https://wttr.in/{location}?format=j1",
            headers={"User-Agent": "curl/7.68.0"},
            timeout=8.0,
        )
        response.raise_for_status()
        data = response.json()
        current = data["current_condition"][0]
        return {
            "location": location,
            "condition": current.get("weatherDesc", [{}])[0].get("value", "Unknown"),
            "temperature_celsius": f"{current.get('temp_C', 'N/A')}°C",
            "temperature_fahrenheit": f"{current.get('temp_F', 'N/A')}°F",
            "humidity": f"{current.get('humidity', 'N/A')}%",
            "wind_speed_kmph": f"{current.get('windspeedKmph', 'N/A')} km/h",
        }
    except Exception as e:
        logger.warning(f"⚠️ Weather lookup failed: {e}. Returning simulated estimate.")
        return {
            "location": location,
            "condition": "Partly Cloudy",
            "temperature_celsius": "20°C",
            "temperature_fahrenheit": "68°F",
            "humidity": "55%",
            "wind_speed_kmph": "10 km/h",
            "note": f"Live data unavailable ({e}); showing estimated conditions.",
        }


weather_agent = LlmAgent(
    model="gemini-3.8-flash",
    name="weather_agent",
    description="A local specialist agent that provides current weather reports and forecasts for travel destinations.",
    instruction=SYSTEM_INSTRUCTION,
    tools=[get_weather],
)

סוכן מזג האוויר הוא LlmAgent עם כלי פונקציה שמאחזר נתוני מזג אוויר בזמן אמת מ-wttr.in עם חלופה גמישה.

2. כלי התזמור של סוכן הנסיעות

עכשיו נסתכל על travel_agent/agent.py:

import logging
import os
from dotenv import load_dotenv
from google.adk.agents import LlmAgent
from google.adk.tools.agent_tool import AgentTool
from google.adk.agents.remote_a2a_agent import RemoteA2aAgent, AGENT_CARD_WELL_KNOWN_PATH

try:
    from travel_agent.subagents.weather_agent import weather_agent
except ImportError:
    from subagents.weather_agent import weather_agent

logger = logging.getLogger(__name__)
logging.basicConfig(format="[%(levelname)s]: %(message)s", level=logging.INFO)

load_dotenv()

SYSTEM_INSTRUCTION = (
    "You are a helpful travel assistant. You help users plan trips, recommend places, "
    "and answer travel-related questions.\n\n"
    "- Whenever a user asks about currency exchange rates or money conversions, "
    "delegate the request to the 'currency_agent' tool.\n"
    "- Whenever a user asks about the weather, climate, or forecasts for a destination, "
    "delegate the request to the 'weather_agent' tool."
)

CURRENCY_AGENT_URL = os.getenv("CURRENCY_AGENT_URL", "http://localhost:8081").rstrip("/")

logger.info(
    "--- 🔗 Connecting to Remote A2A Currency Agent at %s... ---",
    CURRENCY_AGENT_URL,
)

currency_remote_agent = RemoteA2aAgent(
    name="currency_agent",
    agent_card=f"{CURRENCY_AGENT_URL}{AGENT_CARD_WELL_KNOWN_PATH}",
    description="An agent that can help with currency conversions and exchange rates.",
)

logger.info("--- 🤖 Creating ADK Travel Agent... ---")

root_agent = LlmAgent(
    model="gemini-3.8-flash",
    name="travel_agent",
    description="A travel assistant that can help plan trips, check weather forecasts via the local weather agent, and convert currencies via the remote currency agent.",
    instruction=SYSTEM_INSTRUCTION,
    tools=[
        AgentTool(agent=currency_remote_agent),
        AgentTool(agent=weather_agent),
    ],
)

if __name__ == "__main__":
    import uvicorn
    from google.adk.cli.fast_api import get_fast_api_app

    PORT = int(os.getenv("PORT", 8082))
    logger.info(f"🚀 Starting travel_agent Web UI on port {PORT}")
    app = get_fast_api_app(web=True, allow_origins=["*"], agents_dir=os.path.dirname(__file__) or ".")
    uvicorn.run(app, host="0.0.0.0", port=PORT)

הנקודות העיקריות:

  • ‫RemoteA2aAgent מאחזר את הכרטיס של סוכן המטבעות מ-http://localhost:8081/.well-known/agent-card.json.
  • גם currency_remote_agent וגם weather_agent מוגנים באמצעות AgentTool של ADK.
  • הפעלת travel_agent/agent.py מפעילה ישירות את ממשק המשתמש של ADK באינטרנט ביציאה 8082 באמצעות get_fast_api_app(web=True).

8. הרצה ובדיקה של המערכת מרובת הסוכנים באופן מקומי

עכשיו כל שלושת הרכיבים מוכנים. נריץ ונבדוק את המערכת המלאה באופן מקומי.

הפעלת כל שלושת השירותים

מוודאים שהשירותים פועלים בשלושה חלונות טרמינל נפרדים (מהספרייה getting-started-mcp-a2a-adk/python) בסדר הבא:

טרמינל 1 (שרת MCP להמרת מטבעות):

uv run python currency_mcp_server/server.py

(פועל ביציאה 8080)

טרמינל 2 (סוכן A2A להמרת מטבעות):

uv run python currency_agent/agent.py

(פועל ביציאה 8081)

טרמינל 3 (סוכן נסיעות עם ממשק משתמש באינטרנט):

uv run python travel_agent/agent.py

(פועל ביציאה 8082)

בדיקה דרך ממשק המשתמש של ADK באינטרנט

פותחים את הדפדפן בכתובת http://localhost:8082 (או משתמשים ב-Web Preview ביציאה 8082 ב-Cloud Shell).

שואלים את סוכן הנסיעות שאלה מורכבת שדורשת שימוש בשני הכלים:

"אני מתכנן טיול ללונדון. What is the weather like and how much is 200 EUR in GBP?‎"

צפו בתהליך הביצוע:

  1. סוכן הנסיעות מקבל את הבקשה בממשק המשתמש באינטרנט.
  2. הוא מעביר את השאלה לגבי מזג האוויר ל-weather_agent המקומי, שמקבל את התנאים הנוכחיים מ-wttr.in.
  3. הוא מעביר את המרת המטבע דרך פרוטוקול A2A אל currency_agent ביציאה 8081.
  4. ‫currency_agent קורא ל-currency_mcp_server ביציאה 8080 דרך MCP כדי לאחזר את שער החליפין.
  5. כל התוצאות מסונתזות ומוחזרות לכם בתשובה אחת מועילה.

סוכן המטבעות המרוחק של ADK Web

בדיקה באמצעות שורת הפקודה

אפשר גם לבדוק שאילתות ישירות מה-CLI בלי לפתוח דפדפן:

uv run adk run travel_agent "What is the weather in London and how much is 100 USD in EUR?"

עכשיו, כשהכול פועל בצורה חלקה במכונה המקומית, נפרוס את המערכת כולה ב-Google Cloud Run.

9. פריסת המערכת מרובת הסוכנים ב-Cloud Run

לכל רכיב יש Dockerfile משלו ו-pyproject.toml עצמאי, כך שאפשר ליצור ולפרוס אותו בנפרד ב-Cloud Run.

באמצעות gcloud, תפרסו כל שירות ותקשרו את כתובות ה-URL שלהם באופן אוטומטי.

הגדרה של משתני סביבה

מגדירים את מזהה הפרויקט בענן ואת אזור היעד:

export PROJECT_ID=<YOUR_GOOGLE_CLOUD_PROJECT_ID>
export REGION=us-central1

שלב 1: פריסת שרת ה-MCP של המטבעות

פורסים את שרת ה-MCP ישירות מהמקור:

# 1. Deploy MCP server
gcloud run deploy currency-mcp-server \
  --source currency_mcp_server \
  --region $REGION \
  --allow-unauthenticated

# 2. Capture the deployed MCP server URL
MCP_SERVER_URL=$(gcloud run services describe currency-mcp-server --region $REGION --format='value(status.url)')/mcp
echo "MCP Server URL: $MCP_SERVER_URL"

שלב 2: פריסת Currency Agent

כתובת ה-URL הציבורית של currency-agent מוקצית בפריסה הראשונה שלו, לכן צריך לפרוס את השירות קודם, לתעד את כתובת ה-URL שלו ולעדכן את משתנה הסביבה AGENT_URL שלו כדי שכרטיס סוכן ה-A2A שלו יפרסם את כתובת ה-HTTPS הציבורית הנכונה:

# 1. Deploy Currency Agent connected to the deployed MCP Server
gcloud run deploy currency-agent \
  --source currency_agent \
  --region $REGION \
  --allow-unauthenticated \
  --update-env-vars MCP_SERVER_URL="$MCP_SERVER_URL",GOOGLE_GENAI_USE_ENTERPRISE="true",GOOGLE_CLOUD_PROJECT="$PROJECT_ID",GOOGLE_CLOUD_LOCATION="global"

# 2. Capture its assigned Cloud Run URL
CURRENCY_AGENT_URL=$(gcloud run services describe currency-agent --region $REGION --format='value(status.url)')
echo "Currency Agent URL: $CURRENCY_AGENT_URL"

# 3. Update AGENT_URL so the agent advertises its public HTTPS endpoint in its Agent Card
gcloud run services update currency-agent \
  --region $REGION \
  --update-env-vars AGENT_URL="$CURRENCY_AGENT_URL"

שלב 3: פריסת סוכן הנסיעות

עכשיו פורסים את סוכן הנסיעות ומגדירים אותו באמצעות CURRENCY_AGENT_URL:

# 1. Deploy Travel Agent (with ADK Web UI) connected to Currency Agent
gcloud run deploy travel-agent \
  --source travel_agent \
  --region $REGION \
  --allow-unauthenticated \
  --update-env-vars CURRENCY_AGENT_URL="$CURRENCY_AGENT_URL",GOOGLE_GENAI_USE_ENTERPRISE="true",GOOGLE_CLOUD_PROJECT="$PROJECT_ID",GOOGLE_CLOUD_LOCATION="global"

# 2. Capture its assigned Cloud Run URL
TRAVEL_AGENT_URL=$(gcloud run services describe travel-agent --region $REGION --format='value(status.url)')
echo "Travel Agent URL: $TRAVEL_AGENT_URL"

10. בדיקת המערכת מרובת הסוכנים ב-Cloud Run

אחרי שפורסים את שלושת המיקרו-שירותים, מאמתים כל שכבה ב-Cloud Run.

1. בדיקת שרת ה-MCP המרוחק

בודקים את שרת ה-MCP של Cloud Run שנפרס משורת הפקודה:

MCP_SERVER_URL="$MCP_SERVER_URL" uv run python currency_mcp_server/test_server.py

הכלי אמור להופיע ברשימה ולהיות מופעל בהצלחה ב-Cloud Run.

2. בדיקת סוכן המטבעות ב-A2A

בודקים את סוכן המטבעות שנפרס באמצעות לקוח הבדיקה של A2A:

AGENT_URL="$CURRENCY_AGENT_URL" uv run python currency_agent/test_a2aclient.py

הלקוח יאחזר את כרטיס הנציג המרוחק מ-Cloud Run ויחליף הודעות באמצעות HTTPS JSON-RPC.

3. בדיקת ממשק המשתמש של סוכן הנסיעות באינטרנט ב-Cloud Run

פותחים את $TRAVEL_AGENT_URL בדפדפן.

ממשק המשתמש המלא של ADK Web מוגש ישירות מ-Cloud Run. מומלץ לשאול:

"אני מתכננת טיול לטוקיו. מה מזג האוויר וכמה זה 500 דולר ארה"ב בין יפני?"

[Browser] 
   │
   ▼ (HTTPS)
[Cloud Run: travel-agent]
   ├──> Local in-process tool: weather_agent (wttr.in)
   └──> A2A JSON-RPC (HTTPS)
           │
           ▼
        [Cloud Run: currency-agent]
           └──> MCP Streamable HTTP (HTTPS)
                   │
                   ▼
                [Cloud Run: currency-mcp-server] (Frankfurter API)

כל שלושת השירותים מותאמים לעומס באופן עצמאי בתשתית בלי שרת (serverless), ומתקשרים בצורה חלקה באמצעות פרוטוקולים פתוחים.

11. כל הכבוד! עכשיו נשאר רק לנקות

ההישגים שלכם

מעולה! יצרתם ופרסתם מערכת מרובת סוכנים ברמת ייצור:

  • ‫Model Context Protocol (MCP): יצרנו שרת FastMCP שחושף כלי API פעילים דרך Streamable HTTP.
  • ערכה לפיתוח סוכנים (ADK): יצרתם סוכנים מודולריים באמצעות LlmAgent, קישרתם כלי MCP באמצעות McpToolset ואירחתם את ממשק המשתמש של ADK באינטרנט.
  • פרוטוקול Agent2Agent‏ (A2A): חשיפת סוכן כשירות A2A‏ (to_a2a), פרסום כרטיסי סוכנים (agent-card.json) ושימוש בסוכנים מרוחקים באמצעות RemoteA2aAgent.
  • ארכיטקטורה של כמה סוכנים: שילוב של סוכני משנה מקומיים בתהליך (weather_agent) עם סוכנים מרוחקים מבוזרים (currency_agent).
  • פריסה ללא שרת (serverless) ב-Cloud Run: כל שלושת השירותים הועברו לקונטיינרים ונפרסו עם הגדרות חיווט אוטומטיות.

כדי לראות את הקוד המלא (כולל ההטמעות של Python ו-Java) ועדכונים עתידיים, אפשר להיכנס למאגר getting-started-mcp-a2a-adk.

הסרת המשאבים

כדי להימנע מחיובים בחשבון Google Cloud על שירותי Cloud Run שנוצרו ב-Codelab הזה:

מוחקים את שירותי Cloud Run שנפרסו:

gcloud run services delete currency-mcp-server --region $REGION --quiet
gcloud run services delete currency-agent --region $REGION --quiet
gcloud run services delete travel-agent --region $REGION --quiet

לחלופין, אם יצרתם פרויקט ייעודי ל-Codelab הזה, אתם יכולים להשבית את הפרויקט כולו:

gcloud projects delete $PROJECT_ID