תיעוד 178
תנו לקלוד כלים מותאמים אישית
הגדירו כלים מותאמים אישית באמצעות שרת ה-MCP שרץ בתוך התהליך של Claude Agent SDK, כדי שקלוד יוכל לקרוא לפונקציות שלכם, לפנות ל-API שלכם ולבצע פעולות ייעודיות לתחום.
כלים מותאמים אישית מרחיבים את Agent SDK בכך שהם מאפשרים להגדיר פונקציות משלכם שקלוד יכול לקרוא להן במהלך שיחה. באמצעות שרת ה-MCP שרץ בתוך התהליך של ה-SDK אפשר לתת לקלוד גישה למסדי נתונים, ל-API חיצוניים, ללוגיקה ייעודית לתחום, או לכל יכולת אחרת שהיישום שלכם צריך.
#הפניה מהירה
| מה רוצים לעשות | כך עושים |
|---|---|
| להגדיר כלי | השתמשו ב-@tool (Python) או ב-tool() (TypeScript) עם שם, תיאור, סכמה ו-handler. ראו יצירת כלי מותאם אישית. |
| להפוך פרמטר לאופציונלי | הכריזו עליו כאופציונלי בסכמה והחילו את ברירת המחדל ב-handler. ראו הפיכת פרמטר לאופציונלי. |
| לרשום כלי אצל קלוד | עטפו ב-create_sdk_mcp_server / createSdkMcpServer והעבירו ל-mcpServers ב-query(). ראו קריאה לכלי מותאם אישית. |
| לאשר כלי מראש | הוסיפו אותו לכלים המותרים שלכם. ראו הגדרת כלים מותרים. |
| לדרוש אישור לקריאות לכלי, או לחסום את הכלי | ב-SDK של TypeScript, הוסיפו permission_policy לכלי באובייקט ההגדרות ש-createSdkMcpServer() מחזיר. ראו דרישת אישור לכלים מסוימים. |
| להסיר כלי מובנה מההקשר של קלוד | העבירו מערך tools שמציין רק את הכלים המובנים שאתם רוצים. ראו הגדרת כלים מותרים. |
| לאפשר לקלוד לקרוא לכלים במקביל | הגדירו readOnlyHint: true על כלים בלי תופעות לוואי. ראו הוספת הערות לכלי. |
| לשלוט בהודעת השגיאה שקלוד קורא | החזירו isError: true כדי לחבר את ההודעה במקום לחשוף את החריגה הגולמית. ראו טיפול בשגיאות. |
| להחזיר תמונות או קבצים | השתמשו בבלוקי image או resource במערך content. ראו החזרת תמונות ומשאבים. |
| להחזיר תוצאת JSON קריאה למכונה | הגדירו structuredContent על התוצאה. ראו החזרת נתונים מובנים. |
| להתרחב להרבה כלים | השתמשו ב-חיפוש כלים כדי לטעון כלים לפי דרישה. |
#יצירת כלי מותאם אישית
כלי מוגדר על ידי ארבעה חלקים, שמועברים כארגומנטים לעוזר tool() ב-TypeScript או לדקורטור @tool ב-Python:
- שם: מזהה ייחודי שקלוד משתמש בו כדי לקרוא לכלי.
- תיאור: מה הכלי עושה. קלוד קורא את זה כדי להחליט מתי לקרוא לו.
- סכמת קלט: הארגומנטים שהכלי מקבל, מוצהרים לפי שפה:
- TypeScript: סכמת Zod. ה-
argsשל ה-handler מקבלים את הטיפוסים שלהם ממנה. קראו ל-.describe()על שדה כדי לתת לו תיאור שקלוד רואה. - Python: מילון שממפה שמות לטיפוסים, כמו
{"latitude": float}, שה-SDK ממיר עבורכם ל-JSON Schema. עטפו טיפוס ב-Annotated, כמו{"latitude": Annotated[float, "Latitude coordinate"]}, כדי לתת לשדה תיאור שקלוד רואה. הדקורטור מקבל גם מילון JSON Schema מלא ישירות כשצריך enums, טווחים, שדות אופציונליים או אובייקטים מקוננים.
- TypeScript: סכמת Zod. ה-
- Handler: הפונקציה האסינכרונית שרצה כשקלוד קורא לכלי. היא מקבלת את הארגומנטים שעברו אימות וחייבת להחזיר אובייקט עם:
content(חובה): מערך של בלוקי תוצאה, כל אחד עםtypeשל"text","image","audio","resource"או"resource_link". ראו החזרת תמונות ומשאבים לבלוקים שאינם טקסט.structuredContent(אופציונלי): אובייקט JSON שמחזיק את התוצאה כנתונים קריאים למכונה, ומוחזר לצדcontent. ראו החזרת נתונים מובנים.isError(אופציונלי): הגדירו ל-trueכדי לסמן כשל של הכלי כך שקלוד יוכל להגיב אליו. ראו טיפול בשגיאות.
אחרי הגדרת כלי, עטפו אותו בשרת עם createSdkMcpServer (TypeScript) או create_sdk_mcp_server (Python). השרת רץ בתוך התהליך בתוך היישום שלכם, לא כתהליך נפרד.
דוגמאות Python בדף הזה שמבצעות בקשות HTTP משתמשות ב-httpx. הוסיפו אותו עם מנהל החבילות שהפרויקט שלכם משתמש בו:
Python (uv)
uv add httpxPython (pip)
pip install httpx#דוגמה לכלי מזג אוויר
הדוגמה הזו מגדירה כלי get_temperature ועוטפת אותו בשרת MCP, בלי להעביר את השרת ל-query. כדי להריץ את הכלי, ראו קריאה לכלי מותאם אישית בהמשך.
from typing import Annotated, 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": Annotated[float, "Latitude coordinate"],
"longitude": Annotated[float, "Longitude coordinate"],
},
)
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],
)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}¤t=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]
});ראו את העזר tool() ב-TypeScript או את @tool ב-Python לפרטי הפרמטרים המלאים, כולל פורמטי קלט של JSON Schema ומבנה ערך ההחזרה.
#הפיכת פרמטר לאופציונלי
כדי להפוך פרמטר לאופציונלי, הכריזו עליו כאופציונלי בסכמה והחילו את ברירת המחדל ב-handler:
- TypeScript: הוסיפו
.optional()לשדה Zod. - Python: סכמת המילון דורשת כל מפתח. השתמשו בצורת JSON Schema, השאירו את הפרמטר מחוץ ל-
required, וקראו אותו עםargs.get(). לסכמה עם טיפוסים ומפתחות אופציונליים, ראו TypedDict class.
הכלי get_precipitation_chance למטה מציג את שני הדפוסים.
#קריאה לכלי מותאם אישית
העבירו את שרת ה-MCP שיצרתם ל-query דרך האפשרות mcpServers, וציינו את השם המלא של כל כלי ב-allowedTools כדי שהכלי ירוץ בלי בקשת הרשאה. השם הוא mcp__{server_name}__{tool_name}, כאשר {server_name} הוא המפתח שאתם נותנים לשרת ב-mcpServers. אם המפתח מכיל תווים כמו . או רווחים, החליפו אותם ב-_ כשאתם כותבים את {server_name}.
קטעי הקוד האלה משתמשים מחדש ב-weatherServer מדוגמת כלי מזג האוויר כדי לשאול את קלוד מה מזג האוויר במיקום מסוים.
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())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);
}
}שלבו את הקטע הזה עם הגדרות הכלי והשרת מדוגמת כלי מזג האוויר בקובץ אחד, weather.py או weather.ts, ואז הריצו אותו מהטרמינל:
TypeScript
npx tsx weather.tsPython (uv)
uv run weather.pyPython (pip)
הפעילו את הסביבה הווירטואלית שבה התקנתם את ה-SDK, ואז הריצו:
python weather.pyקלוד קורא ל-get_temperature והסקריפט מדפיס תשובה בשורה אחת עם הטמפרטורה הנוכחית בסן פרנסיסקו.
#הוספת כלים נוספים
שרת מחזיק כמה כלים שתציינו במערך tools שלו. כשיש יותר מכלי אחד בשרת, אפשר לציין כל אחד ב-allowedTools בנפרד או להשתמש בתו הכללי mcp__weather__* כדי לכסות כל כלי שהשרת חושף.
הדוגמה למטה מגדירה כלי שני, get_precipitation_chance, ומחליפה את הגדרת weatherServer מדוגמת כלי מזג האוויר בהגדרה שמציינת את שני הכלים במערך.
# Define a second tool for the same server
@tool(
"get_precipitation_chance",
"Get the hourly precipitation probability for a location",
{
"type": "object",
"properties": {
"latitude": {"type": "number"},
"longitude": {"type": "number"},
"hours": {
"type": "integer",
"minimum": 1,
"maximum": 24,
"description": "How many hours of forecast to return",
},
},
# 'hours' is left out of required, so Claude can omit it
"required": ["latitude", "longitude"],
},
)
async def get_precipitation_chance(args: dict[str, Any]) -> dict[str, Any]:
# 'hours' isn't in required - read it with .get() to fall back to a default
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],
)// 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)
.optional() // .optional() lets Claude omit the parameter
.describe("How many hours of forecast to return")
},
async (args) => {
const hours = args.hours ?? 12; // Apply the default in the handler
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, hours);
return {
content: [{ type: "text", text: `Next ${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]
});חיפוש כלים פעיל כברירת מחדל ומשהה כלים של SDK MCP: קלוד רואה את השם של כל כלי ברשימה דחוסה וטוען את הסכמה המלאה שלו לפי דרישה. כשחיפוש כלים מושבת, כל כלי במערך הזה צורך מקום בחלון ההקשר בכל תור. ב-TypeScript, העבירו alwaysLoad: true בארגומנט extras של tool() או באפשרויות של createSdkMcpServer() כדי לשמור את הסכמה המלאה של כלי בפרומפט הראשוני.
#הוספת הערות לכלי
הערות כלי הן מטא-נתונים אופציונליים שמתארים איך כלי מתנהג. העבירו אותן כארגומנט החמישי לעוזר tool() ב-TypeScript או דרך ארגומנט מילת המפתח annotations לדקורטור @tool ב-Python. כל שדות הרמז הם בוליאניים.
| שדה | ברירת מחדל | משמעות |
|---|---|---|
readOnlyHint | false | הכלי לא משנה את הסביבה שלו. קובע אם אפשר לקרוא לכלי במקביל לכלים אחרים לקריאה בלבד. |
destructiveHint | true | הכלי עשוי לבצע עדכונים הרסניים. הגדירו אותו ל-true במפורש בכל כלי שאתם מסמנים always_ask. |
idempotentHint | false | לקריאות חוזרות עם אותם ארגומנטים אין השפעה נוספת. למידע בלבד. |
openWorldHint | true | הכלי מגיע למערכות מחוץ לתהליך שלכם. למידע בלבד. |
הערות הן מטא-נתונים, לא אכיפה. כלי שמסומן readOnlyHint: true עדיין יכול לכתוב לדיסק אם זה מה שה-handler עושה. שמרו על התאמה בין ההערה ל-handler.
הדוגמה הזו מוסיפה readOnlyHint לכלי get_temperature מדוגמת כלי מזג האוויר.
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": "..."}]}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 עלול לבזבז תור בניסיון להשתמש בו. ראו הגדרת הרשאות לסדר ההערכה המלא.
#דרישת אישור לכלים מסוימים
הגדירו לכלי את permission_policy ל-always_ask, ואז allowedTools מפסיק לאשר את הקריאות של הכלי הזה, כך שהן יכולות להגיע ל-callback של canUseTool גם כשהכלי ברשימה. הגדירו במקום זאת always_deny, ואז קריאה לכלי נדחית, אבל mod בתוסף שנטען עדיין יכול לאשר אותה. השדה קיים רק ב-TypeScript ודורש Agent SDK מגרסה v0.3.301 ואילך.
שימו את רשומת המדיניות במערך tools על אובייקט ההגדרה ש-createSdkMcpServer() מחזיר, לא באפשרות tools שמועברת לפונקציה הזו. הדוגמה הזו פורסת את weatherServer מדוגמת כלי מזג האוויר ומוסיפה מערך tools שנותן ל-get_temperature את מדיניות always_ask:
options: {
mcpServers: {
weather: {
...weatherServer,
tools: [{ name: "get_temperature", permission_policy: "always_ask" as const }]
}
},
allowedTools: ["mcp__weather__get_temperature"],
permissionMode: "default",
canUseTool: async (toolName, input) => {
console.log(`Approval requested for ${toolName}`);
return { behavior: "allow", updatedInput: input };
}
}כדי לנסות את המדיניות, החליפו את options בסקריפט קריאה לכלי מותאם ב-options של הדוגמה הזו, ואז הריצו את הסקריפט מהטרמינל. הסקריפט מדפיס Approval requested for mcp__weather__get_temperature, ואחריו את התשובה של Claude על הטמפרטורה. לסקריפט מלא, ראו את הפניה ל-TypeScript.
אם משתמשים במצב הרשאה auto או plan, הגדירו destructiveHint: true בהערות של הכלי כדי שקריאות לכלי עדיין יגיעו ל-callback שלכם. אחרת מסווג מצב auto יכול לאשר חלק מקריאות always_ask, לפי התנאים שרשומים בהפניה ל-TypeScript, וקריאה שהוא מאשר לא מגיעה ל-callback שלכם. הגדירו את הרמז במפורש, כי destructiveHint שלא הוגדר נספר כ-false לקריאות always_ask ולא מקבל ברירת מחדל true.
#טיפול בשגיאות
שגיאה ב-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 מקבל הודעה שמתארת את הכשל במקום מחרוזת חריגה חשופה.
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,
}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 מקבל כל בלוק קישור למשאב כבלוק טקסט שמכיל את שם הקישור, ה-URI והתיאור. ב-TypeScript, האפליקציה שלכם גם מקבלת את הקישורים עצמם כ-resourceLinks על tool_use_result של הודעת המשתמש. ב-Python, ה-SDK משטח אותם לטקסט לפני שה-CLI רואה את התוצאה, כך שמפתח resourceLinks ב-Python לא נוצר אף פעם לכלים בתוך התהליך.
#תמונות
בלוק תמונה נושא את בייטי התמונה בתוך השורה, מקודדים כ-base64. אין שדה URL. כדי להחזיר תמונה שנמצאת בכתובת URL, שלוף אותה ב-handler, קרא את בייטי התשובה, וקודד אותם ב-base64 לפני ההחזרה. תמונת PNG, JPEG, GIF או WebP מגיעה ל-Claude כקלט חזותי. תמונה מכל סוג אחר נשמרת לדיסק ו-Claude מקבל את נתיב הקובץ שלה כטקסט.
| שדה | סוג | הערות |
|---|---|---|
type | "image" | |
data | string | בייטים מקודדים ב-Base64. רק base64 גולמי, בלי קידומת data:image/...;base64, |
mimeType | string | חובה. לדוגמה image/png, image/jpeg, image/webp, image/gif |
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
}
]
}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. התוכן עצמו נמצא בשדה text או blob של הבלוק. השתמשו בזה כשהכלי מייצר קובץ שנוצר או רשומה ממערכת חיצונית.
| שדה | סוג | הערות |
|---|---|---|
type | "resource" | |
resource.uri | string | מזהה לתוכן. כל סכמת URI |
resource.text | string | התוכן, אם הוא טקסט. ספקו את זה או את blob, לא את שניהם |
resource.blob | string | התוכן מקודד ב-base64, אם הוא בינארי. רק TypeScript: ה-SDK של Python משמיט משאבים בינאריים מתוצאת הכלי ורושם אזהרה |
resource.mimeType | string | אופציונלי |
הדוגמה הזו מציגה בלוק משאב שמוחזר מתוך handler של כלי. ה-SDK לא קורא מה-URI של הדוגמה, file:///tmp/report.md.
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
}
}
]
};return {
"content": [
{
"type": "resource",
"resource": {
"uri": "file:///tmp/report.md", # Not a path the SDK reads
"mimeType": "text/markdown",
"text": "# Report\n...", # The actual content, inline
},
}
]
}צורות הבלוקים האלה מגיעות מסוג MCP CallToolResult. ראו את מפרט 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]
}
};[!NOTE]
הדקורטור
@toolב-Python מעביר מהמילון שה-handler מחזיר רק אתcontentואתis_error. כדי להחזירstructuredContentמ-Python, הריצו שרת MCP עצמאי במקום שרת SDK בתוך התהליך.
#דוגמה: ממיר יחידות
הכלי הזה ממיר ערכים בין יחידות של אורך, טמפרטורה ומשקל. משתמש יכול לבקש "convert 100 kilometers to miles" או "what is 72°F in Celsius", ו-Claude בוחר את סוג היחידה ואת היחידות הנכונים מתוך הבקשה.
הוא מדגים שני דפוסים:
- סכמות enum:
unit_typeמוגבל לקבוצה קבועה של ערכים. ב-TypeScript, השתמשו ב-z.enum(). ב-Python, סכמת ה-dict לא תומכת ב-enum, ולכן נדרש dict מלא של JSON Schema. - טיפול בקלט שאינו נתמך: כשזוג המרה לא נמצא, המטפל מחזיר
isError: trueכדי ש-Claude יוכל לומר למשתמש מה השתבש, במקום להתייחס לכישלון כתוצאה רגילה.
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],
)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 טוען את סכמת הכלי הדחויה.
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())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 חיצוניים (filesystem, GitHub, Slack) במקום לבנות שרת משלכם, ראו חיבור שרתי MCP.
- כדי לשלוט באילו כלים רצים אוטומטית ואילו דורשים אישור, ראו הגדרת הרשאות.