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# Donner à Claude des outils personnalisés
6
7> Définissez des outils personnalisés avec le serveur MCP en processus du SDK Agent pour que Claude puisse appeler vos fonctions, accéder à vos API et effectuer des opérations spécifiques au domaine.
8
9Les outils personnalisés étendent le SDK Agent en vous permettant de définir vos propres fonctions que Claude peut appeler lors d'une conversation. En utilisant le serveur MCP en processus du SDK, vous pouvez donner à Claude accès aux bases de données, aux API externes, à la logique spécifique au domaine ou à toute autre capacité dont votre application a besoin.
10
11Ce guide couvre comment définir des outils avec des schémas d'entrée et des gestionnaires, les regrouper dans un serveur MCP, les transmettre à `query`, et contrôler les outils auxquels Claude peut accéder. Il couvre également la gestion des erreurs, les annotations d'outils et le retour de contenu non textuel comme les images.
12
13## Référence rapide
14
15| Si vous voulez... | Faites ceci |
16| :----------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
17| Définir un outil | Utilisez [`@tool`](/fr/agent-sdk/python#tool) (Python) ou [`tool()`](/fr/agent-sdk/typescript#tool) (TypeScript) avec un nom, une description, un schéma et un gestionnaire. Voir [Créer un outil personnalisé](#create-a-custom-tool). |
18| Enregistrer un outil auprès de Claude | Enveloppez dans `create_sdk_mcp_server` / `createSdkMcpServer` et transmettez à `mcpServers` dans `query()`. Voir [Appeler un outil personnalisé](#call-a-custom-tool). |
19| Pré-approuver un outil | Ajoutez à vos outils autorisés. Voir [Configurer les outils autorisés](#configure-allowed-tools). |
20| Supprimer un outil intégré du contexte de Claude | Transmettez un tableau `tools` listant uniquement les outils intégrés que vous souhaitez. Voir [Configurer les outils autorisés](#configure-allowed-tools). |
21| Laisser Claude appeler les outils en parallèle | Définissez `readOnlyHint: true` sur les outils sans effets secondaires. Voir [Ajouter des annotations d'outils](#add-tool-annotations). |
22| Gérer les erreurs sans arrêter la boucle | Retournez `isError: true` au lieu de lever une exception. Voir [Gérer les erreurs](#handle-errors). |
23| Retourner des images ou des fichiers | Utilisez des blocs `image` ou `resource` dans le tableau de contenu. Voir [Retourner des images et des ressources](#return-images-and-resources). |
24| Retourner un résultat JSON lisible par machine | Définissez `structuredContent` sur le résultat. Voir [Retourner des données structurées](#return-structured-data). |
25| Adapter à de nombreux outils | Utilisez [la recherche d'outils](/fr/agent-sdk/tool-search) pour charger les outils à la demande. |
26
27## Créer un outil personnalisé
28
29Un outil est défini par quatre parties, transmises comme arguments à l'assistant [`tool()`](/fr/agent-sdk/typescript#tool) en TypeScript ou au décorateur [`@tool`](/fr/agent-sdk/python#tool) en Python :
30
31* **Nom :** un identifiant unique que Claude utilise pour appeler l'outil.
32* **Description :** ce que fait l'outil. Claude lit ceci pour décider quand l'appeler.
33* **Schéma d'entrée :** les arguments que Claude doit fournir. En TypeScript, c'est toujours un [schéma Zod](https://zod.dev/), et les `args` du gestionnaire sont typés automatiquement à partir de celui-ci. En Python, c'est un dictionnaire mappant les noms aux types, comme `{"latitude": float}`, que le SDK convertit en JSON Schema pour vous. Le décorateur Python accepte également un dictionnaire [JSON Schema](https://json-schema.org/understanding-json-schema/about) complet directement quand vous avez besoin d'énumérations, de plages, de champs optionnels ou d'objets imbriqués.
34* **Gestionnaire :** la fonction asynchrone qui s'exécute quand Claude appelle l'outil. Elle reçoit les arguments validés et doit retourner un objet avec :
35 * `content` (obligatoire) : un tableau de blocs de résultats, chacun avec un `type` de `"text"`, `"image"` ou `"resource"`. Voir [Retourner des images et des ressources](#return-images-and-resources) pour les blocs non textuels.
36 * `structuredContent` (optionnel) : un objet JSON contenant le résultat sous forme de données lisibles par machine, retourné aux côtés de `content`. Voir [Retourner des données structurées](#return-structured-data).
37 * `isError` (optionnel) : définissez sur `true` pour signaler un échec d'outil afin que Claude puisse y réagir. Voir [Gérer les erreurs](#handle-errors).
38
39Après avoir défini un outil, enveloppez-le dans un serveur avec [`createSdkMcpServer`](/fr/agent-sdk/typescript#createsdkmcpserver) (TypeScript) ou [`create_sdk_mcp_server`](/fr/agent-sdk/python#create_sdk_mcp_server) (Python). Le serveur s'exécute en processus à l'intérieur de votre application, pas comme un processus séparé.
40
41### Exemple d'outil météo
42
43Cet exemple définit un outil `get_temperature` et l'enveloppe dans un serveur MCP. Il configure uniquement l'outil ; pour le transmettre à `query` et l'exécuter, voir [Appeler un outil personnalisé](#call-a-custom-tool) ci-dessous.
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
125Voir la référence TypeScript [`tool()`](/fr/agent-sdk/typescript#tool) ou la référence Python [`@tool`](/fr/agent-sdk/python#tool) pour les détails complets des paramètres, y compris les formats de schéma JSON et la structure des valeurs de retour.
126
127<Tip>
128 Pour rendre un paramètre optionnel : en TypeScript, ajoutez `.default()` au champ Zod. En Python, le schéma dict traite chaque clé comme obligatoire, donc omettez le paramètre du schéma, mentionnez-le dans la chaîne de description, et lisez-le avec `args.get()` dans le gestionnaire. L'outil [`get_precipitation_chance` ci-dessous](#add-more-tools) montre les deux modèles.
129</Tip>
130
131### Appeler un outil personnalisé
132
133Transmettez le serveur MCP que vous avez créé à `query` via l'option `mcpServers`. La clé dans `mcpServers` devient le segment `{server_name}` dans le nom complètement qualifié de chaque outil : `mcp__{server_name}__{tool_name}`. Listez ce nom dans `allowedTools` afin que l'outil s'exécute sans invite de permission.
134
135Ces extraits réutilisent le `weatherServer` de l'[exemple ci-dessus](#weather-tool-example) pour demander à Claude quel est le temps dans un endroit spécifique.
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### Ajouter plus d'outils
180
181Un serveur contient autant d'outils que vous en listez dans son tableau `tools`. Avec plus d'un outil sur un serveur, vous pouvez lister chacun dans `allowedTools` individuellement ou utiliser le caractère générique `mcp__weather__*` pour couvrir tous les outils que le serveur expose.
182
183L'exemple ci-dessous ajoute un deuxième outil, `get_precipitation_chance`, au `weatherServer` de l'[exemple d'outil météo](#weather-tool-example) et le reconstruit avec les deux outils dans le tableau.
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
266Chaque outil dans ce tableau consomme de l'espace de fenêtre de contexte à chaque tour. Si vous définissez des dizaines d'outils, voir [la recherche d'outils](/fr/agent-sdk/tool-search) pour les charger à la demande à la place.
267
268### Ajouter des annotations d'outils
269
270Les [annotations d'outils](https://modelcontextprotocol.io/docs/concepts/tools#tool-annotations) sont des métadonnées optionnelles décrivant le comportement d'un outil. Transmettez-les comme cinquième argument à l'assistant `tool()` en TypeScript ou via l'argument de mot-clé `annotations` pour le décorateur `@tool` en Python. Tous les champs d'indice sont des booléens.
271
272| Champ | Par défaut | Signification |
273| :---------------- | :--------- | :--------------------------------------------------------------------------------------------------------------------------------- |
274| `readOnlyHint` | `false` | L'outil ne modifie pas son environnement. Contrôle si l'outil peut être appelé en parallèle avec d'autres outils en lecture seule. |
275| `destructiveHint` | `true` | L'outil peut effectuer des mises à jour destructrices. Informatif uniquement. |
276| `idempotentHint` | `false` | Les appels répétés avec les mêmes arguments n'ont aucun effet supplémentaire. Informatif uniquement. |
277| `openWorldHint` | `true` | L'outil atteint les systèmes en dehors de votre processus. Informatif uniquement. |
278
279Les annotations sont des métadonnées, pas une application. Un outil marqué `readOnlyHint: true` peut toujours écrire sur le disque si c'est ce que fait le gestionnaire. Gardez l'annotation exacte par rapport au gestionnaire.
280
281Cet exemple ajoute `readOnlyHint` à l'outil `get_temperature` de l'[exemple d'outil météo](#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
311Voir `ToolAnnotations` dans la référence [TypeScript](/fr/agent-sdk/typescript#toolannotations) ou [Python](/fr/agent-sdk/python#toolannotations).
312
313## Contrôler l'accès aux outils
314
315L'[exemple d'outil météo](#weather-tool-example) a enregistré un serveur et listé les outils dans `allowedTools`. Cette section couvre comment les noms d'outils sont construits et comment limiter l'accès quand vous avez plusieurs outils ou que vous souhaitez restreindre les outils intégrés.
316
317### Format du nom d'outil
318
319Quand les outils MCP sont exposés à Claude, leurs noms suivent un format spécifique :
320
321* Modèle : `mcp__{server_name}__{tool_name}`
322* Exemple : Un outil nommé `get_temperature` dans le serveur `weather` devient `mcp__weather__get_temperature`
323
324### Configurer les outils autorisés
325
326L'option `tools` et les listes autorisées/interdites opèrent sur des couches séparées. `tools` contrôle quels outils intégrés apparaissent dans le contexte de Claude. Les listes d'outils autorisés et interdits contrôlent si les appels sont approuvés ou refusés une fois que Claude tente de les faire.
327
328| Option | Couche | Effet |
329| :------------------------ | :------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
330| `tools: ["Read", "Grep"]` | Disponibilité | Seuls les outils intégrés listés sont dans le contexte de Claude. Les outils intégrés non listés sont supprimés. Les outils MCP ne sont pas affectés. |
331| `tools: []` | Disponibilité | Tous les outils intégrés sont supprimés. Claude ne peut utiliser que vos outils MCP. |
332| outils autorisés | Permission | Les outils listés s'exécutent sans invite de permission. Les outils non listés restent disponibles ; les appels passent par le [flux de permission](/fr/agent-sdk/permissions). |
333| outils interdits | Permission | Chaque appel à un outil listé est refusé. L'outil reste dans le contexte de Claude, donc Claude peut toujours tenter de l'utiliser avant que l'appel soit rejeté. |
334
335Pour limiter les outils intégrés que Claude peut utiliser, préférez `tools` aux outils interdits. Omettre un outil de `tools` le supprime du contexte afin que Claude ne le tente jamais ; le lister dans `disallowedTools` (Python : `disallowed_tools`) bloque l'appel mais laisse l'outil visible, donc Claude peut gaspiller un tour en le tentant. Voir [Configurer les permissions](/fr/agent-sdk/permissions) pour l'ordre d'évaluation complet.
336
337## Gérer les erreurs
338
339La façon dont votre gestionnaire signale les erreurs détermine si la boucle d'agent continue ou s'arrête :
340
341| Ce qui se passe | Résultat |
342| :---------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------- |
343| Le gestionnaire lève une exception non capturée | La boucle d'agent s'arrête. Claude ne voit jamais l'erreur, et l'appel `query` échoue. |
344| Le gestionnaire capture l'erreur et retourne `isError: true` (TS) / `"is_error": True` (Python) | La boucle d'agent continue. Claude voit l'erreur comme des données et peut réessayer, essayer un outil différent ou expliquer l'échec. |
345
346L'exemple ci-dessous capture deux types d'échecs à l'intérieur du gestionnaire au lieu de les laisser lever. Un statut HTTP non-200 est capturé de la réponse et retourné comme un résultat d'erreur. Une erreur réseau ou un JSON invalide est capturé par le `try/except` (Python) ou `try/catch` (TypeScript) environnant et est également retourné comme un résultat d'erreur. Dans les deux cas, le gestionnaire retourne normalement et la boucle d'agent continue.
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## Retourner des images et des ressources
441
442Le tableau `content` dans un résultat d'outil accepte les blocs `text`, `image` et `resource`. Vous pouvez les mélanger dans la même réponse.
443
444### Images
445
446Un bloc image porte les octets d'image en ligne, codés en base64. Il n'y a pas de champ URL. Pour retourner une image qui se trouve à une URL, récupérez-la dans le gestionnaire, lisez les octets de réponse et encodez-les en base64 avant de les retourner. Le résultat est traité comme une entrée visuelle.
447
448| Champ | Type | Notes |
449| :--------- | :-------- | :-------------------------------------------------------------------------------------- |
450| `type` | `"image"` | |
451| `data` | `string` | Octets codés en base64. Base64 brut uniquement, pas de préfixe `data:image/...;base64,` |
452| `mimeType` | `string` | Obligatoire. Par exemple `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### Ressources
508
509Un bloc ressource intègre un morceau de contenu identifié par un URI. L'URI est un libellé pour que Claude le référence ; le contenu réel se trouve dans le champ `text` ou `blob` du bloc. Utilisez ceci quand votre outil produit quelque chose qui a du sens à adresser par nom plus tard, comme un fichier généré ou un enregistrement d'un système externe.
510
511| Champ | Type | Notes |
512| :------------------ | :----------- | :-------------------------------------------------------------------- |
513| `type` | `"resource"` | |
514| `resource.uri` | `string` | Identifiant du contenu. N'importe quel schéma URI |
515| `resource.text` | `string` | Le contenu, s'il est textuel. Fournissez ceci ou `blob`, pas les deux |
516| `resource.blob` | `string` | Le contenu codé en base64, s'il est binaire |
517| `resource.mimeType` | `string` | Optionnel |
518
519Cet exemple montre un bloc ressource retourné de l'intérieur d'un gestionnaire d'outil. L'URI `file:///tmp/report.md` est un libellé que Claude peut référencer plus tard ; le SDK ne lit pas à partir de ce chemin.
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
553Ces formes de bloc proviennent du type MCP `CallToolResult`. Voir la [spécification MCP](https://modelcontextprotocol.io/specification/2025-06-18/server/tools#tool-result) pour la définition complète.
554
555## Retourner des données structurées
556
557`structuredContent` est un objet JSON optionnel sur le résultat, séparé du tableau `content`. Utilisez-le pour retourner des valeurs brutes que Claude peut lire comme des champs exacts au lieu de les analyser à partir d'une chaîne de texte ou d'une image.
558
559Quand `structuredContent` est défini, Claude reçoit le JSON plus tous les blocs image ou ressource de `content`. Les blocs de texte dans `content` ne sont pas transmis, car on suppose qu'ils dupliquent les données structurées. L'exemple ci-dessous rend un graphique sous forme de bloc image et retourne les points de données derrière lui dans `structuredContent` du même gestionnaire.
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 Le décorateur Python `@tool` transmet uniquement `content` et `is_error` du dictionnaire de retour du gestionnaire. Pour retourner `structuredContent` de Python, exécutez un [serveur MCP autonome](/fr/agent-sdk/mcp) à la place d'un serveur SDK en processus.
580</Note>
581
582## Exemple : convertisseur d'unités
583
584Cet outil convertit les valeurs entre les unités de longueur, température et poids. Un utilisateur peut demander « convertir 100 kilomètres en miles » ou « qu'est-ce que 72°F en Celsius », et Claude choisit le bon type d'unité et les unités de la demande.
585
586Il démontre deux modèles :
587
588* **Schémas d'énumération :** `unit_type` est limité à un ensemble fixe de valeurs. En TypeScript, utilisez `z.enum()`. En Python, le schéma dict ne supporte pas les énumérations, donc le dictionnaire JSON Schema complet est requis.
589* **Gestion des entrées non supportées :** quand une paire de conversion n'est pas trouvée, le gestionnaire retourne `isError: true` afin que Claude puisse dire à l'utilisateur ce qui s'est mal passé au lieu de traiter un échec comme un résultat 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
748Une fois le serveur défini, transmettez-le à `query` de la même manière que l'exemple météo. Cet exemple envoie trois invites différentes dans une boucle pour montrer le même outil gérant différents types d'unités. Pour chaque réponse, il inspecte les objets `AssistantMessage` (qui contiennent les appels d'outils que Claude a effectués pendant ce tour) et imprime chaque `ToolUseBlock` avant d'imprimer le texte final de `ResultMessage`. Cela vous permet de voir quand Claude utilise l'outil par rapport à répondre à partir de ses propres connaissances.
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## Étapes suivantes
819
820Les outils personnalisés enveloppent les fonctions asynchrones dans une interface standard. Vous pouvez mélanger les modèles de cette page dans le même serveur : un seul serveur peut contenir un outil de base de données, un outil de passerelle API et un moteur de rendu d'image côte à côte.
821
822À partir d'ici :
823
824* Si votre serveur grandit à des dizaines d'outils, voir [la recherche d'outils](/fr/agent-sdk/tool-search) pour différer le chargement jusqu'à ce que Claude en ait besoin.
825* Pour vous connecter à des serveurs MCP externes (système de fichiers, GitHub, Slack) au lieu de construire les vôtres, voir [Connecter les serveurs MCP](/fr/agent-sdk/mcp).
826* Pour contrôler quels outils s'exécutent automatiquement par rapport à ceux nécessitant une approbation, voir [Configurer les permissions](/fr/agent-sdk/permissions).
827
828## Documentation connexe
829
830* [Référence du SDK TypeScript](/fr/agent-sdk/typescript)
831* [Référence du SDK Python](/fr/agent-sdk/python)
832* [Documentation MCP](https://modelcontextprotocol.io)
833* [Aperçu du SDK](/fr/agent-sdk/overview)