agent-sdk/custom-tools.md +833 −0 created
1> ## Documentation Index
2> Fetch the complete documentation index at: https://code.claude.com/docs/llms.txt
3> Use this file to discover all available pages before exploring further.
4
5# Geben Sie Claude benutzerdefinierte Tools
6
7> Definieren Sie benutzerdefinierte Tools mit dem In-Process-MCP-Server des Claude Agent SDK, damit Claude Ihre Funktionen aufrufen, Ihre APIs treffen und domänenspezifische Operationen ausführen kann.
8
9Benutzerdefinierte Tools erweitern das Agent SDK, indem Sie Ihre eigenen Funktionen definieren können, die Claude während einer Konversation aufrufen kann. Mit dem In-Process-MCP-Server des SDK können Sie Claude Zugriff auf Datenbanken, externe APIs, domänenspezifische Logik oder jede andere Funktionalität geben, die Ihre Anwendung benötigt.
10
11Dieser Leitfaden behandelt, wie Sie Tools mit Eingabeschemas und Handlern definieren, sie in einen MCP-Server bündeln, sie an `query` übergeben und kontrollieren, auf welche Tools Claude zugreifen kann. Er behandelt auch Fehlerbehandlung, Tool-Annotationen und die Rückgabe von Nicht-Text-Inhalten wie Bildern.
12
13## Schnellreferenz
14
15| Wenn Sie möchten... | Tun Sie dies |
16| :------------------------------------------------------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
17| Ein Tool definieren | Verwenden Sie [`@tool`](/de/agent-sdk/python#tool) (Python) oder [`tool()`](/de/agent-sdk/typescript#tool) (TypeScript) mit einem Namen, einer Beschreibung, einem Schema und einem Handler. Siehe [Erstellen Sie ein benutzerdefiniertes Tool](#create-a-custom-tool). |
18| Ein Tool bei Claude registrieren | Wickeln Sie in `create_sdk_mcp_server` / `createSdkMcpServer` ein und übergeben Sie es an `mcpServers` in `query()`. Siehe [Rufen Sie ein benutzerdefiniertes Tool auf](#call-a-custom-tool). |
19| Ein Tool vorab genehmigen | Fügen Sie es zu Ihren zulässigen Tools hinzu. Siehe [Konfigurieren Sie zulässige Tools](#configure-allowed-tools). |
20| Entfernen Sie ein integriertes Tool aus Claudes Kontext | Übergeben Sie ein `tools`-Array, das nur die gewünschten integrierten Tools auflistet. Siehe [Konfigurieren Sie zulässige Tools](#configure-allowed-tools). |
21| Lassen Sie Claude Tools parallel aufrufen | Setzen Sie `readOnlyHint: true` auf Tools ohne Nebenwirkungen. Siehe [Fügen Sie Tool-Annotationen hinzu](#add-tool-annotations). |
22| Fehler behandeln, ohne die Schleife zu stoppen | Geben Sie `isError: true` zurück, anstatt zu werfen. Siehe [Fehler behandeln](#handle-errors). |
23| Geben Sie Bilder oder Dateien zurück | Verwenden Sie `image`- oder `resource`-Blöcke im Content-Array. Siehe [Geben Sie Bilder und Ressourcen zurück](#return-images-and-resources). |
24| Geben Sie ein maschinenlesbares JSON-Ergebnis zurück | Setzen Sie `structuredContent` auf das Ergebnis. Siehe [Geben Sie strukturierte Daten zurück](#return-structured-data). |
25| Skalieren Sie auf viele Tools | Verwenden Sie [Tool-Suche](/de/agent-sdk/tool-search), um Tools bei Bedarf zu laden. |
26
27## Erstellen Sie ein benutzerdefiniertes Tool
28
29Ein Tool wird durch vier Teile definiert, die als Argumente an den [`tool()`](/de/agent-sdk/typescript#tool)-Helper in TypeScript oder den [`@tool`](/de/agent-sdk/python#tool)-Dekorator in Python übergeben werden:
30
31* **Name:** ein eindeutiger Bezeichner, den Claude verwendet, um das Tool aufzurufen.
32* **Beschreibung:** was das Tool tut. Claude liest dies, um zu entscheiden, wann es aufgerufen werden soll.
33* **Eingabeschema:** die Argumente, die Claude bereitstellen muss. In TypeScript ist dies immer ein [Zod-Schema](https://zod.dev/), und die `args` des Handlers werden automatisch davon typisiert. In Python ist dies ein Dict, das Namen auf Typen abbildet, wie `{"latitude": float}`, das das SDK für Sie in JSON Schema konvertiert. Der Python-Dekorator akzeptiert auch direkt ein vollständiges [JSON Schema](https://json-schema.org/understanding-json-schema/about)-Dict, wenn Sie Enums, Bereiche, optionale Felder oder verschachtelte Objekte benötigen.
34* **Handler:** die asynchrone Funktion, die ausgeführt wird, wenn Claude das Tool aufruft. Sie empfängt die validierten Argumente und muss ein Objekt mit folgenden Eigenschaften zurückgeben:
35 * `content` (erforderlich): ein Array von Ergebnisblöcken, jeder mit einem `type` von `"text"`, `"image"` oder `"resource"`. Siehe [Geben Sie Bilder und Ressourcen zurück](#return-images-and-resources) für Nicht-Text-Blöcke.
36 * `structuredContent` (optional): ein JSON-Objekt, das das Ergebnis als maschinenlesbare Daten enthält, das zusammen mit `content` zurückgegeben wird. Siehe [Geben Sie strukturierte Daten zurück](#return-structured-data).
37 * `isError` (optional): setzen Sie auf `true`, um einen Tool-Fehler zu signalisieren, damit Claude darauf reagieren kann. Siehe [Fehler behandeln](#handle-errors).
38
39Nach dem Definieren eines Tools wickeln Sie es mit [`createSdkMcpServer`](/de/agent-sdk/typescript#createsdkmcpserver) (TypeScript) oder [`create_sdk_mcp_server`](/de/agent-sdk/python#create_sdk_mcp_server) (Python) in einen Server ein. Der Server läuft im Prozess in Ihrer Anwendung, nicht als separater Prozess.
40
41### Beispiel für ein Wetter-Tool
42
43Dieses Beispiel definiert ein `get_temperature`-Tool und wickelt es in einen MCP-Server ein. Es richtet nur das Tool ein; um es an `query` zu übergeben und auszuführen, siehe [Rufen Sie ein benutzerdefiniertes Tool auf](#call-a-custom-tool) unten.
44
45<CodeGroup>
46 ```python Python theme={null}
47 from typing import Any
48 import httpx
49 from claude_agent_sdk import tool, create_sdk_mcp_server
50
51
52 # Define a tool: name, description, input schema, handler
53 @tool(
54 "get_temperature",
55 "Get the current temperature at a location",
56 {"latitude": float, "longitude": float},
57 )
58 async def get_temperature(args: dict[str, Any]) -> dict[str, Any]:
59 async with httpx.AsyncClient() as client:
60 response = await client.get(
61 "https://api.open-meteo.com/v1/forecast",
62 params={
63 "latitude": args["latitude"],
64 "longitude": args["longitude"],
65 "current": "temperature_2m",
66 "temperature_unit": "fahrenheit",
67 },
68 )
69 data = response.json()
70
71 # Return a content array - Claude sees this as the tool result
72 return {
73 "content": [
74 {
75 "type": "text",
76 "text": f"Temperature: {data['current']['temperature_2m']}°F",
77 }
78 ]
79 }
80
81
82 # Wrap the tool in an in-process MCP server
83 weather_server = create_sdk_mcp_server(
84 name="weather",
85 version="1.0.0",
86 tools=[get_temperature],
87 )
88 ```
89
90 ```typescript TypeScript theme={null}
91 import { tool, createSdkMcpServer } from "@anthropic-ai/claude-agent-sdk";
92 import { z } from "zod";
93
94 // Define a tool: name, description, input schema, handler
95 const getTemperature = tool(
96 "get_temperature",
97 "Get the current temperature at a location",
98 {
99 latitude: z.number().describe("Latitude coordinate"), // .describe() adds a field description Claude sees
100 longitude: z.number().describe("Longitude coordinate")
101 },
102 async (args) => {
103 // args is typed from the schema: { latitude: number; longitude: number }
104 const response = await fetch(
105 `https://api.open-meteo.com/v1/forecast?latitude=${args.latitude}&longitude=${args.longitude}¤t=temperature_2m&temperature_unit=fahrenheit`
106 );
107 const data: any = await response.json();
108
109 // Return a content array - Claude sees this as the tool result
110 return {
111 content: [{ type: "text", text: `Temperature: ${data.current.temperature_2m}°F` }]
112 };
113 }
114 );
115
116 // Wrap the tool in an in-process MCP server
117 const weatherServer = createSdkMcpServer({
118 name: "weather",
119 version: "1.0.0",
120 tools: [getTemperature]
121 });
122 ```
123</CodeGroup>
124
125Siehe die [`tool()`](/de/agent-sdk/typescript#tool)-TypeScript-Referenz oder die [`@tool`](/de/agent-sdk/python#tool)-Python-Referenz für vollständige Parameterdetails, einschließlich JSON-Schema-Eingabeformate und Rückgabewertstruktur.
126
127<Tip>
128 Um einen Parameter optional zu machen: Fügen Sie in TypeScript `.default()` zum Zod-Feld hinzu. In Python behandelt das Dict-Schema jeden Schlüssel als erforderlich, also lassen Sie den Parameter aus dem Schema weg, erwähnen Sie ihn in der Beschreibungszeichenkette und lesen Sie ihn mit `args.get()` im Handler. Das [`get_precipitation_chance`-Tool unten](#add-more-tools) zeigt beide Muster.
129</Tip>
130
131### Rufen Sie ein benutzerdefiniertes Tool auf
132
133Übergeben Sie den MCP-Server, den Sie erstellt haben, an `query` über die `mcpServers`-Option. Der Schlüssel in `mcpServers` wird zum `{server_name}`-Segment im vollständig qualifizierten Namen jedes Tools: `mcp__{server_name}__{tool_name}`. Listen Sie diesen Namen in `allowedTools` auf, damit das Tool ohne Genehmigungsaufforderung ausgeführt wird.
134
135Diese Snippets verwenden den `weatherServer` aus dem [Beispiel oben](#weather-tool-example) wieder, um Claude zu fragen, wie das Wetter an einem bestimmten Ort ist.
136
137<CodeGroup>
138 ```python Python theme={null}
139 import asyncio
140 from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage
141
142
143 async def main():
144 options = ClaudeAgentOptions(
145 mcp_servers={"weather": weather_server},
146 allowed_tools=["mcp__weather__get_temperature"],
147 )
148
149 async for message in query(
150 prompt="What's the temperature in San Francisco?",
151 options=options,
152 ):
153 # ResultMessage is the final message after all tool calls complete
154 if isinstance(message, ResultMessage) and message.subtype == "success":
155 print(message.result)
156
157
158 asyncio.run(main())
159 ```
160
161 ```typescript TypeScript theme={null}
162 import { query } from "@anthropic-ai/claude-agent-sdk";
163
164 for await (const message of query({
165 prompt: "What's the temperature in San Francisco?",
166 options: {
167 mcpServers: { weather: weatherServer },
168 allowedTools: ["mcp__weather__get_temperature"]
169 }
170 })) {
171 // "result" is the final message after all tool calls complete
172 if (message.type === "result" && message.subtype === "success") {
173 console.log(message.result);
174 }
175 }
176 ```
177</CodeGroup>
178
179### Fügen Sie weitere Tools hinzu
180
181Ein Server enthält so viele Tools, wie Sie in seinem `tools`-Array auflisten. Mit mehr als einem Tool auf einem Server können Sie jedes einzelne in `allowedTools` auflisten oder das Wildcard `mcp__weather__*` verwenden, um alle Tools abzudecken, die der Server verfügbar macht.
182
183Das Beispiel unten fügt ein zweites Tool, `get_precipitation_chance`, zum `weatherServer` aus dem [Wetter-Tool-Beispiel](#weather-tool-example) hinzu und erstellt ihn mit beiden Tools im Array neu.
184
185<CodeGroup>
186 ```python Python theme={null}
187 # Define a second tool for the same server
188 @tool(
189 "get_precipitation_chance",
190 "Get the hourly precipitation probability for a location. "
191 "Optionally pass 'hours' (1-24) to control how many hours to return.",
192 {"latitude": float, "longitude": float},
193 )
194 async def get_precipitation_chance(args: dict[str, Any]) -> dict[str, Any]:
195 # 'hours' isn't in the schema - read it with .get() to make it optional
196 hours = args.get("hours", 12)
197 async with httpx.AsyncClient() as client:
198 response = await client.get(
199 "https://api.open-meteo.com/v1/forecast",
200 params={
201 "latitude": args["latitude"],
202 "longitude": args["longitude"],
203 "hourly": "precipitation_probability",
204 "forecast_days": 1,
205 },
206 )
207 data = response.json()
208 chances = data["hourly"]["precipitation_probability"][:hours]
209
210 return {
211 "content": [
212 {
213 "type": "text",
214 "text": f"Next {hours} hours: {'%, '.join(map(str, chances))}%",
215 }
216 ]
217 }
218
219
220 # Rebuild the server with both tools in the array
221 weather_server = create_sdk_mcp_server(
222 name="weather",
223 version="1.0.0",
224 tools=[get_temperature, get_precipitation_chance],
225 )
226 ```
227
228 ```typescript TypeScript theme={null}
229 // Define a second tool for the same server
230 const getPrecipitationChance = tool(
231 "get_precipitation_chance",
232 "Get the hourly precipitation probability for a location",
233 {
234 latitude: z.number(),
235 longitude: z.number(),
236 hours: z
237 .number()
238 .int()
239 .min(1)
240 .max(24)
241 .default(12) // .default() makes the parameter optional
242 .describe("How many hours of forecast to return")
243 },
244 async (args) => {
245 const response = await fetch(
246 `https://api.open-meteo.com/v1/forecast?latitude=${args.latitude}&longitude=${args.longitude}&hourly=precipitation_probability&forecast_days=1`
247 );
248 const data: any = await response.json();
249 const chances = data.hourly.precipitation_probability.slice(0, args.hours);
250
251 return {
252 content: [{ type: "text", text: `Next ${args.hours} hours: ${chances.join("%, ")}%` }]
253 };
254 }
255 );
256
257 // Rebuild the server with both tools in the array
258 const weatherServer = createSdkMcpServer({
259 name: "weather",
260 version: "1.0.0",
261 tools: [getTemperature, getPrecipitationChance]
262 });
263 ```
264</CodeGroup>
265
266Jedes Tool in diesem Array verbraucht Kontextfensterplatz bei jedem Durchgang. Wenn Sie Dutzende von Tools definieren, siehe [Tool-Suche](/de/agent-sdk/tool-search), um sie stattdessen bei Bedarf zu laden.
267
268### Fügen Sie Tool-Annotationen hinzu
269
270[Tool-Annotationen](https://modelcontextprotocol.io/docs/concepts/tools#tool-annotations) sind optionale Metadaten, die beschreiben, wie sich ein Tool verhält. Übergeben Sie sie als fünftes Argument an den `tool()`-Helper in TypeScript oder über das `annotations`-Schlüsselwortargument für den `@tool`-Dekorator in Python. Alle Hint-Felder sind Boolesche Werte.
271
272| Feld | Standard | Bedeutung |
273| :---------------- | :------- | :--------------------------------------------------------------------------------------------------------------------------- |
274| `readOnlyHint` | `false` | Tool ändert seine Umgebung nicht. Steuert, ob das Tool parallel mit anderen schreibgeschützten Tools aufgerufen werden kann. |
275| `destructiveHint` | `true` | Tool kann destruktive Updates durchführen. Nur informativ. |
276| `idempotentHint` | `false` | Wiederholte Aufrufe mit denselben Argumenten haben keine zusätzliche Auswirkung. Nur informativ. |
277| `openWorldHint` | `true` | Tool erreicht Systeme außerhalb Ihres Prozesses. Nur informativ. |
278
279Annotationen sind Metadaten, keine Durchsetzung. Ein Tool, das mit `readOnlyHint: true` markiert ist, kann immer noch auf die Festplatte schreiben, wenn das der Handler tut. Halten Sie die Annotation genau zum Handler.
280
281Dieses Beispiel fügt `readOnlyHint` zum `get_temperature`-Tool aus dem [Wetter-Tool-Beispiel](#weather-tool-example) hinzu.
282
283<CodeGroup>
284 ```python Python theme={null}
285 from claude_agent_sdk import tool, ToolAnnotations
286
287
288 @tool(
289 "get_temperature",
290 "Get the current temperature at a location",
291 {"latitude": float, "longitude": float},
292 annotations=ToolAnnotations(
293 readOnlyHint=True
294 ), # Lets Claude batch this with other read-only calls
295 )
296 async def get_temperature(args):
297 return {"content": [{"type": "text", "text": "..."}]}
298 ```
299
300 ```typescript TypeScript theme={null}
301 tool(
302 "get_temperature",
303 "Get the current temperature at a location",
304 { latitude: z.number(), longitude: z.number() },
305 async (args) => ({ content: [{ type: "text", text: `...` }] }),
306 { annotations: { readOnlyHint: true } } // Lets Claude batch this with other read-only calls
307 );
308 ```
309</CodeGroup>
310
311Siehe `ToolAnnotations` in der [TypeScript](/de/agent-sdk/typescript#toolannotations)- oder [Python](/de/agent-sdk/python#toolannotations)-Referenz.
312
313## Kontrollieren Sie den Tool-Zugriff
314
315Das [Wetter-Tool-Beispiel](#weather-tool-example) registrierte einen Server und listete Tools in `allowedTools` auf. Dieser Abschnitt behandelt, wie Tool-Namen konstruiert werden und wie Sie den Zugriff scoped, wenn Sie mehrere Tools haben oder integrierte Tools einschränken möchten.
316
317### Tool-Namensformat
318
319Wenn MCP-Tools Claude verfügbar gemacht werden, folgen ihre Namen einem bestimmten Format:
320
321* Muster: `mcp__{server_name}__{tool_name}`
322* Beispiel: Ein Tool namens `get_temperature` im Server `weather` wird zu `mcp__weather__get_temperature`
323
324### Konfigurieren Sie zulässige Tools
325
326Die `tools`-Option und die zulässigen/nicht zulässigen Listen arbeiten auf separaten Ebenen. `tools` steuert, welche integrierten Tools in Claudes Kontext erscheinen. Zulässige und nicht zulässige Tool-Listen steuern, ob Aufrufe genehmigt oder abgelehnt werden, sobald Claude sie versucht.
327
328| Option | Ebene | Auswirkung |
329| :------------------------ | :------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
330| `tools: ["Read", "Grep"]` | Verfügbarkeit | Nur die aufgelisteten integrierten Tools sind in Claudes Kontext. Nicht aufgelistete integrierte Tools werden entfernt. MCP-Tools sind nicht betroffen. |
331| `tools: []` | Verfügbarkeit | Alle integrierten Tools werden entfernt. Claude kann nur Ihre MCP-Tools verwenden. |
332| zulässige Tools | Berechtigung | Aufgelistete Tools werden ohne Genehmigungsaufforderung ausgeführt. Nicht aufgelistete Tools bleiben verfügbar; Aufrufe gehen durch den [Genehmigungsfluss](/de/agent-sdk/permissions). |
333| nicht zulässige Tools | Berechtigung | Jeder Aufruf eines aufgelisteten Tools wird abgelehnt. Das Tool bleibt in Claudes Kontext, daher kann Claude es möglicherweise immer noch versuchen, bevor der Aufruf abgelehnt wird. |
334
335Um zu begrenzen, welche integrierten Tools Claude verwenden kann, bevorzugen Sie `tools` gegenüber nicht zulässigen Tools. Das Weglassen eines Tools aus `tools` entfernt es aus dem Kontext, damit Claude es nie versucht; das Auflisten in `disallowedTools` (Python: `disallowed_tools`) blockiert den Aufruf, lässt das Tool aber sichtbar, daher kann Claude möglicherweise einen Durchgang damit verschwenden. Siehe [Konfigurieren Sie Berechtigungen](/de/agent-sdk/permissions) für die vollständige Evaluierungsreihenfolge.
336
337## Fehler behandeln
338
339Wie Ihr Handler Fehler meldet, bestimmt, ob die Agent-Schleife fortgesetzt oder gestoppt wird:
340
341| Was passiert | Ergebnis |
342| :--------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------ |
343| Handler wirft eine nicht abgefangene Ausnahme | Agent-Schleife stoppt. Claude sieht den Fehler nie, und der `query`-Aufruf schlägt fehl. |
344| Handler fängt den Fehler ab und gibt `isError: true` (TS) / `"is_error": True` (Python) zurück | Agent-Schleife setzt sich fort. Claude sieht den Fehler als Daten und kann erneut versuchen, ein anderes Tool versuchen oder den Fehler erklären. |
345
346Das Beispiel unten fängt zwei Arten von Fehlern im Handler ab, anstatt sie werfen zu lassen. Ein Nicht-200-HTTP-Status wird aus der Antwort abgefangen und als Fehler-Ergebnis zurückgegeben. Ein Netzwerkfehler oder ungültiges JSON wird durch das umgebende `try/except` (Python) oder `try/catch` (TypeScript) abgefangen und auch als Fehler-Ergebnis zurückgegeben. In beiden Fällen gibt der Handler normal zurück und die Agent-Schleife setzt sich fort.
347
348<CodeGroup>
349 ```python Python theme={null}
350 import json
351 import httpx
352 from typing import Any
353
354
355 @tool(
356 "fetch_data",
357 "Fetch data from an API",
358 {"endpoint": str}, # Simple schema
359 )
360 async def fetch_data(args: dict[str, Any]) -> dict[str, Any]:
361 try:
362 async with httpx.AsyncClient() as client:
363 response = await client.get(args["endpoint"])
364 if response.status_code != 200:
365 # Return the failure as a tool result so Claude can react to it.
366 # is_error marks this as a failed call rather than odd-looking data.
367 return {
368 "content": [
369 {
370 "type": "text",
371 "text": f"API error: {response.status_code} {response.reason_phrase}",
372 }
373 ],
374 "is_error": True,
375 }
376
377 data = response.json()
378 return {"content": [{"type": "text", "text": json.dumps(data, indent=2)}]}
379 except Exception as e:
380 # Catching here keeps the agent loop alive. An uncaught exception
381 # would end the whole query() call.
382 return {
383 "content": [{"type": "text", "text": f"Failed to fetch data: {str(e)}"}],
384 "is_error": True,
385 }
386 ```
387
388 ```typescript TypeScript theme={null}
389 tool(
390 "fetch_data",
391 "Fetch data from an API",
392 {
393 endpoint: z.string().url().describe("API endpoint URL")
394 },
395 async (args) => {
396 try {
397 const response = await fetch(args.endpoint);
398
399 if (!response.ok) {
400 // Return the failure as a tool result so Claude can react to it.
401 // isError marks this as a failed call rather than odd-looking data.
402 return {
403 content: [
404 {
405 type: "text",
406 text: `API error: ${response.status} ${response.statusText}`
407 }
408 ],
409 isError: true
410 };
411 }
412
413 const data = await response.json();
414 return {
415 content: [
416 {
417 type: "text",
418 text: JSON.stringify(data, null, 2)
419 }
420 ]
421 };
422 } catch (error) {
423 // Catching here keeps the agent loop alive. An uncaught throw
424 // would end the whole query() call.
425 return {
426 content: [
427 {
428 type: "text",
429 text: `Failed to fetch data: ${error instanceof Error ? error.message : String(error)}`
430 }
431 ],
432 isError: true
433 };
434 }
435 }
436 );
437 ```
438</CodeGroup>
439
440## Geben Sie Bilder und Ressourcen zurück
441
442Das `content`-Array in einem Tool-Ergebnis akzeptiert `text`-, `image`- und `resource`-Blöcke. Sie können sie in derselben Antwort mischen.
443
444### Bilder
445
446Ein Bildblock trägt die Bildbytes inline, kodiert als Base64. Es gibt kein URL-Feld. Um ein Bild zurückzugeben, das sich unter einer URL befindet, rufen Sie es im Handler ab, lesen Sie die Antwortbytes und kodieren Sie sie Base64, bevor Sie sie zurückgeben. Das Ergebnis wird als visueller Input verarbeitet.
447
448| Feld | Typ | Notizen |
449| :--------- | :-------- | :------------------------------------------------------------------------------ |
450| `type` | `"image"` | |
451| `data` | `string` | Base64-kodierte Bytes. Nur rohes Base64, kein `data:image/...;base64,`-Präfix |
452| `mimeType` | `string` | Erforderlich. Zum Beispiel `image/png`, `image/jpeg`, `image/webp`, `image/gif` |
453
454<CodeGroup>
455 ```python Python theme={null}
456 import base64
457 import httpx
458
459
460 # Define a tool that fetches an image from a URL and returns it to Claude
461 @tool("fetch_image", "Fetch an image from a URL and return it to Claude", {"url": str})
462 async def fetch_image(args):
463 async with httpx.AsyncClient() as client: # Fetch the image bytes
464 response = await client.get(args["url"])
465
466 return {
467 "content": [
468 {
469 "type": "image",
470 "data": base64.b64encode(response.content).decode(
471 "ascii"
472 ), # Base64-encode the raw bytes
473 "mimeType": response.headers.get(
474 "content-type", "image/png"
475 ), # Read MIME type from the response
476 }
477 ]
478 }
479 ```
480
481 ```typescript TypeScript theme={null}
482 tool(
483 "fetch_image",
484 "Fetch an image from a URL and return it to Claude",
485 {
486 url: z.string().url()
487 },
488 async (args) => {
489 const response = await fetch(args.url); // Fetch the image bytes
490 const buffer = Buffer.from(await response.arrayBuffer()); // Read into a Buffer for base64 encoding
491 const mimeType = response.headers.get("content-type") ?? "image/png";
492
493 return {
494 content: [
495 {
496 type: "image",
497 data: buffer.toString("base64"), // Base64-encode the raw bytes
498 mimeType
499 }
500 ]
501 };
502 }
503 );
504 ```
505</CodeGroup>
506
507### Ressourcen
508
509Ein Ressourcenblock bettet ein Stück Inhalt ein, das durch einen URI identifiziert wird. Der URI ist ein Label für Claude, um darauf zu verweisen; der tatsächliche Inhalt befindet sich im `text`- oder `blob`-Feld des Blocks. Verwenden Sie dies, wenn Ihr Tool etwas produziert, das sinnvoll ist, um später nach Name adressiert zu werden, wie eine generierte Datei oder ein Datensatz aus einem externen System.
510
511| Feld | Typ | Notizen |
512| :------------------ | :----------- | :----------------------------------------------------------------------- |
513| `type` | `"resource"` | |
514| `resource.uri` | `string` | Bezeichner für den Inhalt. Beliebiges URI-Schema |
515| `resource.text` | `string` | Der Inhalt, wenn er Text ist. Geben Sie dies oder `blob` an, nicht beide |
516| `resource.blob` | `string` | Der Inhalt Base64-kodiert, wenn er binär ist |
517| `resource.mimeType` | `string` | Optional |
518
519Dieses Beispiel zeigt einen Ressourcenblock, der von innen aus einem Tool-Handler zurückgegeben wird. Der URI `file:///tmp/report.md` ist ein Label, das Claude später referenzieren kann; das SDK liest nicht aus diesem Pfad.
520
521<CodeGroup>
522 ```typescript TypeScript theme={null}
523 return {
524 content: [
525 {
526 type: "resource",
527 resource: {
528 uri: "file:///tmp/report.md", // Label for Claude to reference, not a path the SDK reads
529 mimeType: "text/markdown",
530 text: "# Report\n..." // The actual content, inline
531 }
532 }
533 ]
534 };
535 ```
536
537 ```python Python theme={null}
538 return {
539 "content": [
540 {
541 "type": "resource",
542 "resource": {
543 "uri": "file:///tmp/report.md", # Label for Claude to reference, not a path the SDK reads
544 "mimeType": "text/markdown",
545 "text": "# Report\n...", # The actual content, inline
546 },
547 }
548 ]
549 }
550 ```
551</CodeGroup>
552
553Diese Block-Formen stammen aus dem MCP-`CallToolResult`-Typ. Siehe die [MCP-Spezifikation](https://modelcontextprotocol.io/specification/2025-06-18/server/tools#tool-result) für die vollständige Definition.
554
555## Geben Sie strukturierte Daten zurück
556
557`structuredContent` ist ein optionales JSON-Objekt auf dem Ergebnis, getrennt vom `content`-Array. Verwenden Sie es, um Rohwerte zurückzugeben, die Claude als exakte Felder lesen kann, anstatt sie aus einer Textzeichenkette oder einem Bild zu analysieren.
558
559Wenn `structuredContent` gesetzt ist, empfängt Claude das JSON plus alle Bild- oder Ressourcenblöcke aus `content`. Textblöcke in `content` werden nicht weitergeleitet, da angenommen wird, dass sie die strukturierten Daten duplizieren. Das Beispiel unten rendert ein Diagramm als Bildblock und gibt die Datenpunkte dahinter in `structuredContent` vom selben Handler zurück.
560
561```typescript TypeScript theme={null}
562return {
563 content: [
564 {
565 type: "image",
566 data: chartPngBuffer.toString("base64"),
567 mimeType: "image/png"
568 }
569 ],
570 structuredContent: {
571 series: "temperature_2m",
572 unit: "fahrenheit",
573 points: [62.1, 63.4, 65.0, 64.2]
574 }
575};
576```
577
578<Note>
579 Der Python-`@tool`-Dekorator leitet nur `content` und `is_error` aus dem Rückgabe-Dict des Handlers weiter. Um `structuredContent` von Python zurückzugeben, führen Sie stattdessen einen [eigenständigen MCP-Server](/de/agent-sdk/mcp) aus.
580</Note>
581
582## Beispiel: Einheitenkonverter
583
584Dieses Tool konvertiert Werte zwischen Einheiten der Länge, Temperatur und des Gewichts. Ein Benutzer kann fragen „100 Kilometer in Meilen konvertieren" oder „Was ist 72°F in Celsius", und Claude wählt den richtigen Einheitstyp und die Einheiten aus der Anfrage.
585
586Es demonstriert zwei Muster:
587
588* **Enum-Schemas:** `unit_type` ist auf einen festen Satz von Werten beschränkt. In TypeScript verwenden Sie `z.enum()`. In Python unterstützt das Dict-Schema keine Enums, daher ist das vollständige JSON-Schema-Dict erforderlich.
589* **Behandlung nicht unterstützter Eingaben:** Wenn ein Konvertierungspaar nicht gefunden wird, gibt der Handler `isError: true` zurück, damit Claude dem Benutzer sagen kann, was schief gelaufen ist, anstatt einen Fehler als normales Ergebnis zu behandeln.
590
591<CodeGroup>
592 ```python Python theme={null}
593 from typing import Any
594 from claude_agent_sdk import tool, create_sdk_mcp_server
595
596
597 # z.enum() in TypeScript becomes an "enum" constraint in JSON Schema.
598 # The dict schema has no equivalent, so full JSON Schema is required.
599 @tool(
600 "convert_units",
601 "Convert a value from one unit to another",
602 {
603 "type": "object",
604 "properties": {
605 "unit_type": {
606 "type": "string",
607 "enum": ["length", "temperature", "weight"],
608 "description": "Category of unit",
609 },
610 "from_unit": {
611 "type": "string",
612 "description": "Unit to convert from, e.g. kilometers, fahrenheit, pounds",
613 },
614 "to_unit": {"type": "string", "description": "Unit to convert to"},
615 "value": {"type": "number", "description": "Value to convert"},
616 },
617 "required": ["unit_type", "from_unit", "to_unit", "value"],
618 },
619 )
620 async def convert_units(args: dict[str, Any]) -> dict[str, Any]:
621 conversions = {
622 "length": {
623 "kilometers_to_miles": lambda v: v * 0.621371,
624 "miles_to_kilometers": lambda v: v * 1.60934,
625 "meters_to_feet": lambda v: v * 3.28084,
626 "feet_to_meters": lambda v: v * 0.3048,
627 },
628 "temperature": {
629 "celsius_to_fahrenheit": lambda v: (v * 9) / 5 + 32,
630 "fahrenheit_to_celsius": lambda v: (v - 32) * 5 / 9,
631 "celsius_to_kelvin": lambda v: v + 273.15,
632 "kelvin_to_celsius": lambda v: v - 273.15,
633 },
634 "weight": {
635 "kilograms_to_pounds": lambda v: v * 2.20462,
636 "pounds_to_kilograms": lambda v: v * 0.453592,
637 "grams_to_ounces": lambda v: v * 0.035274,
638 "ounces_to_grams": lambda v: v * 28.3495,
639 },
640 }
641
642 key = f"{args['from_unit']}_to_{args['to_unit']}"
643 fn = conversions.get(args["unit_type"], {}).get(key)
644
645 if not fn:
646 return {
647 "content": [
648 {
649 "type": "text",
650 "text": f"Unsupported conversion: {args['from_unit']} to {args['to_unit']}",
651 }
652 ],
653 "is_error": True,
654 }
655
656 result = fn(args["value"])
657 return {
658 "content": [
659 {
660 "type": "text",
661 "text": f"{args['value']} {args['from_unit']} = {result:.4f} {args['to_unit']}",
662 }
663 ]
664 }
665
666
667 converter_server = create_sdk_mcp_server(
668 name="converter",
669 version="1.0.0",
670 tools=[convert_units],
671 )
672 ```
673
674 ```typescript TypeScript theme={null}
675 import { tool, createSdkMcpServer } from "@anthropic-ai/claude-agent-sdk";
676 import { z } from "zod";
677
678 const convert = tool(
679 "convert_units",
680 "Convert a value from one unit to another",
681 {
682 unit_type: z.enum(["length", "temperature", "weight"]).describe("Category of unit"),
683 from_unit: z
684 .string()
685 .describe("Unit to convert from, e.g. kilometers, fahrenheit, pounds"),
686 to_unit: z.string().describe("Unit to convert to"),
687 value: z.number().describe("Value to convert")
688 },
689 async (args) => {
690 type Conversions = Record<string, Record<string, (v: number) => number>>;
691
692 const conversions: Conversions = {
693 length: {
694 kilometers_to_miles: (v) => v * 0.621371,
695 miles_to_kilometers: (v) => v * 1.60934,
696 meters_to_feet: (v) => v * 3.28084,
697 feet_to_meters: (v) => v * 0.3048
698 },
699 temperature: {
700 celsius_to_fahrenheit: (v) => (v * 9) / 5 + 32,
701 fahrenheit_to_celsius: (v) => ((v - 32) * 5) / 9,
702 celsius_to_kelvin: (v) => v + 273.15,
703 kelvin_to_celsius: (v) => v - 273.15
704 },
705 weight: {
706 kilograms_to_pounds: (v) => v * 2.20462,
707 pounds_to_kilograms: (v) => v * 0.453592,
708 grams_to_ounces: (v) => v * 0.035274,
709 ounces_to_grams: (v) => v * 28.3495
710 }
711 };
712
713 const key = `${args.from_unit}_to_${args.to_unit}`;
714 const fn = conversions[args.unit_type]?.[key];
715
716 if (!fn) {
717 return {
718 content: [
719 {
720 type: "text",
721 text: `Unsupported conversion: ${args.from_unit} to ${args.to_unit}`
722 }
723 ],
724 isError: true
725 };
726 }
727
728 const result = fn(args.value);
729 return {
730 content: [
731 {
732 type: "text",
733 text: `${args.value} ${args.from_unit} = ${result.toFixed(4)} ${args.to_unit}`
734 }
735 ]
736 };
737 }
738 );
739
740 const converterServer = createSdkMcpServer({
741 name: "converter",
742 version: "1.0.0",
743 tools: [convert]
744 });
745 ```
746</CodeGroup>
747
748Sobald der Server definiert ist, übergeben Sie ihn an `query` auf die gleiche Weise wie das Wetter-Beispiel. Dieses Beispiel sendet drei verschiedene Prompts in einer Schleife, um zu zeigen, wie dasselbe Tool verschiedene Einheitstypen handhabt. Für jede Antwort inspiziert es `AssistantMessage`-Objekte (die die Tool-Aufrufe enthalten, die Claude während dieses Durchgangs gemacht hat) und gibt jeden `ToolUseBlock` aus, bevor es den endgültigen `ResultMessage`-Text ausgibt. Dies lässt Sie sehen, wann Claude das Tool verwendet, im Gegensatz zu Antworten aus seinem eigenen Wissen.
749
750<CodeGroup>
751 ```python Python theme={null}
752 import asyncio
753 from claude_agent_sdk import (
754 query,
755 ClaudeAgentOptions,
756 ResultMessage,
757 AssistantMessage,
758 ToolUseBlock,
759 )
760
761
762 async def main():
763 options = ClaudeAgentOptions(
764 mcp_servers={"converter": converter_server},
765 allowed_tools=["mcp__converter__convert_units"],
766 )
767
768 prompts = [
769 "Convert 100 kilometers to miles.",
770 "What is 72°F in Celsius?",
771 "How many pounds is 5 kilograms?",
772 ]
773
774 for prompt in prompts:
775 async for message in query(prompt=prompt, options=options):
776 if isinstance(message, AssistantMessage):
777 for block in message.content:
778 if isinstance(block, ToolUseBlock):
779 print(f"[tool call] {block.name}({block.input})")
780 elif isinstance(message, ResultMessage) and message.subtype == "success":
781 print(f"Q: {prompt}\nA: {message.result}\n")
782
783
784 asyncio.run(main())
785 ```
786
787 ```typescript TypeScript theme={null}
788 import { query } from "@anthropic-ai/claude-agent-sdk";
789
790 const prompts = [
791 "Convert 100 kilometers to miles.",
792 "What is 72°F in Celsius?",
793 "How many pounds is 5 kilograms?"
794 ];
795
796 for (const prompt of prompts) {
797 for await (const message of query({
798 prompt,
799 options: {
800 mcpServers: { converter: converterServer },
801 allowedTools: ["mcp__converter__convert_units"]
802 }
803 })) {
804 if (message.type === "assistant") {
805 for (const block of message.message.content) {
806 if (block.type === "tool_use") {
807 console.log(`[tool call] ${block.name}`, block.input);
808 }
809 }
810 } else if (message.type === "result" && message.subtype === "success") {
811 console.log(`Q: ${prompt}\nA: ${message.result}\n`);
812 }
813 }
814 }
815 ```
816</CodeGroup>
817
818## Nächste Schritte
819
820Benutzerdefinierte Tools wickeln asynchrone Funktionen in einer Standardschnittstelle ein. Sie können die Muster auf dieser Seite im selben Server mischen: Ein einzelner Server kann ein Datenbank-Tool, ein API-Gateway-Tool und einen Bild-Renderer nebeneinander halten.
821
822Von hier aus:
823
824* Wenn Ihr Server auf Dutzende von Tools wächst, siehe [Tool-Suche](/de/agent-sdk/tool-search), um das Laden zu verschieben, bis Claude sie benötigt.
825* Um sich mit externen MCP-Servern (Dateisystem, GitHub, Slack) zu verbinden, anstatt Ihre eigenen zu erstellen, siehe [Verbinden Sie MCP-Server](/de/agent-sdk/mcp).
826* Um zu kontrollieren, welche Tools automatisch ausgeführt werden, im Gegensatz zu denen, die Genehmigung erfordern, siehe [Konfigurieren Sie Berechtigungen](/de/agent-sdk/permissions).
827
828## Verwandte Dokumentation
829
830* [TypeScript SDK-Referenz](/de/agent-sdk/typescript)
831* [Python SDK-Referenz](/de/agent-sdk/python)
832* [MCP-Dokumentation](https://modelcontextprotocol.io)
833* [SDK-Übersicht](/de/agent-sdk/overview)