מדריך קלוד קוד בעברית

תיעוד 137

מתן כלים מותאמים אישית ל-Claude

הגדירו כלים מותאמים אישית באמצעות שרת ה-MCP הרץ בתוך התהליך (in-process) של Claude Agent SDK, כך ש-Claude יוכל לקרוא לפונקציות שלכם, לפנות לממשקי ה-API שלכם, ולבצע פעולות ייעודיות לתחום שלכם.

כלים מותאמים אישית מרחיבים את ה-Agent SDK בכך שהם מאפשרים לכם להגדיר פונקציות משלכם ש-Claude יכול לקרוא להן במהלך שיחה. באמצעות שרת ה-MCP של ה-SDK הרץ בתוך התהליך, אתם יכולים לתת ל-Claude גישה למסדי נתונים, לממשקי API חיצוניים, ללוגיקה ייעודית לתחום, או לכל יכולת אחרת שהיישום שלכם זקוק לה.

#התייחסות מהירה

אם אתם רוצים...עשו זאת
להגדיר כליהשתמשו ב-@tool (ב-Python) או ב-tool() (ב-TypeScript) עם שם, תיאור, סכמה ומטפל (handler). ראו יצירת כלי מותאם אישית.
לרשום כלי מול Claudeעטפו ב-create_sdk_mcp_server / createSdkMcpServer והעבירו ל-mcpServers בתוך query(). ראו קריאה לכלי מותאם אישית.
לאשר כלי מראשהוסיפו אותו לכלים המורשים שלכם. ראו הגדרת כלים מורשים.
להסיר כלי מובנה מההקשר של Claudeהעבירו מערך tools המפרט רק את הכלים המובנים שאתם רוצים. ראו הגדרת כלים מורשים.
לאפשר ל-Claude לקרוא לכלים במקבילהגדירו readOnlyHint: true בכלים ללא תופעות לוואי. ראו הוספת ביאורים לכלים.
לשלוט בהודעת השגיאה ש-Claude קוראהחזירו isError: true כדי להרכיב את ההודעה במקום להציג את החריגה הגולמית. ראו טיפול בשגיאות.
להחזיר תמונות או קבציםהשתמשו בבלוקי image או resource במערך התוכן (content). ראו החזרת תמונות ומשאבים.
להחזיר תוצאת JSON קריאה למכונההגדירו structuredContent בתוצאה. ראו החזרת נתונים מובנים.
להתרחב לכלים רביםהשתמשו ב-חיפוש כלים כדי לטעון כלים לפי דרישה.

#יצירת כלי מותאם אישית

כלי מוגדר על ידי ארבעה חלקים, המועברים כארגומנטים לעוזר tool() ב-TypeScript או לדקורטור @tool ב-Python:

  • שם: מזהה ייחודי שבו Claude משתמש כדי לקרוא לכלי.
  • תיאור: מה הכלי עושה. Claude קורא זאת כדי להחליט מתי לקרוא לו.
  • סכמת קלט: הארגומנטים שעל Claude לספק. ב-TypeScript זו תמיד סכמת Zod, וה-args של ה-handler מקבלים טיפוס מתוכה באופן אוטומטי. ב-Python זהו מילון הממפה שמות לטיפוסים, כמו {"latitude": float}, שה-SDK ממיר עבורכם ל-JSON Schema. הדקורטור ב-Python מקבל גם מילון JSON Schema מלא ישירות כאשר אתם זקוקים ל-enums, טווחים, שדות אופציונליים או אובייקטים מקוננים.
  • מטפל (handler): הפונקציה האסינכרונית שרצה כאשר Claude קורא לכלי. היא מקבלת את הארגומנטים המאומתים וחייבת להחזיר אובייקט עם:
    • content (חובה): מערך של בלוקי תוצאה, כל אחד עם type של "text", "image", "audio", "resource", או "resource_link". ראו החזרת תמונות ומשאבים עבור בלוקים שאינם טקסט.
    • structuredContent (אופציונלי): אובייקט JSON המחזיק את התוצאה כנתונים קריאים למכונה, המוחזר לצד content. ראו החזרת נתונים מובנים.
    • isError (אופציונלי): מוגדר ל-true כדי לסמן כשל של הכלי כך ש-Claude יוכל להגיב אליו. ראו טיפול בשגיאות.

לאחר הגדרת כלי, עטפו אותו בשרת באמצעות createSdkMcpServer (ב-TypeScript) או create_sdk_mcp_server (ב-Python). השרת רץ בתוך התהליך (in-process) בתוך היישום שלכם, ולא כתהליך נפרד.

#דוגמה לכלי מזג אוויר

דוגמה זו מגדירה כלי get_temperature ועוטפת אותו בשרת MCP. היא מגדירה את הכלי בלבד, כדי להעביר אותו ל-query ולהריץ אותו, ראו קריאה לכלי מותאם אישית בהמשך.

#Python

from typing import Any
import httpx
from claude_agent_sdk import tool, create_sdk_mcp_server


# Define a tool: name, description, input schema, handler
@tool(
    "get_temperature",
    "Get the current temperature at a location",
    {"latitude": float, "longitude": float},
)
async def get_temperature(args: dict[str, Any]) -> dict[str, Any]:
    async with httpx.AsyncClient() as client:
        response = await client.get(
            "https://api.open-meteo.com/v1/forecast",
            params={
                "latitude": args["latitude"],
                "longitude": args["longitude"],
                "current": "temperature_2m",
                "temperature_unit": "fahrenheit",
            },
        )
        data = response.json()

    
# Return a content array - Claude sees this as the tool result
    return {
        "content": [
            {
                "type": "text",
                "text": f"Temperature: {data['current']['temperature_2m']}°F",
            }
        ]
    }


# Wrap the tool in an in-process MCP server
weather_server = create_sdk_mcp_server(
    name="weather",
    version="1.0.0",
    tools=[get_temperature],
)

#TypeScript

import { tool, createSdkMcpServer } from "@anthropic-ai/claude-agent-sdk";
import { z } from "zod";

// Define a tool: name, description, input schema, handler
const getTemperature = tool(
  "get_temperature",
  "Get the current temperature at a location",
  {
    latitude: z.number().describe("Latitude coordinate"), // .describe() adds a field description Claude sees
    longitude: z.number().describe("Longitude coordinate")
  },
  async (args) => {
    // args is typed from the schema: { latitude: number; longitude: number }
    const response = await fetch(
      `https://api.open-meteo.com/v1/forecast?latitude=${args.latitude}&longitude=${args.longitude}&current=temperature_2m&temperature_unit=fahrenheit`
    );
    const data: any = await response.json();

    // Return a content array - Claude sees this as the tool result
    return {
      content: [{ type: "text", text: `Temperature: ${data.current.temperature_2m}°F` }]
    };
  }
);

// Wrap the tool in an in-process MCP server
const weatherServer = createSdkMcpServer({
  name: "weather",
  version: "1.0.0",
  tools: [getTemperature]
});

ראו את הפניית ה-TypeScript של tool() או את הפניית ה-Python של @tool לפרטים מלאים על הפרמטרים, כולל פורמטים של קלט ב-JSON Schema ומבנה ערכי החזרה.

טיפ: כדי להפוך פרמטר לאופציונלי: ב-TypeScript, הוסיפו .default() לשדה ה-Zod. ב-Python, סכמת המילון מתייחסת לכל מפתח כחובה, לכן השמיטו את הפרמטר מהסכמה, ציינו אותו במחרוזת התיאור, וקראו אותו באמצעות args.get() ב-handler. הכלי get_precipitation_chance להלן מציג את שני הדפוסים.

#קריאה לכלי מותאם אישית

העבירו את שרת ה-MCP שיצרתם אל query באמצעות האפשרות mcpServers. המפתח ב-mcpServers הופך למקטע {server_name} בשם המלא של כל כלי: mcp__{server_name}__{tool_name}. רשמו את השם הזה ב-allowedTools כדי שהכלי ירוץ ללא בקשת אישור.

קטעי קוד אלה משתמשים מחדש ב-weatherServer מתוך הדוגמה לעיל כדי לשאול את Claude מהו מזג האוויר במיקום מסוים.

#Python

import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage


async def main():
    options = ClaudeAgentOptions(
        mcp_servers={"weather": weather_server},
        allowed_tools=["mcp__weather__get_temperature"],
    )

    async for message in query(
        prompt="What's the temperature in San Francisco?",
        options=options,
    ):
        
# ResultMessage is the final message after all tool calls complete
        if isinstance(message, ResultMessage) and message.subtype == "success":
            print(message.result)


asyncio.run(main())

#TypeScript

import { query } from "@anthropic-ai/claude-agent-sdk";

for await (const message of query({
  prompt: "What's the temperature in San Francisco?",
  options: {
    mcpServers: { weather: weatherServer },
    allowedTools: ["mcp__weather__get_temperature"]
  }
})) {
  // "result" is the final message after all tool calls complete
  if (message.type === "result" && message.subtype === "success") {
    console.log(message.result);
  }
}

שלבו את קטע הקוד הזה עם הגדרות הכלי והשרת מתוך הדוגמה לכלי מזג אוויר בקובץ אחד, ולאחר מכן הריצו אותו עם python weather.py עבור Python או עם npx tsx weather.ts עבור TypeScript. Claude קורא ל-get_temperature והסקריפט מדפיס תשובה בת שורה אחת עם הטמפרטורה הנוכחית בסן פרנסיסקו.

#הוספת כלים נוספים

שרת מחזיק כלים רבים ככל שתפרטו במערך ה-tools שלו. כאשר יש יותר מכלי אחד בשרת, ניתן לרשום כל כלי בנפרד ב-allowedTools או להשתמש בתו הכללי mcp__weather__* כדי לכסות כל כלי שהשרת חושף.

הדוגמה שלהלן מגדירה כלי שני, get_precipitation_chance, ומחליפה את הגדרת weatherServer מתוך הדוגמה לכלי מזג אוויר בהגדרה שמפרטת את שני הכלים במערך.

#Python

# Define a second tool for the same server
@tool(
    "get_precipitation_chance",
    "Get the hourly precipitation probability for a location. "
    "Optionally pass 'hours' (1-24) to control how many hours to return.",
    {"latitude": float, "longitude": float},
)
async def get_precipitation_chance(args: dict[str, Any]) -> dict[str, Any]:
    
# 'hours' isn't in the schema - read it with .get() to make it optional
    hours = args.get("hours", 12)
    async with httpx.AsyncClient() as client:
        response = await client.get(
            "https://api.open-meteo.com/v1/forecast",
            params={
                "latitude": args["latitude"],
                "longitude": args["longitude"],
                "hourly": "precipitation_probability",
                "forecast_days": 1,
            },
        )
        data = response.json()
    chances = data["hourly"]["precipitation_probability"][:hours]

    return {
        "content": [
            {
                "type": "text",
                "text": f"Next {hours} hours: {'%, '.join(map(str, chances))}%",
            }
        ]
    }


# Rebuild the server with both tools in the array
weather_server = create_sdk_mcp_server(
    name="weather",
    version="1.0.0",
    tools=[get_temperature, get_precipitation_chance],
)

#TypeScript

// Define a second tool for the same server
const getPrecipitationChance = tool(
  "get_precipitation_chance",
  "Get the hourly precipitation probability for a location",
  {
    latitude: z.number(),
    longitude: z.number(),
    hours: z
      .number()
      .int()
      .min(1)
      .max(24)
      .default(12) // .default() makes the parameter optional
      .describe("How many hours of forecast to return")
  },
  async (args) => {
    const response = await fetch(
      `https://api.open-meteo.com/v1/forecast?latitude=${args.latitude}&longitude=${args.longitude}&hourly=precipitation_probability&forecast_days=1`
    );
    const data: any = await response.json();
    const chances = data.hourly.precipitation_probability.slice(0, args.hours);

    return {
      content: [{ type: "text", text: `Next ${args.hours} hours: ${chances.join("%, ")}%` }]
    };
  }
);

// Rebuild the server with both tools in the array
const weatherServer = createSdkMcpServer({
  name: "weather",
  version: "1.0.0",
  tools: [getTemperature, getPrecipitationChance]
});

חיפוש כלים מופעל כברירת מחדל ומשהה את טעינת כלי ה-MCP של ה-SDK: Claude רואה את שמו של כל כלי ברשימה קומפקטית וטוען את הסכמה המלאה שלו לפי דרישה. כאשר חיפוש כלים מושבת, כל כלי במערך זה צורך מקום בחלון ההקשר בכל תור שיחה. ב-TypeScript, העבירו alwaysLoad: true בארגומנט extras של tool() או באפשרויות של createSdkMcpServer() כדי לשמור את הסכמה המלאה של הכלי בתוך הפרומפט הראשוני.

#הוספת ביאורים לכלים

ביאורי כלים הם מטא-נתונים אופציונליים המתארים כיצד כלי מתנהג. העבירו אותם כארגומנט החמישי לעוזר tool() ב-TypeScript או באמצעות ארגומנט המפתח annotations בדקורטור @tool ב-Python. כל שדות הרמז (hint) הם ערכים בוליאניים.

שדהברירת מחדלמשמעות
readOnlyHintfalseהכלי אינו משנה את סביבתו. שולט בשאלה האם ניתן לקרוא לכלי במקביל לכלים אחרים שהם לקריאה בלבד.
destructiveHinttrueהכלי עשוי לבצע עדכונים הרסניים. למטרות מידע בלבד.
idempotentHintfalseקריאות חוזרות עם אותם ארגומנטים אינן גורמות להשפעה נוספת. למטרות מידע בלבד.
openWorldHinttrueהכלי מגיע למערכות מחוץ לתהליך שלכם. למטרות מידע בלבד.

ביאורים הם מטא-נתונים, לא אכיפה. כלי המסומן כ-readOnlyHint: true עדיין יכול לכתוב לדיסק אם זה מה שה-handler עושה. הקפידו שהביאור יהיה מדויק ותואם ל-handler.

דוגמה זו מוסיפה את readOnlyHint לכלי get_temperature מתוך הדוגמה לכלי מזג אוויר.

#Python

from claude_agent_sdk import tool, ToolAnnotations


@tool(
    "get_temperature",
    "Get the current temperature at a location",
    {"latitude": float, "longitude": float},
    annotations=ToolAnnotations(
        readOnlyHint=True
    ),  
# Lets Claude batch this with other read-only calls
)
async def get_temperature(args):
    return {"content": [{"type": "text", "text": "..."}]}

#TypeScript

import { tool } from "@anthropic-ai/claude-agent-sdk";
import { z } from "zod";

tool(
  "get_temperature",
  "Get the current temperature at a location",
  { latitude: z.number(), longitude: z.number() },
  async (args) => ({ content: [{ type: "text", text: `...` }] }),
  { annotations: { readOnlyHint: true } } // Lets Claude batch this with other read-only calls
);

ראו את ToolAnnotations בהפניית TypeScript או Python.

#שליטה בגישה לכלים

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

#הגדרת כלים מורשים

האפשרות tools ורשימות הכלים המורשים/הלא מורשים משפיעות על שתי שכבות: זמינות, השולטת בשאלה האם כלי מופיע בהקשר של Claude, והרשאה, השולטת בשאלה האם קריאה מאושרת ברגע ש-Claude מנסה לבצע אותה. האפשרות tools וערכי שם חשוף ב-disallowedTools משנים זמינות. כללי allowedTools וכללי disallowedTools מוגדרי תחום משנים הרשאה. אם אתם מציינים אחד מכלי מעקב המשימות בתוך allowedTools, Claude Code גם מצרף את ההפעלה לשירות.

אפשרותשכבההשפעה
tools: ["Read", "Grep"]זמינותרק הכלים המובנים המפורטים נמצאים בהקשר של Claude. כלים מובנים שאינם מפורטים מוסרים. כלי MCP אינם מושפעים.
tools: []זמינותכל הכלים המובנים מוסרים. Claude יכול להשתמש רק בכלי ה-MCP שלכם.
כלים מורשיםהרשאהכלים מפורטים רצים ללא בקשת אישור. כלים אחרים שאינם מפורטים נותרים זמינים, קריאות עוברות דרך תהליך ההרשאות.
כלים לא מורשיםשתיהןשם כלי חשוף כגון "Bash" מסיר את הכלי מההקשר של Claude, בדיוק כמו השמטתו מ-tools. כלל מוגדר תחום כגון "Bash(rm *)" משאיר את הכלי בהקשר וחוסם רק קריאות תואמות.

כדי להסיר כלי מובנה לחלוטין, השמיטו אותו מ-tools או רשמו את שמו החשוף ב-disallowedTools (ב-Python: disallowed_tools), שני המקרים מרחיקים את הכלי מההקשר כך ש-Claude לעולם לא ינסה להשתמש בו. כלל disallowedTools מוגדר תחום חוסם קריאות תואמות אך משאיר את הכלי גלוי, כך ש-Claude עלול לבזבז תור בניסיון להפעיל אותו. ראו הגדרת הרשאות לסדר הבדיקה המלא.

#טיפול בשגיאות

שגיאה ב-handler אינה עוצרת את לולאת הסוכן. שרת ה-MCP של ה-SDK הרץ בתוך התהליך תופס חריגות שלא נתפסו ומחזיר אותן כתוצאות שגיאה, לכן האופן שבו אתם מדווחים על שגיאה קובע מה Claude יקרא, ולא האם השאילתה תיכשל:

מה קורהתוצאה
ה-handler זורק חריגה שלא נתפסהשרת ה-MCP ממיר אותה לתוצאת שגיאה הנושאת את הודעת החריגה הגולמית. Claude רואה את ההודעה הזו, ולולאת הסוכן ממשיכה.
ה-handler תופס את השגיאה ומחזיר isError: true (ב-TS) / "is_error": True (ב-Python)Claude רואה את ההודעה שאתם מרכיבים. אתם יכולים להוסיף הקשר שחסר בחריגה הגולמית, כמו איזו בקשה נכשלה או מה לנסות במקום.

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

הדוגמה שלהלן תופסת שני סוגי כשלים בתוך ה-handler ומרכיבה את הודעת השגיאה ש-Claude קורא. סטטוס HTTP שאינו 200 נתפס מתוך התגובה ומוחזר כתוצאת שגיאה. שגיאת רשת או JSON לא תקין נתפסים על ידי בלוק ה-try/except (ב-Python) או try/catch (ב-TypeScript) העוטף, ומוחזרים גם הם כתוצאת שגיאה. בשני המקרים Claude מקבל הודעה המתארת את הכשל במקום מחרוזת חריגה חשופה.

#Python

import json
import httpx
from typing import Any
from claude_agent_sdk import tool


@tool(
    "fetch_data",
    "Fetch data from an API",
    {"endpoint": str},  
# Simple schema
)
async def fetch_data(args: dict[str, Any]) -> dict[str, Any]:
    try:
        async with httpx.AsyncClient() as client:
            response = await client.get(args["endpoint"])
            if response.status_code != 200:
                
# Return the failure as a tool result so Claude can react to it.
                
# is_error marks this as a failed call rather than odd-looking data.
                return {
                    "content": [
                        {
                            "type": "text",
                            "text": f"API error: {response.status_code} {response.reason_phrase}",
                        }
                    ],
                    "is_error": True,
                }

            data = response.json()
            return {"content": [{"type": "text", "text": json.dumps(data, indent=2)}]}
    except Exception as e:
        
# Composes the message Claude reads. An uncaught exception would
        
# reach Claude as the raw str(e) with no context.
        return {
            "content": [{"type": "text", "text": f"Failed to fetch data: {str(e)}"}],
            "is_error": True,
        }

#TypeScript

import { tool } from "@anthropic-ai/claude-agent-sdk";
import { z } from "zod";

tool(
  "fetch_data",
  "Fetch data from an API",
  {
    endpoint: z.string().url().describe("API endpoint URL")
  },
  async (args) => {
    try {
      const response = await fetch(args.endpoint);

      if (!response.ok) {
        // Return the failure as a tool result so Claude can react to it.
        // isError marks this as a failed call rather than odd-looking data.
        return {
          content: [
            {
              type: "text",
              text: `API error: ${response.status} ${response.statusText}`
            }
          ],
          isError: true
        };
      }

      const data = await response.json();
      return {
        content: [
          {
            type: "text",
            text: JSON.stringify(data, null, 2)
          }
        ]
      };
    } catch (error) {
      // Composes the message Claude reads. An uncaught throw would
      // reach Claude as the raw error message with no context.
      return {
        content: [
          {
            type: "text",
            text: `Failed to fetch data: ${error instanceof Error ? error.message : String(error)}`
          }
        ],
        isError: true
      };
    }
  }
);

#החזרת תמונות ומשאבים

מערך ה-content בתוצאת כלי מקבל בלוקים מסוג text, image, audio, resource, ו-resource_link. ניתן לשלב ביניהם באותה תגובה. ב-TypeScript, ה-SDK שומר בלוקי אודיו לדיסק ו-Claude מקבל בלוק טקסט עם נתיב הקובץ השמור. ב-Python, ה-SDK משמיט בלוקי אודיו מתוצאת הכלי ורושם אזהרה ביומן.

Claude מקבל כל בלוק קישור למשאב (resource link) כבלוק טקסט המכיל את שם הקישור, ה-URI שלו ותיאורו. ב-TypeScript, היישום שלכם מקבל גם את הקישורים עצמם בתור resourceLinks ב-tool_use_result של הודעת המשתמש. ב-Python, ה-SDK משטח אותם לטקסט לפני שה-CLI רואה את התוצאה, כך שהמפתח resourceLinks ב-Python לעולם אינו נוצר עבור כלים הרצים בתוך התהליך.

#תמונות

בלוק תמונה נושא את בייטי התמונה ישירות, מקודדים כ-base64. אין שדה URL. כדי להחזיר תמונה הנמצאת בכתובת URL, הביאו אותה בתוך ה-handler, קראו את בייטי התגובה, וקודדו אותם ב-base64 לפני ההחזרה. התוצאה מעובדת כקלט חזותי.

שדהטיפוסהערות
type"image"
datastringבייטים מקודדים ב-Base64. ערך Base64 גולמי בלבד, ללא קידומת data:image/...;base64,
mimeTypestringחובה. לדוגמה image/png, image/jpeg, image/webp, image/gif

#Python

import base64
import httpx
from claude_agent_sdk import tool


# Define a tool that fetches an image from a URL and returns it to Claude
@tool("fetch_image", "Fetch an image from a URL and return it to Claude", {"url": str})
async def fetch_image(args):
    async with httpx.AsyncClient() as client:  
# Fetch the image bytes
        response = await client.get(args["url"])

    return {
        "content": [
            {
                "type": "image",
                "data": base64.b64encode(response.content).decode(
                    "ascii"
                ),  
# Base64-encode the raw bytes
                "mimeType": response.headers.get(
                    "content-type", "image/png"
                ),  
# Read MIME type from the response
            }
        ]
    }

#TypeScript

import { tool } from "@anthropic-ai/claude-agent-sdk";
import { z } from "zod";

tool(
  "fetch_image",
  "Fetch an image from a URL and return it to Claude",
  {
    url: z.string().url()
  },
  async (args) => {
    const response = await fetch(args.url); // Fetch the image bytes
    const buffer = Buffer.from(await response.arrayBuffer()); // Read into a Buffer for base64 encoding
    const mimeType = response.headers.get("content-type") ?? "image/png";

    return {
      content: [
        {
          type: "image",
          data: buffer.toString("base64"), // Base64-encode the raw bytes
          mimeType
        }
      ]
    };
  }
);

#משאבים

בלוק משאב מטמיע פיסת תוכן המזוהה על ידי URI. ה-URI הוא תווית שעליה Claude יכול להסתמך בעתיד, התוכן בפועל נמצא בשדה text או blob של הבלוק. השתמשו בכך כאשר הכלי שלכם מייצר משהו שהגיוני לפנות אליו בשם מאוחר יותר, כגון קובץ שנוצר או רשומה ממערכת חיצונית.

שדהטיפוסהערות
type"resource"
resource.uristringמזהה עבור התוכן. כל סכמת URI
resource.textstringהתוכן, אם הוא טקסט. ספקו שדה זה או blob, לא את שניהם
resource.blobstringהתוכן מקודד ב-base64, אם הוא בינארי. ב-TypeScript בלבד: ה-SDK של Python משמיט משאבים בינאריים מתוצאת הכלי ורושם אזהרה ביומן
resource.mimeTypestringאופציונלי

דוגמה זו מציגה בלוק משאב המוחזר מתוך handler של כלי. ה-URI file:///tmp/report.md הוא תווית ש-Claude יכול להתייחס אליה בהמשך, ה-SDK אינו קורא מהנתיב הזה.

#TypeScript

return {
  content: [
    {
      type: "resource",
      resource: {
        uri: "file:///tmp/report.md", // Label for Claude to reference, not a path the SDK reads
        mimeType: "text/markdown",
        text: "
# Report\n..." // The actual content, inline
      }
    }
  ]
};

#Python

return {
    "content": [
        {
            "type": "resource",
            "resource": {
                "uri": "file:///tmp/report.md",  
# Label for Claude to reference, not a path the SDK reads
                "mimeType": "text/markdown",
                "text": "
# Report\n...",  
# The actual content, inline
            },
        }
    ]
}

מבני בלוקים אלה מגיעים מטיפוס CallToolResult של MCP. ראו את מפרט MCP להגדרה המלאה.

#החזרת נתונים מובנים

structuredContent הוא אובייקט JSON אופציונלי בתוצאה, נפרד ממערך ה-content. השתמשו בו כדי להחזיר ערכים גולמיים ש-Claude יכול לקרוא כשדות מדויקים במקום לנתח אותם מתוך מחרוזת טקסט או תמונה.

כאשר structuredContent מוגדר, Claude מקבל את ה-JSON בתוספת כל בלוקי תמונה או משאב מתוך content. בלוקי טקסט ב-content אינם מועברים הלאה, מכיוון שההנחה היא שהם משכפלים את הנתונים המובנים. הדוגמה שלהלן מרנדרת תרשים כבלוק תמונה ומחזירה את נקודות הנתונים שמאחוריו ב-structuredContent מאותו handler. בקטע הקוד, chartPngBuffer הוא Buffer המחזיק את בייטי ה-PNG שרונדרו.

return {
  content: [
    {
      type: "image",
      data: chartPngBuffer.toString("base64"),
      mimeType: "image/png"
    }
  ],
  structuredContent: {
    series: "temperature_2m",
    unit: "fahrenheit",
    points: [62.1, 63.4, 65.0, 64.2]
  }
};

הערה: הדקורטור @tool ב-Python מעביר רק את content ואת is_error מתוך מילון ההחזרה של ה-handler. כדי להחזיר structuredContent מ-Python, הריצו שרת MCP עצמאי במקום שרת SDK הרץ בתוך התהליך.

#דוגמה: ממיר יחידות

כלי זה ממיר ערכים בין יחידות אורך, טמפרטורה ומשקל. משתמש יכול לבקש "המר 100 קילומטרים למיילים" או "מה זה 72°F בצלזיוס", ו-Claude בוחר את סוג היחידה ואת היחידות המתאימות מתוך הבקשה.

הוא מדגים שני דפוסים:

  • סכמות של ערכים מוגדרים (Enum schemas): unit_type מוגבל לקבוצה קבועה של ערכים. ב-TypeScript, השתמשו ב-z.enum(). ב-Python, סכמת המילון אינה תומכת ב-enums, ולכן נדרש מילון JSON Schema מלא.
  • טיפול בקלט שאינו נתמך (Unsupported input handling): כאשר צמד המרה אינו נמצא, ה-handler מחזיר isError: true כדי ש-Claude יוכל לומר למשתמש מה השתבש במקום להתייחס לכשל כאל תוצאה רגילה.

#Python

from typing import Any
from claude_agent_sdk import tool, create_sdk_mcp_server


# z.enum() in TypeScript becomes an "enum" constraint in JSON Schema.
# The dict schema has no equivalent, so full JSON Schema is required.
@tool(
    "convert_units",
    "Convert a value from one unit to another",
    {
        "type": "object",
        "properties": {
            "unit_type": {
                "type": "string",
                "enum": ["length", "temperature", "weight"],
                "description": "Category of unit",
            },
            "from_unit": {
                "type": "string",
                "description": "Unit to convert from, e.g. kilometers, fahrenheit, pounds",
            },
            "to_unit": {"type": "string", "description": "Unit to convert to"},
            "value": {"type": "number", "description": "Value to convert"},
        },
        "required": ["unit_type", "from_unit", "to_unit", "value"],
    },
)
async def convert_units(args: dict[str, Any]) -> dict[str, Any]:
    conversions = {
        "length": {
            "kilometers_to_miles": lambda v: v * 0.621371,
            "miles_to_kilometers": lambda v: v * 1.60934,
            "meters_to_feet": lambda v: v * 3.28084,
            "feet_to_meters": lambda v: v * 0.3048,
        },
        "temperature": {
            "celsius_to_fahrenheit": lambda v: (v * 9) / 5 + 32,
            "fahrenheit_to_celsius": lambda v: (v - 32) * 5 / 9,
            "celsius_to_kelvin": lambda v: v + 273.15,
            "kelvin_to_celsius": lambda v: v - 273.15,
        },
        "weight": {
            "kilograms_to_pounds": lambda v: v * 2.20462,
            "pounds_to_kilograms": lambda v: v * 0.453592,
            "grams_to_ounces": lambda v: v * 0.035274,
            "ounces_to_grams": lambda v: v * 28.3495,
        },
    }

    key = f"{args['from_unit']}_to_{args['to_unit']}"
    fn = conversions.get(args["unit_type"], {}).get(key)

    if not fn:
        return {
            "content": [
                {
                    "type": "text",
                    "text": f"Unsupported conversion: {args['from_unit']} to {args['to_unit']}",
                }
            ],
            "is_error": True,
        }

    result = fn(args["value"])
    return {
        "content": [
            {
                "type": "text",
                "text": f"{args['value']} {args['from_unit']} = {result:.4f} {args['to_unit']}",
            }
        ]
    }


converter_server = create_sdk_mcp_server(
    name="converter",
    version="1.0.0",
    tools=[convert_units],
)

#TypeScript

import { tool, createSdkMcpServer } from "@anthropic-ai/claude-agent-sdk";
import { z } from "zod";

const convert = tool(
  "convert_units",
  "Convert a value from one unit to another",
  {
    unit_type: z.enum(["length", "temperature", "weight"]).describe("Category of unit"),
    from_unit: z
      .string()
      .describe("Unit to convert from, e.g. kilometers, fahrenheit, pounds"),
    to_unit: z.string().describe("Unit to convert to"),
    value: z.number().describe("Value to convert")
  },
  async (args) => {
    type Conversions = Record<string, Record<string, (v: number) => number>>;

    const conversions: Conversions = {
      length: {
        kilometers_to_miles: (v) => v * 0.621371,
        miles_to_kilometers: (v) => v * 1.60934,
        meters_to_feet: (v) => v * 3.28084,
        feet_to_meters: (v) => v * 0.3048
      },
      temperature: {
        celsius_to_fahrenheit: (v) => (v * 9) / 5 + 32,
        fahrenheit_to_celsius: (v) => ((v - 32) * 5) / 9,
        celsius_to_kelvin: (v) => v + 273.15,
        kelvin_to_celsius: (v) => v - 273.15
      },
      weight: {
        kilograms_to_pounds: (v) => v * 2.20462,
        pounds_to_kilograms: (v) => v * 0.453592,
        grams_to_ounces: (v) => v * 0.035274,
        ounces_to_grams: (v) => v * 28.3495
      }
    };

    const key = `${args.from_unit}_to_${args.to_unit}`;
    const fn = conversions[args.unit_type]?.[key];

    if (!fn) {
      return {
        content: [
          {
            type: "text",
            text: `Unsupported conversion: ${args.from_unit} to ${args.to_unit}`
          }
        ],
        isError: true
      };
    }

    const result = fn(args.value);
    return {
      content: [
        {
          type: "text",
          text: `${args.value} ${args.from_unit} = ${result.toFixed(4)} ${args.to_unit}`
        }
      ]
    };
  }
);

const converterServer = createSdkMcpServer({
  name: "converter",
  version: "1.0.0",
  tools: [convert]
});

לאחר שהשרת מוגדר, העבירו אותו ל-query באותו אופן כמו בדוגמת מזג האוויר. דוגמה זו שולחת שלושה פרומפטים שונים בלולאה כדי להראות את אותו הכלי מטפל בסוגי יחידות שונים. עבור כל תגובה, היא בודקת אובייקטי AssistantMessage (המכילים את קריאות הכלים ש-Claude ביצע במהלך אותו תור) ומדפיסה כל ToolUseBlock לפני הדפסת טקסט ה-ResultMessage הסופי. הדבר מאפשר לכם לראות מתי Claude משתמש בכלי לעומת מענה מתוך הידע שלו עצמו.

מכיוון ש-חיפוש כלים מופעל כברירת מחדל, הפלט עשוי לכלול גם קריאת ToolSearch כאשר Claude טוען את סכמת הכלי המושהית.

#Python

import asyncio
from claude_agent_sdk import (
    query,
    ClaudeAgentOptions,
    ResultMessage,
    AssistantMessage,
    ToolUseBlock,
)


async def main():
    options = ClaudeAgentOptions(
        mcp_servers={"converter": converter_server},
        allowed_tools=["mcp__converter__convert_units"],
    )

    prompts = [
        "Convert 100 kilometers to miles.",
        "What is 72°F in Celsius?",
        "How many pounds is 5 kilograms?",
    ]

    for prompt in prompts:
        try:
            async for message in query(prompt=prompt, options=options):
                if isinstance(message, AssistantMessage):
                    for block in message.content:
                        if isinstance(block, ToolUseBlock):
                            print(f"[tool call] {block.name}({block.input})")
                elif isinstance(message, ResultMessage) and message.subtype == "success":
                    print(f"Q: {prompt}\nA: {message.result}\n")
        except Exception as error:
            
# A single-shot query() raises after yielding an error result. Only success
            
# results are printed above, so handle the failure here and continue with
            
# the next prompt.
            print(f"Call failed: {error}")


asyncio.run(main())

#TypeScript

import { query } from "@anthropic-ai/claude-agent-sdk";

const prompts = [
  "Convert 100 kilometers to miles.",
  "What is 72°F in Celsius?",
  "How many pounds is 5 kilograms?"
];

for (const prompt of prompts) {
  try {
    for await (const message of query({
      prompt,
      options: {
        mcpServers: { converter: converterServer },
        allowedTools: ["mcp__converter__convert_units"]
      }
    })) {
      if (message.type === "assistant") {
        for (const block of message.message.content) {
          if (block.type === "tool_use") {
            console.log(`[tool call] ${block.name}`, block.input);
          }
        }
      } else if (message.type === "result" && message.subtype === "success") {
        console.log(`Q: ${prompt}\nA: ${message.result}\n`);
      }
    }
  } catch (error) {
    // A single-shot query() throws after yielding an error result. Only success
    // results are logged above, so handle the failure here and continue with
    // the next prompt.
    console.error(`Call failed: ${error}`);
  }
}

#הצעדים הבאים

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

מכאן:

  • אם השרת שלכם גדל לעשרות כלים, ראו חיפוש כלים כדי להשהות את טעינתם עד ש-Claude זקוק להם.
  • כדי להתחבר לשרתי MCP חיצוניים (מערכת קבצים, GitHub, Slack) במקום לבנות שרתים משלכם, ראו חיבור שרתי MCP.
  • כדי לשלוט באילו כלים ירוצו אוטומטית לעומת כאלה הדורשים אישור, ראו הגדרת הרשאות.