לדלג לתוכן

7.4 בניית שרת MCP הרצאה

בהרצאות הקודמות ראינו איך לתת למודל tools - הגדרנו אותם בתוך בקשת ה-API, או השתמשנו בtools המובנים של ה-Agent SDK. אבל יש בעיה: הtools שהגדרנו קשורים לתוכנית ספציפית. אם כתבנו tool מזג אוויר בתוך סקריפט פייתון, הוא זמין רק שם. מה אם נרצה שאותו tool יהיה זמין ל-Claude Code, ל-Claude Desktop, ל-Agent SDK, ולכל tool AI אחר - בלי לכתוב אותו מחדש בכל מקום? זו בדיוק הבעיה ש-MCP פותר. MCP, ראשי תיבות של Model Context Protocol, הוא פרוטוקול פתוח וסטנדרטי שמגדיר איך אפליקציית AI (מארח) מדברת עם שרת שחושף tools, נתונים וprompts. כשאנחנו כותבים שרת MCP פעם אחת, כל מארח שתומך בפרוטוקול יכול להשתמש בו.

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


מהו MCP - the Model Context Protocol

MCP הוא כמו "USB לtool AI". במקום שכל אפליקציה תמציא דרך משלה לחבר tools, כולן מדברות את אותו פרוטוקול. שרת MCP יכול לחשוף שלושה סוגי יכולות:

  • Tools: פונקציות שהמודל יכול לקרוא (עם אישור המשתמש). זה מה שנתמקד בו.
  • משאבים - resources: נתונים דמויי קבצים שהמארח יכול לקרוא (למשל תשובות API או תוכן קבצים).
  • Prompts: תבניות מוכנות שעוזרות למשתמש לבצע משימות.

הצד היפה: המארח (Claude Code, Claude Desktop, Agent SDK) הוא הלקוח, והשרת שלנו מספק את הtools. הם מדברים ביניהם דרך הפרוטוקול, בפורמט JSON-RPC. אנחנו לא צריכים לדעת את הפרטים - ה-SDK של MCP עוטף הכל.


הכנת הסביבה - environment setup

נשתמש ב-SDK של MCP לפייתון. החבילה נקראת mcp, ומתקינים אותה עם התוספת cli. הדרך המומלצת היא עם הtool uv, אבל גם pip רגיל עובד:

# with uv (recommended)
uv init weather
cd weather
uv venv
source .venv/bin/activate
uv add "mcp[cli]" httpx

# or with plain pip
pip install "mcp[cli]" httpx

דרישות: Python 3.10 ומעלה, וגרסת MCP SDK 1.2.0 ומעלה.

הערה קריטית על תיעוד (logging): בשרת MCP שמתקשר דרך stdio (הקלט/פלט הסטנדרטי), אסור לכתוב ל-stdout. פקודת print רגילה כותבת ל-stdout, וזה ישבור את השרת כי הוא ידרוס את הודעות ה-JSON-RPC. אם צריך להדפיס לניפוי, כותבים ל-stderr:

import sys
print("Processing request", file=sys.stderr)   # safe - written to stderr

יצירת השרת - creating the server

נשתמש במחלקה FastMCP, שמפשטת מאוד את הכתיבה. היא משתמשת ברמזי טיפוסים (type hints) וב-docstrings של פייתון כדי לייצר את הגדרות הtools אוטומטית. ניצור קובץ בשם weather.py ונתחיל:

from typing import Any
import httpx
from mcp.server.fastmcp import FastMCP

# initialize a FastMCP server with a name
mcp = FastMCP("weather")

# constants
NWS_API_BASE = "https://api.weather.gov"
USER_AGENT = "weather-app/1.0"

זהו - יצרנו שרת. עכשיו צריך להוסיף לו tool.


הגדרת tool - defining a tool

מגדירים tool עם הדקורטור @mcp.tool(). ה-docstring הופך לתיאור הtool, ורמזי הטיפוסים הופכים לסכמת הקלט. הנה tool שמחזיר התראות מזג אוויר עבור מדינה בארה"ב:

async def make_nws_request(url: str) -> dict[str, Any] | None:
    """Sends a request to the NWS API with error handling."""
    headers = {"User-Agent": USER_AGENT, "Accept": "application/geo+json"}
    async with httpx.AsyncClient() as client:
        try:
            response = await client.get(url, headers=headers, timeout=30.0)
            response.raise_for_status()
            return response.json()
        except Exception:
            return None


@mcp.tool()
async def get_alerts(state: str) -> str:
    """Returns weather alerts for a US state.

    Args:
        state: Two-letter state code, e.g. CA or NY.
    """
    url = f"{NWS_API_BASE}/alerts/active/area/{state}"
    data = await make_nws_request(url)

    if not data or "features" not in data:
        return "לא ניתן להביא התראות או שאין התראות."

    if not data["features"]:
        return "אין התראות פעילות במדינה הזו."

    alerts = []
    for feature in data["features"]:
        props = feature["properties"]
        alerts.append(f"אירוע: {props.get('event', 'לא ידוע')}")
    return "\n---\n".join(alerts)

שימו לב כמה זה נקי: לא כתבנו JSON Schema ידנית. הדקורטור לוקח את החתימה get_alerts(state: str) וגוזר ממנה שהtool מקבל פרמטר state מסוג מחרוזת. ה-docstring מסביר למודל מתי להשתמש בtool.


הרצת השרת - running the server

בסוף הקובץ מוסיפים את נקודת הכניסה שמריצה את השרת. נשתמש בתעבורת stdio - השרת מקבל בקשות דרך הקלט הסטנדרטי ומחזיר תשובות דרך הפלט:

def main():
    # initialize and run the server over stdio
    mcp.run(transport="stdio")


if __name__ == "__main__":
    main()

השרת מוכן. מריצים אותו כדי לבדוק שהוא עולה:

uv run weather.py

השרת יתחיל להאזין להודעות ממארחי MCP. הוא לא ידפיס כלום ולא יסתיים - הוא מחכה בשקט לבקשות. זה הצפוי.


חיבור ל-Claude Code - wiring it in

עכשיו החלק המעניין: לחבר את השרת ל-Claude Code כדי שיוכל להשתמש בtool שלנו. הדרך הפשוטה היא הפקודה claude mcp add, שמקבלת שם לשרת ואת הפקודה שמפעילה אותו:

claude mcp add weather -- uv --directory /absolute/path/to/weather run weather.py

הפקודה אומרת ל-Claude Code: "יש שרת MCP בשם weather, והפעל אותו על ידי הרצת הפקודה הזו". אחרי זה, בתוך Claude Code, הtool get_alerts יהיה זמין - המודל יוכל לקרוא לו כשמישהו שואל על התראות מזג אוויר.

לחלופין, אפשר להגדיר את השרת בקובץ .mcp.json בשורש הפרויקט. הפורמט הוא מפתח mcpServers שמכיל את השרתים - אותו פורמט בדיוק שבו משתמש גם Claude Desktop (שם בקובץ claude_desktop_config.json):

{
  "mcpServers": {
    "weather": {
      "command": "uv",
      "args": [
        "--directory",
        "/absolute/path/to/weather",
        "run",
        "weather.py"
      ]
    }
  }
}

שתי נקודות חשובות: הנתיב חייב להיות מוחלט (אפשר לקבל אותו עם pwd), ולפעמים צריך לתת את הנתיב המלא לפקודה uv עצמה (מקבלים עם which uv). אחרי שמירת ההגדרה, מפעילים מחדש את המארח.


בדיקה ואימות - testing

אחרי החיבור, אפשר לבדוק שהשרת רשום. ב-Claude Code:

claude mcp list

הפקודה תציג את השרתים המחוברים ואת הסטטוס שלהם. אם weather מופיע עם סטטוס תקין, הכל עובד. עכשיו אפשר לשאול את Claude Code שאלה כמו "האם יש התראות מזג אוויר בקליפורניה?", והמודל יזהה שהtool get_alerts רלוונטי, יקרא לו עם state="CA", ויקבל את התוצאה חזרה - בדיוק כמו בלולאת הtools שבנינו ידנית, אבל דרך הפרוטוקול הסטנדרטי.

זה הכוח של MCP: אותו שרת weather.py, בלי שינוי, יעבוד גם ב-Claude Desktop, גם עם ה-Agent SDK (דרך mcp_servers שראינו בהרצאה הקודמת), וגם עם כל מארח MCP אחר בעתיד.


מתי לבנות שרת MCP - when to build one

לא כל tool צריך להיות שרת MCP. אם הtool רלוונטי רק לתוכנית אחת, פשוט להגדיר אותו בתוך בקשת ה-API (כפי שראינו בהרצאה 7.2). בונים שרת MCP כאשר:

  • רוצים לחשוף את הtool לכמה מארחים או צוותים.
  • הtool מייצג יכולת רב-שימושית (גישה לבסיס נתונים פנימי, API של החברה, מערכת קבצים).
  • רוצים סטנדרטיזציה - להפריד את הגדרת הtool מהאפליקציה שמשתמשת בו.

הכלל: שרת MCP הוא ה"מוצר" - יכולת ארוזה שאפשר להתקין; הגדרת tool בקוד היא פתרון מקומי לתוכנית אחת.


עוד tool אחד - a second tool

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

@mcp.tool()
async def get_forecast(latitude: float, longitude: float) -> str:
    """Returns a weather forecast for a location.

    Args:
        latitude: The latitude of the location.
        longitude: The longitude of the location.
    """
    points_url = f"{NWS_API_BASE}/points/{latitude},{longitude}"
    points_data = await make_nws_request(points_url)
    if not points_data:
        return "לא ניתן להביא נתוני תחזית למיקום הזה."

    forecast_url = points_data["properties"]["forecast"]
    forecast_data = await make_nws_request(forecast_url)
    if not forecast_data:
        return "לא ניתן להביא תחזית מפורטת."

    periods = forecast_data["properties"]["periods"]
    lines = []
    for period in periods[:5]:   # only the next five periods
        lines.append(f"{period['name']}: {period['temperature']}°{period['temperatureUnit']}")
    return "\n---\n".join(lines)

זהו - עכשיו לשרת שני tools, get_alerts ו-get_forecast, והמודל יבחר את המתאים לפי השאלה. אין צורך ברישום ידני; הדקורטור מספיק. שרת MCP יכול לחשוף עשרות tools באותו אופן.


תעבורה: stdio מול HTTP - transports

בדוגמה השתמשנו בתעבורת stdio - השרת רץ כתת-תהליך של המארח, ומדבר איתו דרך הקלט/פלט הסטנדרטי. זו הבחירה הנכונה לשרת מקומי שרץ על אותה מכונה כמו המארח.

אבל MCP תומך גם בתעבורת HTTP - השרת רץ כשירות רשת עצמאי, והמארח מתחבר אליו דרך כתובת URL. זה מתאים כשרוצים שרת מרכזי שמשרת כמה משתמשים או צוותים מרחוק. ההבדל המעשי: בשרת stdio אסור לכתוב ל-stdout (כי הוא שמור להודעות הפרוטוקול), בעוד שבשרת HTTP רישום ל-stdout בטוח כי הוא לא מפריע לתשובות ה-HTTP. את הtools עצמם - @mcp.tool() - לא צריך לשנות; רק את שורת ה-mcp.run בסוף.


משאבים וPrompts - resources and prompts

התמקדנו בtools, אבל שרת MCP יכול לחשוף עוד שני סוגי יכולות שהזכרנו בהתחלה:

  • משאבים - resources: נתונים דמויי קבצים שהמארח יכול לקרוא - למשל תוכן של מסמך, שורה מבסיס נתונים, או תשובת API. בניגוד לtool, משאב לא "עושה" פעולה, הוא רק מספק תוכן לקריאה.
  • Prompts: תבניות מוכנות שהמשתמש יכול להפעיל, למשל תבנית ל"סקירת קוד" או "סיכום פגישה". הן חוסכות מהמשתמש לכתוב את אותו prompt שוב ושוב.

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


ניפוי שגיאות - debugging

שרת MCP רץ כתת-תהליך שקט, ולכן כשמשהו לא עובד קשה לדעת למה. כמה טכניקות שיעזרו:

  • הרצה ידנית: מריצים uv run weather.py ישירות. אם השרת קורס מיד עם שגיאה, נראה אותה בטרמינל - בדרך כלל שגיאת ייבוא או תלות חסרה.
  • תיעוד ל-stderr: כפי שהדגשנו, מדפיסים ל-stderr (לא ל-stdout). אפשר להוסיף הדפסות ניפוי בתוך הtools כדי לעקוב אחרי הקלט שהתקבל.
  • בדיקת רישום: claude mcp list מראה אם השרת מחובר ובאיזה סטטוס. סטטוס שגוי מרמז על נתיב לא נכון בהגדרה.
  • נתיבים מוחלטים: הטעות הנפוצה ביותר היא נתיב יחסי בהגדרה. תמיד משתמשים בנתיב מלא, ולפעמים גם לפקודה uv עצמה (מוצאים עם which uv).

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


סיכום

בהרצאה הזו בנינו שרת MCP משלנו וחיברנו אותו ל-Claude Code:

  • MCP (Model Context Protocol) הוא פרוטוקול פתוח שמגדיר איך מארח AI מדבר עם שרת שחושף tools, משאבים וprompts - "USB לtool AI". שרת אחד עובד בכל מארח שתומך בפרוטוקול.
  • ה-SDK לפייתון הוא החבילה mcp (מתקינים "mcp[cli]"), הדורשת Python 3.10 ומעלה.
  • יוצרים שרת עם FastMCP("name"), ומגדירים tool עם הדקורטור @mcp.tool() - הסכמה נגזרת אוטומטית מרמזי הטיפוסים וה-docstring.
  • מריצים את השרת עם mcp.run(transport="stdio"). בשרת stdio אסור לכתוב ל-stdout - זה שובר את ה-JSON-RPC; מדפיסים ל-stderr.
  • מחברים ל-Claude Code עם claude mcp add, או דרך קובץ .mcp.json עם מפתח mcpServers (אותו פורמט כמו Claude Desktop). הנתיבים חייבים להיות מוחלטים.
  • בודקים עם claude mcp list, ואז המודל יכול לקרוא לtool כשהוא רלוונטי.
  • בונים שרת MCP כשרוצים tool רב-שימושי שזמין לכמה מארחים - אחרת מספיקה הגדרת tool בקוד.

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