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# Berikan Claude alat kustom
6
7> Tentukan alat kustom dengan server MCP dalam proses SDK Agent sehingga Claude dapat memanggil fungsi Anda, mengakses API Anda, dan melakukan operasi khusus domain.
8
9Alat kustom memperluas SDK Agent dengan memungkinkan Anda menentukan fungsi Anda sendiri yang dapat dipanggil Claude selama percakapan. Menggunakan server MCP dalam proses SDK, Anda dapat memberikan Claude akses ke database, API eksternal, logika khusus domain, atau kemampuan lain yang dibutuhkan aplikasi Anda.
10
11Panduan ini mencakup cara menentukan alat dengan skema input dan penangan, membundel mereka ke dalam server MCP, meneruskannya ke `query`, dan mengontrol alat mana yang dapat diakses Claude. Ini juga mencakup penanganan kesalahan, anotasi alat, dan mengembalikan konten non-teks seperti gambar.
12
13## Referensi cepat
14
15| Jika Anda ingin... | Lakukan ini |
16| :-------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
17| Menentukan alat | Gunakan [`@tool`](/id/agent-sdk/python#tool) (Python) atau [`tool()`](/id/agent-sdk/typescript#tool) (TypeScript) dengan nama, deskripsi, skema, dan penangan. Lihat [Buat alat kustom](#create-a-custom-tool). |
18| Mendaftarkan alat dengan Claude | Bungkus dalam `create_sdk_mcp_server` / `createSdkMcpServer` dan teruskan ke `mcpServers` dalam `query()`. Lihat [Panggil alat kustom](#call-a-custom-tool). |
19| Pra-setujui alat | Tambahkan ke alat yang diizinkan Anda. Lihat [Konfigurasi alat yang diizinkan](#configure-allowed-tools). |
20| Hapus alat bawaan dari konteks Claude | Teruskan array `tools` yang hanya mencantumkan bawaan yang Anda inginkan. Lihat [Konfigurasi alat yang diizinkan](#configure-allowed-tools). |
21| Biarkan Claude memanggil alat secara paralel | Atur `readOnlyHint: true` pada alat tanpa efek samping. Lihat [Tambahkan anotasi alat](#add-tool-annotations). |
22| Tangani kesalahan tanpa menghentikan loop | Kembalikan `isError: true` alih-alih melempar. Lihat [Tangani kesalahan](#handle-errors). |
23| Kembalikan gambar atau file | Gunakan blok `image` atau `resource` dalam array konten. Lihat [Kembalikan gambar dan sumber daya](#return-images-and-resources). |
24| Kembalikan hasil JSON yang dapat dibaca mesin | Atur `structuredContent` pada hasilnya. Lihat [Kembalikan data terstruktur](#return-structured-data). |
25| Skalakan ke banyak alat | Gunakan [pencarian alat](/id/agent-sdk/tool-search) untuk memuat alat sesuai permintaan. |
26
27## Buat alat kustom
28
29Alat didefinisikan oleh empat bagian, diteruskan sebagai argumen ke pembantu [`tool()`](/id/agent-sdk/typescript#tool) di TypeScript atau dekorator [`@tool`](/id/agent-sdk/python#tool) di Python:
30
31* **Nama:** pengidentifikasi unik yang digunakan Claude untuk memanggil alat.
32* **Deskripsi:** apa yang dilakukan alat. Claude membaca ini untuk memutuskan kapan memanggilnya.
33* **Skema input:** argumen yang harus disediakan Claude. Di TypeScript ini selalu [skema Zod](https://zod.dev/), dan `args` penangan diketik darinya secara otomatis. Di Python ini adalah dict yang memetakan nama ke tipe, seperti `{"latitude": float}`, yang dikonversi SDK ke JSON Schema untuk Anda. Dekorator Python juga menerima dict [JSON Schema](https://json-schema.org/understanding-json-schema/about) lengkap secara langsung ketika Anda membutuhkan enum, rentang, bidang opsional, atau objek bersarang.
34* **Penangan:** fungsi async yang berjalan ketika Claude memanggil alat. Ini menerima argumen yang divalidasi dan harus mengembalikan objek dengan:
35 * `content` (diperlukan): array blok hasil, masing-masing dengan `type` dari `"text"`, `"image"`, atau `"resource"`. Lihat [Kembalikan gambar dan sumber daya](#return-images-and-resources) untuk blok non-teks.
36 * `structuredContent` (opsional): objek JSON yang menyimpan hasil sebagai data yang dapat dibaca mesin, dikembalikan bersama `content`. Lihat [Kembalikan data terstruktur](#return-structured-data).
37 * `isError` (opsional): atur ke `true` untuk menandakan kegagalan alat sehingga Claude dapat bereaksi terhadapnya. Lihat [Tangani kesalahan](#handle-errors).
38
39Setelah menentukan alat, bungkus dalam server dengan [`createSdkMcpServer`](/id/agent-sdk/typescript#createsdkmcpserver) (TypeScript) atau [`create_sdk_mcp_server`](/id/agent-sdk/python#create_sdk_mcp_server) (Python). Server berjalan dalam proses di dalam aplikasi Anda, bukan sebagai proses terpisah.
40
41### Contoh alat cuaca
42
43Contoh ini menentukan alat `get_temperature` dan membungkusnya dalam server MCP. Ini hanya menyiapkan alat; untuk meneruskannya ke `query` dan menjalankannya, lihat [Panggil alat kustom](#call-a-custom-tool) di bawah.
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
125Lihat referensi TypeScript [`tool()`](/id/agent-sdk/typescript#tool) atau referensi Python [`@tool`](/id/agent-sdk/python#tool) untuk detail parameter lengkap, termasuk format input JSON Schema dan struktur nilai pengembalian.
126
127<Tip>
128 Untuk membuat parameter opsional: di TypeScript, tambahkan `.default()` ke bidang Zod. Di Python, skema dict memperlakukan setiap kunci sebagai wajib, jadi tinggalkan parameter dari skema, sebutkan dalam string deskripsi, dan baca dengan `args.get()` dalam penangan. Alat [`get_precipitation_chance` di bawah](#add-more-tools) menunjukkan kedua pola.
129</Tip>
130
131### Panggil alat kustom
132
133Teruskan server MCP yang Anda buat ke `query` melalui opsi `mcpServers`. Kunci dalam `mcpServers` menjadi segmen `{server_name}` dalam nama lengkap setiap alat: `mcp__{server_name}__{tool_name}`. Cantumkan nama itu dalam `allowedTools` sehingga alat berjalan tanpa prompt izin.
134
135Cuplikan ini menggunakan kembali `weatherServer` dari [contoh di atas](#weather-tool-example) untuk menanyakan Claude tentang cuaca di lokasi tertentu.
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### Tambahkan lebih banyak alat
180
181Server menyimpan sebanyak alat yang Anda cantumkan dalam array `tools` nya. Dengan lebih dari satu alat di server, Anda dapat mencantumkan masing-masing dalam `allowedTools` secara individual atau menggunakan wildcard `mcp__weather__*` untuk mencakup setiap alat yang diekspos server.
182
183Contoh di bawah menambahkan alat kedua, `get_precipitation_chance`, ke `weatherServer` dari [contoh alat cuaca](#weather-tool-example) dan membangunnya kembali dengan kedua alat dalam array.
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
266Setiap alat dalam array ini mengonsumsi ruang jendela konteks pada setiap giliran. Jika Anda menentukan puluhan alat, lihat [pencarian alat](/id/agent-sdk/tool-search) untuk memuat mereka sesuai permintaan.
267
268### Tambahkan anotasi alat
269
270[Anotasi alat](https://modelcontextprotocol.io/docs/concepts/tools#tool-annotations) adalah metadata opsional yang menggambarkan perilaku alat. Teruskan sebagai argumen kelima ke pembantu `tool()` di TypeScript atau melalui argumen kata kunci `annotations` untuk dekorator `@tool` di Python. Semua bidang hint adalah Boolean.
271
272| Bidang | Default | Arti |
273| :---------------- | :------ | :-------------------------------------------------------------------------------------------------------------------------- |
274| `readOnlyHint` | `false` | Alat tidak memodifikasi lingkungannya. Mengontrol apakah alat dapat dipanggil secara paralel dengan alat baca-saja lainnya. |
275| `destructiveHint` | `true` | Alat dapat melakukan pembaruan destruktif. Hanya informatif. |
276| `idempotentHint` | `false` | Panggilan berulang dengan argumen yang sama tidak memiliki efek tambahan. Hanya informatif. |
277| `openWorldHint` | `true` | Alat menjangkau sistem di luar proses Anda. Hanya informatif. |
278
279Anotasi adalah metadata, bukan penegakan. Alat yang ditandai `readOnlyHint: true` masih dapat menulis ke disk jika itulah yang dilakukan penangan. Jaga anotasi akurat dengan penangan.
280
281Contoh ini menambahkan `readOnlyHint` ke alat `get_temperature` dari [contoh alat cuaca](#weather-tool-example).
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
311Lihat `ToolAnnotations` dalam referensi [TypeScript](/id/agent-sdk/typescript#toolannotations) atau [Python](/id/agent-sdk/python#toolannotations).
312
313## Kontrol akses alat
314
315[Contoh alat cuaca](#weather-tool-example) mendaftarkan server dan mencantumkan alat dalam `allowedTools`. Bagian ini mencakup cara nama alat dibangun dan cara membatasi akses ketika Anda memiliki beberapa alat atau ingin membatasi bawaan.
316
317### Format nama alat
318
319Ketika alat MCP diekspos ke Claude, nama mereka mengikuti format tertentu:
320
321* Pola: `mcp__{server_name}__{tool_name}`
322* Contoh: Alat bernama `get_temperature` di server `weather` menjadi `mcp__weather__get_temperature`
323
324### Konfigurasi alat yang diizinkan
325
326Opsi `tools` dan daftar izin/larangan yang diizinkan beroperasi pada lapisan terpisah. `tools` mengontrol alat bawaan mana yang muncul dalam konteks Claude. Daftar alat yang diizinkan dan dilarang mengontrol apakah panggilan disetujui atau ditolak setelah Claude mencoba mereka.
327
328| Opsi | Lapisan | Efek |
329| :------------------------ | :----------- | :-------------------------------------------------------------------------------------------------------------------------------------------------- |
330| `tools: ["Read", "Grep"]` | Ketersediaan | Hanya bawaan yang tercantum ada dalam konteks Claude. Bawaan yang tidak tercantum dihapus. Alat MCP tidak terpengaruh. |
331| `tools: []` | Ketersediaan | Semua bawaan dihapus. Claude hanya dapat menggunakan alat MCP Anda. |
332| alat yang diizinkan | Izin | Alat yang tercantum berjalan tanpa prompt izin. Alat yang tidak tercantum tetap tersedia; panggilan melalui [alur izin](/id/agent-sdk/permissions). |
333| alat yang dilarang | Izin | Setiap panggilan ke alat yang tercantum ditolak. Alat tetap dalam konteks Claude, jadi Claude mungkin masih mencoba sebelum panggilan ditolak. |
334
335Untuk membatasi alat bawaan mana yang dapat digunakan Claude, lebih suka `tools` daripada alat yang dilarang. Menghilangkan alat dari `tools` menghapusnya dari konteks sehingga Claude tidak pernah mencobanya; mencantumkannya dalam `disallowedTools` (Python: `disallowed_tools`) memblokir panggilan tetapi membiarkan alat terlihat, jadi Claude mungkin membuang giliran mencobanya. Lihat [Konfigurasi izin](/id/agent-sdk/permissions) untuk urutan evaluasi lengkap.
336
337## Tangani kesalahan
338
339Cara penangan Anda melaporkan kesalahan menentukan apakah loop agen berlanjut atau berhenti:
340
341| Apa yang terjadi | Hasil |
342| :------------------------------------------------------------------------------------------------ | :----------------------------------------------------------------------------------------------------------------------------------- |
343| Penangan melempar pengecualian yang tidak tertangkap | Loop agen berhenti. Claude tidak pernah melihat kesalahan, dan panggilan `query` gagal. |
344| Penangan menangkap kesalahan dan mengembalikan `isError: true` (TS) / `"is_error": True` (Python) | Loop agen berlanjut. Claude melihat kesalahan sebagai data dan dapat mencoba lagi, mencoba alat berbeda, atau menjelaskan kegagalan. |
345
346Contoh di bawah menangkap dua jenis kegagalan di dalam penangan alih-alih membiarkan mereka melempar. Status HTTP non-200 ditangkap dari respons dan dikembalikan sebagai hasil kesalahan. Kesalahan jaringan atau JSON yang tidak valid ditangkap oleh `try/except` (Python) atau `try/catch` (TypeScript) sekitarnya dan juga dikembalikan sebagai hasil kesalahan. Dalam kedua kasus penangan mengembalikan secara normal dan loop agen berlanjut.
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## Kembalikan gambar dan sumber daya
441
442Array `content` dalam hasil alat menerima blok `text`, `image`, dan `resource`. Anda dapat mencampurnya dalam respons yang sama.
443
444### Gambar
445
446Blok gambar membawa byte gambar secara inline, dikodekan sebagai base64. Tidak ada bidang URL. Untuk mengembalikan gambar yang berada di URL, ambil dalam penangan, baca byte respons, dan kodekan base64 sebelum mengembalikan. Hasilnya diproses sebagai input visual.
447
448| Bidang | Tipe | Catatan |
449| :--------- | :-------- | :------------------------------------------------------------------------------------- |
450| `type` | `"image"` | |
451| `data` | `string` | Byte yang dikodekan Base64. Hanya base64 mentah, tanpa awalan `data:image/...;base64,` |
452| `mimeType` | `string` | Diperlukan. Misalnya `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### Sumber daya
508
509Blok sumber daya menyematkan sepotong konten yang diidentifikasi oleh URI. URI adalah label untuk Claude referensikan; konten aktual berada dalam bidang `text` atau `blob` blok. Gunakan ini ketika alat Anda menghasilkan sesuatu yang masuk akal untuk ditangani berdasarkan nama nanti, seperti file yang dihasilkan atau catatan dari sistem eksternal.
510
511| Bidang | Tipe | Catatan |
512| :------------------ | :----------- | :---------------------------------------------------------- |
513| `type` | `"resource"` | |
514| `resource.uri` | `string` | Pengidentifikasi untuk konten. Skema URI apa pun |
515| `resource.text` | `string` | Konten, jika teks. Sediakan ini atau `blob`, bukan keduanya |
516| `resource.blob` | `string` | Konten yang dikodekan base64, jika biner |
517| `resource.mimeType` | `string` | Opsional |
518
519Contoh ini menunjukkan blok sumber daya yang dikembalikan dari dalam penangan alat. URI `file:///tmp/report.md` adalah label yang dapat direferensikan Claude nanti; SDK tidak membaca dari jalur itu.
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
553Bentuk blok ini berasal dari tipe MCP `CallToolResult`. Lihat [spesifikasi MCP](https://modelcontextprotocol.io/specification/2025-06-18/server/tools#tool-result) untuk definisi lengkap.
554
555## Kembalikan data terstruktur
556
557`structuredContent` adalah objek JSON opsional pada hasil, terpisah dari array `content`. Gunakan untuk mengembalikan nilai mentah yang dapat dibaca Claude sebagai bidang tepat alih-alih menguraikannya dari string teks atau gambar.
558
559Ketika `structuredContent` diatur, Claude menerima JSON plus blok gambar atau sumber daya apa pun dari `content`. Blok teks dalam `content` tidak diteruskan, karena diasumsikan menduplikasi data terstruktur. Contoh di bawah merender bagan sebagai blok gambar dan mengembalikan titik data di baliknya dalam `structuredContent` dari penangan yang sama.
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 Dekorator Python `@tool` hanya meneruskan `content` dan `is_error` dari dict pengembalian penangan. Untuk mengembalikan `structuredContent` dari Python, jalankan [server MCP mandiri](/id/agent-sdk/mcp) alih-alih server SDK dalam proses.
580</Note>
581
582## Contoh: konverter unit
583
584Alat ini mengonversi nilai antara unit panjang, suhu, dan berat. Pengguna dapat menanyakan "konversi 100 kilometer ke mil" atau "berapa 72°F dalam Celsius," dan Claude memilih tipe unit dan unit yang tepat dari permintaan.
585
586Ini menunjukkan dua pola:
587
588* **Skema enum:** `unit_type` dibatasi pada set nilai tetap. Di TypeScript, gunakan `z.enum()`. Di Python, skema dict tidak mendukung enum, jadi dict JSON Schema lengkap diperlukan.
589* **Penanganan input yang tidak didukung:** ketika pasangan konversi tidak ditemukan, penangan mengembalikan `isError: true` sehingga Claude dapat memberi tahu pengguna apa yang salah alih-alih memperlakukan kegagalan sebagai hasil normal.
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
748Setelah server didefinisikan, teruskan ke `query` dengan cara yang sama seperti contoh cuaca. Contoh ini mengirim tiga prompt berbeda dalam loop untuk menunjukkan alat yang sama menangani tipe unit berbeda. Untuk setiap respons, ia memeriksa objek `AssistantMessage` (yang berisi panggilan alat yang dibuat Claude selama giliran itu) dan mencetak setiap `ToolUseBlock` sebelum mencetak teks `ResultMessage` akhir. Ini memungkinkan Anda melihat kapan Claude menggunakan alat versus menjawab dari pengetahuannya sendiri.
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## Langkah berikutnya
819
820Alat kustom membungkus fungsi async dalam antarmuka standar. Anda dapat mencampur pola di halaman ini di server yang sama: server tunggal dapat menyimpan alat database, alat gateway API, dan renderer gambar bersama-sama.
821
822Dari sini:
823
824* Jika server Anda tumbuh menjadi puluhan alat, lihat [pencarian alat](/id/agent-sdk/tool-search) untuk menunda pemuatan mereka sampai Claude membutuhkannya.
825* Untuk terhubung ke server MCP eksternal (sistem file, GitHub, Slack) alih-alih membangun milik Anda sendiri, lihat [Hubungkan server MCP](/id/agent-sdk/mcp).
826* Untuk mengontrol alat mana yang berjalan secara otomatis versus memerlukan persetujuan, lihat [Konfigurasi izin](/id/agent-sdk/permissions).
827
828## Dokumentasi terkait
829
830* [Referensi SDK TypeScript](/id/agent-sdk/typescript)
831* [Referensi SDK Python](/id/agent-sdk/python)
832* [Dokumentasi MCP](https://modelcontextprotocol.io)
833* [Ikhtisar SDK](/id/agent-sdk/overview)