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# Fornisci a Claude strumenti personalizzati
6
7> Definisci strumenti personalizzati con il server MCP in-process dell'Agent SDK di Claude in modo che Claude possa chiamare le tue funzioni, accedere alle tue API ed eseguire operazioni specifiche del dominio.
8
9Gli strumenti personalizzati estendono l'Agent SDK permettendoti di definire le tue funzioni che Claude può chiamare durante una conversazione. Utilizzando il server MCP in-process dell'SDK, puoi dare a Claude accesso a database, API esterne, logica specifica del dominio o qualsiasi altra capacità di cui la tua applicazione ha bisogno.
10
11Questa guida copre come definire strumenti con schemi di input e handler, raggrupparli in un server MCP, passarli a `query` e controllare a quali strumenti Claude può accedere. Copre anche la gestione degli errori, le annotazioni degli strumenti e la restituzione di contenuti non testuali come immagini.
12
13## Riferimento rapido
14
15| Se vuoi... | Fai questo |
16| :------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
17| Definire uno strumento | Usa [`@tool`](/it/agent-sdk/python#tool) (Python) o [`tool()`](/it/agent-sdk/typescript#tool) (TypeScript) con un nome, una descrizione, uno schema e un handler. Vedi [Creare uno strumento personalizzato](#create-a-custom-tool). |
18| Registrare uno strumento con Claude | Avvolgi in `create_sdk_mcp_server` / `createSdkMcpServer` e passa a `mcpServers` in `query()`. Vedi [Chiamare uno strumento personalizzato](#call-a-custom-tool). |
19| Pre-approvare uno strumento | Aggiungi ai tuoi strumenti consentiti. Vedi [Configurare gli strumenti consentiti](#configure-allowed-tools). |
20| Rimuovere uno strumento integrato dal contesto di Claude | Passa un array `tools` elencando solo gli strumenti integrati che desideri. Vedi [Configurare gli strumenti consentiti](#configure-allowed-tools). |
21| Permettere a Claude di chiamare strumenti in parallelo | Imposta `readOnlyHint: true` su strumenti senza effetti collaterali. Vedi [Aggiungere annotazioni degli strumenti](#add-tool-annotations). |
22| Gestire gli errori senza interrompere il ciclo | Restituisci `isError: true` invece di lanciare un'eccezione. Vedi [Gestire gli errori](#handle-errors). |
23| Restituire immagini o file | Usa blocchi `image` o `resource` nell'array di contenuti. Vedi [Restituire immagini e risorse](#return-images-and-resources). |
24| Restituire un risultato JSON leggibile da macchina | Imposta `structuredContent` sul risultato. Vedi [Restituire dati strutturati](#return-structured-data). |
25| Scalare a molti strumenti | Usa [tool search](/it/agent-sdk/tool-search) per caricare gli strumenti su richiesta. |
26
27## Creare uno strumento personalizzato
28
29Uno strumento è definito da quattro parti, passate come argomenti al helper [`tool()`](/it/agent-sdk/typescript#tool) in TypeScript o al decoratore [`@tool`](/it/agent-sdk/python#tool) in Python:
30
31* **Nome:** un identificatore univoco che Claude usa per chiamare lo strumento.
32* **Descrizione:** cosa fa lo strumento. Claude legge questo per decidere quando chiamarlo.
33* **Schema di input:** gli argomenti che Claude deve fornire. In TypeScript questo è sempre uno [schema Zod](https://zod.dev/), e gli `args` dell'handler sono tipizzati automaticamente da esso. In Python questo è un dict che mappa nomi a tipi, come `{"latitude": float}`, che l'SDK converte in JSON Schema per te. Il decoratore Python accetta anche un dict completo di [JSON Schema](https://json-schema.org/understanding-json-schema/about) direttamente quando hai bisogno di enum, intervalli, campi opzionali o oggetti annidati.
34* **Handler:** la funzione asincrona che viene eseguita quando Claude chiama lo strumento. Riceve gli argomenti convalidati e deve restituire un oggetto con:
35 * `content` (obbligatorio): un array di blocchi di risultato, ognuno con un `type` di `"text"`, `"image"` o `"resource"`. Vedi [Restituire immagini e risorse](#return-images-and-resources) per blocchi non testuali.
36 * `structuredContent` (opzionale): un oggetto JSON che contiene il risultato come dati leggibili da macchina, restituito insieme a `content`. Vedi [Restituire dati strutturati](#return-structured-data).
37 * `isError` (opzionale): imposta su `true` per segnalare un fallimento dello strumento in modo che Claude possa reagire. Vedi [Gestire gli errori](#handle-errors).
38
39Dopo aver definito uno strumento, avvolgilo in un server con [`createSdkMcpServer`](/it/agent-sdk/typescript#createsdkmcpserver) (TypeScript) o [`create_sdk_mcp_server`](/it/agent-sdk/python#create_sdk_mcp_server) (Python). Il server viene eseguito in-process all'interno della tua applicazione, non come processo separato.
40
41### Esempio di strumento meteo
42
43Questo esempio definisce uno strumento `get_temperature` e lo avvolge in un server MCP. Configura solo lo strumento; per passarlo a `query` e eseguirlo, vedi [Chiamare uno strumento personalizzato](#call-a-custom-tool) di seguito.
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
125Vedi il riferimento TypeScript [`tool()`](/it/agent-sdk/typescript#tool) o il riferimento Python [`@tool`](/it/agent-sdk/python#tool) per i dettagli completi dei parametri, inclusi i formati di input JSON Schema e la struttura del valore di ritorno.
126
127<Tip>
128 Per rendere un parametro opzionale: in TypeScript, aggiungi `.default()` al campo Zod. In Python, lo schema dict tratta ogni chiave come obbligatoria, quindi ometti il parametro dallo schema, menzionalo nella stringa di descrizione e leggilo con `args.get()` nell'handler. Lo strumento [`get_precipitation_chance` di seguito](#add-more-tools) mostra entrambi i pattern.
129</Tip>
130
131### Chiamare uno strumento personalizzato
132
133Passa il server MCP che hai creato a `query` tramite l'opzione `mcpServers`. La chiave in `mcpServers` diventa il segmento `{server_name}` nel nome completamente qualificato di ogni strumento: `mcp__{server_name}__{tool_name}`. Elenca quel nome in `allowedTools` in modo che lo strumento venga eseguito senza un prompt di autorizzazione.
134
135Questi snippet riutilizzano il `weatherServer` dall'[esempio precedente](#weather-tool-example) per chiedere a Claude qual è il meteo in una posizione specifica.
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### Aggiungere più strumenti
180
181Un server contiene quanti strumenti elenchi nel suo array `tools`. Con più di uno strumento su un server, puoi elencare ognuno in `allowedTools` individualmente o usare il wildcard `mcp__weather__*` per coprire ogni strumento che il server espone.
182
183L'esempio di seguito aggiunge un secondo strumento, `get_precipitation_chance`, al `weatherServer` dall'[esempio di strumento meteo](#weather-tool-example) e lo ricostruisce con entrambi gli strumenti nell'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
266Ogni strumento in questo array consuma spazio della finestra di contesto ad ogni turno. Se stai definendo dozzine di strumenti, vedi [tool search](/it/agent-sdk/tool-search) per caricarli su richiesta invece.
267
268### Aggiungere annotazioni degli strumenti
269
270Le [annotazioni degli strumenti](https://modelcontextprotocol.io/docs/concepts/tools#tool-annotations) sono metadati opzionali che descrivono come si comporta uno strumento. Passali come quinto argomento al helper `tool()` in TypeScript o tramite l'argomento della parola chiave `annotations` per il decoratore `@tool` in Python. Tutti i campi hint sono booleani.
271
272| Campo | Predefinito | Significato |
273| :---------------- | :---------- | :----------------------------------------------------------------------------------------------------------------------------------------- |
274| `readOnlyHint` | `false` | Lo strumento non modifica il suo ambiente. Controlla se lo strumento può essere chiamato in parallelo con altri strumenti di sola lettura. |
275| `destructiveHint` | `true` | Lo strumento può eseguire aggiornamenti distruttivi. Solo informativo. |
276| `idempotentHint` | `false` | Le chiamate ripetute con gli stessi argomenti non hanno effetti aggiuntivi. Solo informativo. |
277| `openWorldHint` | `true` | Lo strumento raggiunge sistemi al di fuori del tuo processo. Solo informativo. |
278
279Le annotazioni sono metadati, non applicazione. Uno strumento contrassegnato con `readOnlyHint: true` può comunque scrivere su disco se è quello che fa l'handler. Mantieni l'annotazione accurata rispetto all'handler.
280
281Questo esempio aggiunge `readOnlyHint` allo strumento `get_temperature` dall'[esempio di strumento meteo](#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
311Vedi `ToolAnnotations` nel riferimento [TypeScript](/it/agent-sdk/typescript#toolannotations) o [Python](/it/agent-sdk/python#toolannotations).
312
313## Controllare l'accesso agli strumenti
314
315L'[esempio di strumento meteo](#weather-tool-example) ha registrato un server e elencato gli strumenti in `allowedTools`. Questa sezione copre come vengono costruiti i nomi degli strumenti e come limitare l'accesso quando hai più strumenti o vuoi limitare gli strumenti integrati.
316
317### Formato del nome dello strumento
318
319Quando gli strumenti MCP vengono esposti a Claude, i loro nomi seguono un formato specifico:
320
321* Pattern: `mcp__{server_name}__{tool_name}`
322* Esempio: Uno strumento denominato `get_temperature` nel server `weather` diventa `mcp__weather__get_temperature`
323
324### Configurare gli strumenti consentiti
325
326L'opzione `tools` e gli elenchi consentiti/non consentiti operano su livelli separati. `tools` controlla quali strumenti integrati appaiono nel contesto di Claude. Gli elenchi di strumenti consentiti e non consentiti controllano se le chiamate vengono approvate o negate una volta che Claude tenta di farle.
327
328| Opzione | Livello | Effetto |
329| :------------------------ | :------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
330| `tools: ["Read", "Grep"]` | Disponibilità | Solo gli strumenti integrati elencati sono nel contesto di Claude. Gli strumenti integrati non elencati vengono rimossi. Gli strumenti MCP non sono interessati. |
331| `tools: []` | Disponibilità | Tutti gli strumenti integrati vengono rimossi. Claude può usare solo i tuoi strumenti MCP. |
332| strumenti consentiti | Autorizzazione | Gli strumenti elencati vengono eseguiti senza un prompt di autorizzazione. Gli strumenti non elencati rimangono disponibili; le chiamate passano attraverso il [flusso di autorizzazione](/it/agent-sdk/permissions). |
333| strumenti non consentiti | Autorizzazione | Ogni chiamata a uno strumento elencato viene negata. Lo strumento rimane nel contesto di Claude, quindi Claude potrebbe comunque tentarlo prima che la chiamata venga rifiutata. |
334
335Per limitare quali strumenti integrati Claude può usare, preferisci `tools` rispetto agli strumenti non consentiti. Omettere uno strumento da `tools` lo rimuove dal contesto in modo che Claude non lo tenti mai; elencarlo in `disallowedTools` (Python: `disallowed_tools`) blocca la chiamata ma lascia lo strumento visibile, quindi Claude potrebbe sprecare un turno tentandolo. Vedi [Configurare le autorizzazioni](/it/agent-sdk/permissions) per l'ordine di valutazione completo.
336
337## Gestire gli errori
338
339Come il tuo handler segnala gli errori determina se il ciclo dell'agente continua o si ferma:
340
341| Cosa succede | Risultato |
342| :------------------------------------------------------------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------- |
343| L'handler lancia un'eccezione non catturata | Il ciclo dell'agente si ferma. Claude non vede mai l'errore e la chiamata `query` fallisce. |
344| L'handler cattura l'errore e restituisce `isError: true` (TS) / `"is_error": True` (Python) | Il ciclo dell'agente continua. Claude vede l'errore come dati e può riprovare, provare uno strumento diverso o spiegare il fallimento. |
345
346L'esempio di seguito cattura due tipi di fallimenti all'interno dell'handler invece di lasciarli lanciare. Uno stato HTTP non-200 viene catturato dalla risposta e restituito come risultato di errore. Un errore di rete o JSON non valido viene catturato dal `try/except` (Python) o `try/catch` (TypeScript) circostante e viene anche restituito come risultato di errore. In entrambi i casi l'handler restituisce normalmente e il ciclo dell'agente continua.
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## Restituire immagini e risorse
441
442L'array `content` in un risultato dello strumento accetta blocchi `text`, `image` e `resource`. Puoi mischiarli nella stessa risposta.
443
444### Immagini
445
446Un blocco immagine trasporta i byte dell'immagine inline, codificati come base64. Non c'è campo URL. Per restituire un'immagine che si trova in un URL, recuperala nell'handler, leggi i byte della risposta e codificali in base64 prima di restituire. Il risultato viene elaborato come input visivo.
447
448| Campo | Tipo | Note |
449| :--------- | :-------- | :-------------------------------------------------------------------------------------- |
450| `type` | `"image"` | |
451| `data` | `string` | Byte codificati in base64. Solo base64 grezzo, nessun prefisso `data:image/...;base64,` |
452| `mimeType` | `string` | Obbligatorio. Ad esempio `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### Risorse
508
509Un blocco risorsa incorpora un pezzo di contenuto identificato da un URI. L'URI è un'etichetta per Claude da riferire; il contenuto effettivo si trova nel campo `text` o `blob` del blocco. Usa questo quando il tuo strumento produce qualcosa che ha senso affrontare per nome in seguito, come un file generato o un record da un sistema esterno.
510
511| Campo | Tipo | Note |
512| :------------------ | :----------- | :--------------------------------------------------------------- |
513| `type` | `"resource"` | |
514| `resource.uri` | `string` | Identificatore per il contenuto. Qualsiasi schema URI |
515| `resource.text` | `string` | Il contenuto, se è testo. Fornisci questo o `blob`, non entrambi |
516| `resource.blob` | `string` | Il contenuto codificato in base64, se è binario |
517| `resource.mimeType` | `string` | Opzionale |
518
519Questo esempio mostra un blocco risorsa restituito dall'interno di un handler dello strumento. L'URI `file:///tmp/report.md` è un'etichetta che Claude può riferire in seguito; l'SDK non legge da quel percorso.
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
553Queste forme di blocco provengono dal tipo MCP `CallToolResult`. Vedi la [specifica MCP](https://modelcontextprotocol.io/specification/2025-06-18/server/tools#tool-result) per la definizione completa.
554
555## Restituire dati strutturati
556
557`structuredContent` è un oggetto JSON opzionale sul risultato, separato dall'array `content`. Usalo per restituire valori grezzi che Claude può leggere come campi esatti invece di analizzarli da una stringa di testo o da un'immagine.
558
559Quando `structuredContent` è impostato, Claude riceve il JSON più eventuali blocchi di immagine o risorsa da `content`. I blocchi di testo in `content` non vengono inoltrati, poiché si presume che duplichino i dati strutturati. L'esempio di seguito renderizza un grafico come blocco immagine e restituisce i punti dati dietro di esso in `structuredContent` dallo stesso handler.
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 Il decoratore Python `@tool` inoltro solo `content` e `is_error` dal dict di ritorno dell'handler. Per restituire `structuredContent` da Python, esegui un [server MCP standalone](/it/agent-sdk/mcp) invece di un server SDK in-process.
580</Note>
581
582## Esempio: convertitore di unità
583
584Questo strumento converte valori tra unità di lunghezza, temperatura e peso. Un utente può chiedere "converti 100 chilometri in miglia" o "quanto è 72°F in Celsius", e Claude sceglie il tipo di unità e le unità corrette dalla richiesta.
585
586Dimostra due pattern:
587
588* **Schemi enum:** `unit_type` è vincolato a un insieme fisso di valori. In TypeScript, usa `z.enum()`. In Python, lo schema dict non supporta enum, quindi è richiesto il dict JSON Schema completo.
589* **Gestione dell'input non supportato:** quando una coppia di conversione non viene trovata, l'handler restituisce `isError: true` in modo che Claude possa dire all'utente cosa è andato storto invece di trattare un fallimento come un risultato normale.
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
748Una volta definito il server, passalo a `query` nello stesso modo dell'esempio meteo. Questo esempio invia tre prompt diversi in un ciclo per mostrare lo stesso strumento che gestisce diversi tipi di unità. Per ogni risposta, ispeziona gli oggetti `AssistantMessage` (che contengono le chiamate dello strumento che Claude ha fatto durante quel turno) e stampa ogni `ToolUseBlock` prima di stampare il testo finale di `ResultMessage`. Questo ti permette di vedere quando Claude sta usando lo strumento rispetto a rispondere dalla sua stessa conoscenza.
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## Passaggi successivi
819
820Gli strumenti personalizzati avvolgono funzioni asincrone in un'interfaccia standard. Puoi mescolare i pattern su questa pagina nello stesso server: un singolo server può contenere uno strumento di database, uno strumento di gateway API e un renderer di immagini uno accanto all'altro.
821
822Da qui:
823
824* Se il tuo server cresce fino a dozzine di strumenti, vedi [tool search](/it/agent-sdk/tool-search) per differire il caricamento fino a quando Claude ne ha bisogno.
825* Per connettersi a server MCP esterni (filesystem, GitHub, Slack) invece di costruire il tuo, vedi [Connetti server MCP](/it/agent-sdk/mcp).
826* Per controllare quali strumenti vengono eseguiti automaticamente rispetto a quelli che richiedono approvazione, vedi [Configurare le autorizzazioni](/it/agent-sdk/permissions).
827
828## Documentazione correlata
829
830* [Riferimento SDK TypeScript](/it/agent-sdk/typescript)
831* [Riferimento SDK Python](/it/agent-sdk/python)
832* [Documentazione MCP](https://modelcontextprotocol.io)
833* [Panoramica SDK](/it/agent-sdk/overview)