docs: refresh translations for recent English changes (#3379)
This commit is contained in:
parent
d8b638304e
commit
c5d7d0b6d0
18 changed files with 3149 additions and 1047 deletions
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [2c79b6338e09b7ac, 7edc43b3fae11314, 1086e77ce561cd7f, a3f71823df5efc31, 9fc7109f72201cae, 7bf25983df655b66, 6330e1f4c6029683, 2f1749c8c133fa1c, b3530fcf4d11fd56, ebc33704fbd74262, cd0e9c933350390e]
|
||||
sections: [2c79b6338e09b7ac, 7edc43b3fae11314, 1086e77ce561cd7f, a3f71823df5efc31, 9fc7109f72201cae, d50fe7faead8cf68, 7bf25983df655b66, 6330e1f4c6029683, 2f1749c8c133fa1c, 8db7116fc8ddd0ee, ebc33704fbd74262, cd0e9c933350390e]
|
||||
tool: 1
|
||||
---
|
||||
# Le Server de bas niveau {#the-low-level-server}
|
||||
|
|
@ -116,6 +116,17 @@ Le bloc `_meta` est la marque d’identité du serveur : le SDK l’ajoute à ch
|
|||
|
||||
Le serveur ne compare jamais les deux champs. Le `Client` de ce SDK, si : renvoyez un `structured_content` qui ne satisfait pas le `output_schema` que vous avez déclaré et `call_tool` lève une `RuntimeError` qui commence par `Invalid structured content returned by tool search_books` puis cite l’échec de `jsonschema`. Promettre un schéma ne coûte rien ; le tenir vous incombe. Toute l’échelle des types de retour et des schémas est dans **[Sortie structurée](../servers/structured-output.md)**.
|
||||
|
||||
## Le dialecte est JSON Schema 2020-12 {#the-dialect-is-json-schema-2020-12}
|
||||
|
||||
`input_schema` et `output_schema` sont du JSON Schema, et la [spécification MCP](https://modelcontextprotocol.io/specification/latest/basic#json-schema-usage) fixe le dialecte : un schéma sans clé `$schema` est du **JSON Schema 2020-12**. Les schémas que génère `MCPServer` s’appuient sur cette valeur par défaut (Pydantic écrit du 2020-12 et omet la clé), et un dict écrit à la main y est tenu lui aussi ; tout le vocabulaire 2020-12 est donc disponible :
|
||||
|
||||
```python title="server.py" hl_lines="8 14-15"
|
||||
--8<-- "docs_src/lowlevel/tutorial007.py"
|
||||
```
|
||||
|
||||
* La racine de `input_schema` doit être `"type": "object"`. À côté, `oneOf`, `additionalProperties`, `anyOf`, `if`/`then`/`else`, `prefixItems`, `$defs` avec des `$ref` locaux et le reste des mots-clés 2020-12 parviennent au client exactement tels qu’écrits.
|
||||
* Aucune clé `$schema` n’est nécessaire. N’en ajoutez une que pour opter pour une version (draft) antérieure : le `Client` de ce SDK, qui valide `structured_content` par rapport au `output_schema` d’un outil, choisit son validateur d’après `$schema` et utilise 2020-12 en son absence.
|
||||
|
||||
## `_meta` : pour l’application, pas pour le modèle {#\_meta-for-the-application-not-the-model}
|
||||
|
||||
`content` est la partie de la réponse que lit le modèle. `structured_content` est la même réponse sous forme de données typées. `_meta` est le troisième canal : des données qui voyagent avec le résultat à destination de l’**application cliente**, sans faire partie de la réponse du tout.
|
||||
|
|
@ -167,7 +178,7 @@ Le constructeur couvre les méthodes que MCP définit. `add_request_handler` cou
|
|||
--8<-- "docs_src/lowlevel/tutorial006.py"
|
||||
```
|
||||
|
||||
* Le premier argument est la chaîne de la méthode. Les notifications ont un jumeau, `add_notification_handler`.
|
||||
* Le premier argument est la chaîne de la méthode. Les notifications ont un jumeau, `add_notification_handler`. Ses gestionnaires se déclenchent sur stdio et sur les connexions HTTP de la génération à poignée de main (handshake) ; sur le chemin Streamable HTTP en version `2026-07-28`, le POST de notification d’un client reçoit un accusé de réception `202` et n’est pas distribué, car cette révision ne définit aucune notification du client vers le serveur sur HTTP.
|
||||
* `params_type` est le modèle par rapport auquel les `params` entrants sont validés **avant** l’exécution de votre gestionnaire ; les méthodes personnalisées *ont* donc droit à la validation dont les outils sont privés. Dérivez de `RequestParams` pour que le champ `_meta` s’analyse comme celui de toute autre méthode.
|
||||
* Le gestionnaire renvoie un `BaseModel`, un `dict` ou `None`. Le SDK le sérialise dans le résultat JSON-RPC.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [6048b4f308edbb8c, 068bda0f21ee9c1b, c3e565b61acd75c5, c62422b159c6ed09, 47204fab253cc45c]
|
||||
sections: [6048b4f308edbb8c, 46056f318ef205e4, c3e565b61acd75c5, c62422b159c6ed09, 420968f514138f43]
|
||||
tool: 1
|
||||
---
|
||||
# Middleware {#middleware}
|
||||
|
|
@ -56,8 +56,12 @@ C’est tout l’intérêt. Le middleware enveloppe **chaque** message entrant :
|
|||
|
||||
* La mise en place de la connexion : `server/discover`, ou `initialize` et
|
||||
`notifications/initialized` sur une session historique.
|
||||
* Chaque requête et chaque notification. Pour une notification, `ctx.request_id is None`,
|
||||
`call_next(ctx)` renvoie `None`, et tout ce que vous renvoyez est ignoré.
|
||||
* Chaque requête et chaque notification qui atteint le serveur. Pour une notification,
|
||||
`ctx.request_id is None`, `call_next(ctx)` renvoie `None`, et tout ce que vous renvoyez est
|
||||
ignoré. (Sur le chemin Streamable HTTP en version `2026-07-28`, le POST de notification d’un
|
||||
client reçoit un accusé de réception `202` au niveau du transport et n’est jamais distribué ;
|
||||
il n’atteint donc pas non plus le middleware. Cette révision ne définit aucune notification du
|
||||
client vers le serveur sur HTTP.)
|
||||
* Même une méthode pour laquelle le serveur n’a pas de gestionnaire : `call_next` lève
|
||||
`MCPError(-32601, "Method not found")` *à travers* votre middleware en route vers le client.
|
||||
|
||||
|
|
@ -119,8 +123,9 @@ page : **[OpenTelemetry](../run/opentelemetry.md)**.
|
|||
|
||||
* Un middleware est `async (ctx, call_next) -> result`, passé via `MCPServer(middleware=[...])`
|
||||
(ou ajouté à `mcp.middleware`), et ajouté à `server.middleware` sur le `Server` bas niveau.
|
||||
* Il enveloppe **chaque** message entrant (`server/discover`, `initialize`, requêtes,
|
||||
notifications, méthodes inconnues) et s’exécute de l’extérieur vers l’intérieur.
|
||||
* Il enveloppe **chaque** message entrant qui atteint le serveur (`server/discover`,
|
||||
`initialize`, requêtes, notifications, méthodes inconnues) et s’exécute de l’extérieur vers
|
||||
l’intérieur.
|
||||
* `ctx.request_id is None` est ce qui distingue une notification d’une requête.
|
||||
* Levez une exception au lieu d’appeler `call_next` pour refuser un message ; la connexion
|
||||
survit.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [ebef1e7a0df854f4, a4c687d3d627d516, 8e79141fc2985342, b345dd05b9c3c7ab, 80ce41579825a6fa, 5f0fa90494de8f65, 83d10514eaa62fa5, 9190555aa39a5d28, 84a4c9d8bf14dddb, 927d71cf40b58c30]
|
||||
sections: [ebef1e7a0df854f4, 8355cfaf1f76c9d5, 8e79141fc2985342, 46bdb07c7537e8a5, 80ce41579825a6fa, 5f0fa90494de8f65, 83d10514eaa62fa5, 9190555aa39a5d28, 84a4c9d8bf14dddb, 927d71cf40b58c30]
|
||||
tool: 1
|
||||
---
|
||||
# Le client {#the-client}
|
||||
|
|
@ -27,9 +27,10 @@ Le serveur en haut n’est là que pour vous donner quelque chose à quoi vous c
|
|||
|
||||
* Une instance de `MCPServer` (ou du `Server` bas niveau) : connexion **dans le processus**.
|
||||
* Une chaîne d’URL (`Client("http://localhost:8000/mcp")`) : Streamable HTTP, la voie de production.
|
||||
* Un **transport** : tout ce sur quoi vous pouvez faire `async with ... as (read, write)`, comme `stdio_client(...)` qui enveloppe un sous-processus.
|
||||
* Un `StdioServerParameters` : la commande à lancer en **sous-processus**, avec laquelle le client dialogue via son stdin et son stdout.
|
||||
* Un **transport** : tout ce sur quoi vous pouvez faire `async with ... as (read, write)`, comme `streamable_http_client(url, http_client=...)` autour de votre propre client HTTP.
|
||||
|
||||
Tout le reste de cette page est identique pour les trois. Les en-têtes, les sous-processus, les délais d’expiration et le protocole `Transport` ont leur propre page : **[Transports côté client](transports.md)**.
|
||||
Tout le reste de cette page est identique pour les quatre. Les en-têtes, les sous-processus, les délais d’expiration et le protocole `Transport` ont leur propre page : **[Transports côté client](transports.md)**.
|
||||
|
||||
### Ce que porte un client connecté {#whats-on-a-connected-client}
|
||||
|
||||
|
|
@ -85,7 +86,7 @@ Ce schéma est tout ce dont une interface a besoin pour afficher un formulaire d
|
|||
|
||||
`call_tool(name, arguments)` exécute l’outil et vous renvoie un `CallToolResult`.
|
||||
|
||||
```python title="client.py" hl_lines="26-33"
|
||||
```python title="client.py" hl_lines="27-34"
|
||||
--8<-- "docs_src/client/tutorial003.py"
|
||||
```
|
||||
|
||||
|
|
@ -117,7 +118,7 @@ Un outil qui lève une exception ne lève **rien** dans votre client. Il revient
|
|||
|
||||
!!! check
|
||||
Demandez `"Solaris"` à `lookup_book` (un titre qui n’est pas au catalogue) et la fonction lève
|
||||
`ValueError`. L’appel revient pourtant normalement :
|
||||
`ToolError`. L’appel revient pourtant normalement :
|
||||
|
||||
```python
|
||||
result.is_error # True
|
||||
|
|
@ -125,9 +126,10 @@ Un outil qui lève une exception ne lève **rien** dans votre client. Il revient
|
|||
result.structured_content # None
|
||||
```
|
||||
|
||||
Le message de l’exception a atterri dans `content`, où le **modèle** peut le lire et réessayer. C’est
|
||||
délibéré : une erreur d’outil fait partie de la conversation, ce n’est pas un plantage. Regardez toujours `is_error`
|
||||
avant de faire confiance à `structured_content`.
|
||||
Le message de la `ToolError` a atterri dans `content`, où le **modèle** peut le lire et réessayer. C’est
|
||||
délibéré : une erreur d’outil fait partie de la conversation, ce n’est pas un plantage. (Si l’outil avait planté avec
|
||||
une autre exception, `content` dirait seulement `Error executing tool lookup_book`.) Regardez toujours
|
||||
`is_error` avant de faire confiance à `structured_content`.
|
||||
|
||||
!!! warning
|
||||
`is_error=True` couvre plus que vos propres `raise`. Demandez un outil que le serveur n’a même pas
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [9cac816674181eb0, 0700f337babcd4dd, 2bde0dd58cdf00f5, ff7401df479af877, 3d0832f39b0d7059, d4bf7e4479637768, 05e20c0a798860e7]
|
||||
sections: [9cac816674181eb0, 0700f337babcd4dd, 2bde0dd58cdf00f5, 40b4916d82eaf1d4, 3d0832f39b0d7059, dfa4446556badef0, 5bd93be2ab2ecb9c]
|
||||
tool: 1
|
||||
---
|
||||
# Transports côté client {#client-transports}
|
||||
|
|
@ -87,15 +87,15 @@ ou passez un `verify=ssl_context` explicite à votre `httpx2.AsyncClient`
|
|||
|
||||
Un serveur **stdio** est un sous-processus. Le client le lance, écrit du JSON-RPC sur son stdin et lit du JSON-RPC depuis son stdout. C’est ainsi qu’un hôte de bureau exécute un serveur sur votre machine : un hôte *est* ce code plus une interface utilisateur, et **[Se connecter à un véritable hôte](../get-started/real-host.md)** montre la même relation vue du côté de l’hôte, sous forme de fichier de configuration.
|
||||
|
||||
Décrivez le processus avec `StdioServerParameters`, transformez-le en transport avec `stdio_client`, et passez *cela* à `Client` :
|
||||
Décrivez le processus avec `StdioServerParameters` et passez-le à `Client` :
|
||||
|
||||
```python title="client.py" hl_lines="4-8 12"
|
||||
```python title="client.py" hl_lines="3-7 11"
|
||||
--8<-- "docs_src/client_transports/tutorial004.py"
|
||||
```
|
||||
|
||||
`Client` n’accepte pas l’objet de paramètres seul. `StdioServerParameters` est de la configuration ; `stdio_client(server)` est le transport qui sait lancer un processus à partir de celle-ci. Enveloppez toujours.
|
||||
Entrer dans le bloc lance le processus. En sortir arrête le sous-processus : fermeture de stdin, attente, arrêt forcé s’il traîne. Vous ne le nettoyez jamais vous-même.
|
||||
|
||||
Quitter le bloc `async with` arrête aussi le sous-processus : fermeture de stdin, attente, arrêt forcé s’il traîne. Vous ne le nettoyez jamais vous-même.
|
||||
Le stderr du processus enfant va vers le vôtre. Pour l’envoyer ailleurs, construisez le transport vous-même avec `stdio_client` (du module `mcp`) et passez-le à la place : `Client(stdio_client(server, errlog=log_file))`.
|
||||
|
||||
!!! warning
|
||||
Le processus enfant n’hérite **pas** de votre environnement. Il reçoit une liste d’autorisation minimale (`HOME`, `LOGNAME`,
|
||||
|
|
@ -113,16 +113,16 @@ Quitter le bloc `async with` arrête aussi le sous-processus : fermeture de stdi
|
|||
|
||||
Pour `Client`, tout ce qui précède est une seule et même chose.
|
||||
|
||||
Un **transport** est n’importe quel gestionnaire de contexte asynchrone qui produit une paire `(read, write)` de flux de messages : formellement, le protocole `Transport` de `mcp.client`. `Client` résout son argument selon son type : un objet serveur se connecte dans le processus, une `str` devient `streamable_http_client(url)`, et tout le reste est ouvert directement comme transport. C’est cette dernière règle qui explique pourquoi `stdio_client(...)`, `streamable_http_client(...)` et `sse_client(...)` s’insèrent tous au même emplacement, et pourquoi vous pouvez écrire le vôtre.
|
||||
Un **transport** est n’importe quel gestionnaire de contexte asynchrone qui produit une paire `(read, write)` de flux de messages : formellement, le protocole `Transport` de `mcp.client`. `Client` résout son argument selon son type : un objet serveur se connecte dans le processus, une `str` devient `streamable_http_client(url)`, un `StdioServerParameters` devient `stdio_client(params)`, et tout le reste est ouvert directement comme transport. C’est cette dernière règle qui explique pourquoi `stdio_client(...)`, `streamable_http_client(...)` et `sse_client(...)` s’insèrent tous au même emplacement, et pourquoi vous pouvez écrire le vôtre.
|
||||
|
||||
## Récapitulatif {#recap}
|
||||
|
||||
* `Client(mcp)` (l’objet serveur) se connecte en mémoire. Utilisez-le pour les tests et pour l’intégration.
|
||||
* `Client("http://.../mcp")` (une URL) se connecte via Streamable HTTP, le transport de production.
|
||||
* Les en-têtes, l’authentification, les proxys et les délais d’expiration vont sur un `httpx2.AsyncClient` que vous passez à `streamable_http_client(url, http_client=...)`. Il n’y a pas de mot-clé `headers=`.
|
||||
* stdio s’écrit `Client(stdio_client(StdioServerParameters(...)))`, jamais l’objet de paramètres seul.
|
||||
* stdio s’écrit `Client(StdioServerParameters(...))`. Ne l’enveloppez vous-même dans `stdio_client(...)` que pour rediriger le stderr du processus enfant.
|
||||
* Le sous-processus reçoit un environnement sous liste d’autorisation, pas le vôtre ; `env=` s’y ajoute.
|
||||
* Un transport est tout ce sur quoi vous pouvez faire `async with x as (read, write)`. `Client` transmet directement à ce protocole tout ce qui n’est ni un objet serveur ni une URL.
|
||||
* Un transport est tout ce sur quoi vous pouvez faire `async with x as (read, write)`. `Client` transmet directement à ce protocole tout ce qui n’est ni un objet serveur, ni une URL, ni un `StdioServerParameters`.
|
||||
* Construire un `Client` choisit le transport. `async with` l’ouvre.
|
||||
|
||||
Une fois le transport ouvert, les deux côtés doivent s’accorder sur une version du protocole. En temps normal, vous n’y pensez jamais ; le jour où vous devez y penser, la page à consulter est **[Versions du protocole](../protocol-versions.md)**.
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
---
|
||||
translation:
|
||||
sections: [20541a40dbdd5980, 01262a123ad9501d, 429db5b574a2ac08, 56b2d49da412cb28, 6a1717123fe4513c]
|
||||
sections: [490237e61c3a7a44, 01262a123ad9501d, 429db5b574a2ac08, e2d0d273fbd2d74b, 64ab0331e868f3d4, 6c8878ce2d1f6d56, 4068f23e371bf0b3, eaef75b8725bc931]
|
||||
tool: 1
|
||||
---
|
||||
# Fonctionnalités obsolètes {#deprecated-features}
|
||||
|
||||
La spécification 2026-07-28 retire cinq éléments. Le SDK les implémente toujours tous, et chacun d’eux porte désormais un **avertissement d’obsolescence**.
|
||||
La spécification 2026-07-28 retire cinq éléments. Le SDK les implémente toujours tous, et chacun d’eux porte désormais un **avertissement d’obsolescence**. Un utilitaire du SDK est obsolète pour des raisons qui lui sont propres ; il figure [à la fin](#deprecated-sdk-helpers).
|
||||
|
||||
Le tableau ci-dessous nomme chaque fonctionnalité obsolète, la raison de sa disparition et le remplacement sur lequel vous appuyer.
|
||||
|
||||
|
|
@ -56,6 +56,55 @@ MCPDeprecationWarning: The logging capability is deprecated as of 2026-07-28 (SE
|
|||
une connexion `mode="legacy"` dont le client a enregistré la fonction de rappel
|
||||
correspondante.
|
||||
|
||||
## `ping` sur une session historique {#ping-on-a-legacy-session}
|
||||
|
||||
Un **ping** est une requête vide que chaque côté peut envoyer pour vérifier que l’autre répond toujours. La spécification 2026-07-28 le supprime ([SEP-2575](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575)) : chaque requête qu’envoie un client moderne prouve déjà que le serveur est là, et un serveur moderne n’a aucun canal pour en envoyer un. Les deux méthodes du SDK fonctionnent toujours sur une session de la génération poignée de main (handshake). Depuis le client :
|
||||
|
||||
```python
|
||||
async def main() -> None:
|
||||
async with Client("http://localhost:8000/mcp", mode="legacy") as client:
|
||||
await client.send_ping() # warns; returns an EmptyResult
|
||||
```
|
||||
|
||||
Et depuis le serveur, dans n’importe quel gestionnaire (handler) :
|
||||
|
||||
```python
|
||||
@mcp.tool()
|
||||
async def check_client(ctx: Context) -> str:
|
||||
"""A tool that still pings the client mid-call."""
|
||||
await ctx.session.send_ping() # no warning; an EmptyResult while the client is connected
|
||||
return "client answered"
|
||||
```
|
||||
|
||||
* `client.send_ping()` avertit avec `MCPDeprecationWarning` à chaque appel. Sur une connexion par défaut (`2026-07-28`), le serveur répond à la place `MCPError: Method not found`.
|
||||
* `ctx.session.send_ping()` ne porte aucun avertissement. Sur une connexion moderne, elle lève la même erreur d’absence de canal de retour (back-channel) que toute autre requête à l’initiative du serveur.
|
||||
* Aucun des deux côtés n’enregistre quoi que ce soit pour répondre à un ping.
|
||||
|
||||
## Notifications de changement des racines {#roots-change-notifications}
|
||||
|
||||
Un client de génération 2025 qui a déclaré la capacité des racines peut signaler au serveur que les dossiers de son espace de travail ont changé en envoyant `notifications/roots/list_changed` ; le serveur répond en redemandant `roots/list`. La spécification 2026-07-28 supprime la notification avec le reste du flux des racines en mode push. Côté client, c’est le passage de `list_roots_callback=` (**[Fonctions de rappel du client](client/callbacks.md)**) qui déclare `"roots": {"listChanged": true}`, et un seul appel tient cette promesse :
|
||||
|
||||
```python
|
||||
async def open_folder(client: Client, uri: str, name: str) -> None:
|
||||
"""The user opened another folder: expose it through the roots callback, then tell the server."""
|
||||
workspace.append(Root(uri=FileUrl(uri), name=name))
|
||||
await client.send_roots_list_changed()
|
||||
```
|
||||
|
||||
Côté serveur, c’est le `Server` de bas niveau qui accueille le gestionnaire de réception :
|
||||
|
||||
```python
|
||||
async def roots_changed(ctx: ServerRequestContext, params: NotificationParams | None) -> None:
|
||||
"""The client's roots changed: ask for the new list."""
|
||||
roots = (await ctx.session.list_roots()).roots
|
||||
|
||||
|
||||
server = Server("Bookshop", on_roots_list_changed=roots_changed)
|
||||
```
|
||||
|
||||
* `workspace` est la liste que renvoie votre `list_roots_callback`. `client.send_roots_list_changed()` avertit, et il lui faut un client `mode="legacy"` : sur une connexion moderne, la notification est abandonnée silencieusement. Gardez ensuite la session ouverte, car le `roots/list` de suivi du serveur arrive dessus.
|
||||
* `MCPServer` n’a aucun hook pour la notification. Sur le `Server` de bas niveau, `on_roots_list_changed=` enregistre le gestionnaire (obsolète lui aussi, il avertit à la construction). La notification ne porte aucune charge utile, donc le gestionnaire appelle `ctx.session.list_roots()` pour obtenir la nouvelle liste.
|
||||
|
||||
## Faire taire l’avertissement {#silencing-the-warning}
|
||||
|
||||
Dans du nouveau code, ne le faites pas.
|
||||
|
|
@ -76,22 +125,33 @@ C’est toute l’API. Il n’y a pas d’interrupteur par méthode, et vous n
|
|||
Inversez le filtre et vous obtenez gratuitement un test de non-régression. Ajoutez
|
||||
`"error::mcp.MCPDeprecationWarning"` au réglage `filterwarnings` de votre configuration
|
||||
pytest et l’appel obsolète **lève une exception** au lieu d’avertir. Un outil nommé
|
||||
`old_log` qui appelle encore `ctx.info()` cesse de passer et se met à signaler :
|
||||
`old_log` qui appelle encore `ctx.info()` cesse de passer : l’appel revient avec
|
||||
`is_error=True` et `Error executing tool old_log`, et le journal du serveur capturé
|
||||
désigne le coupable :
|
||||
|
||||
```text
|
||||
Error executing tool old_log: The logging capability is deprecated as of 2026-07-28 (SEP-2577).
|
||||
mcp.shared.exceptions.MCPDeprecationWarning: The logging capability is deprecated as of 2026-07-28 (SEP-2577).
|
||||
```
|
||||
|
||||
Une ligne de configuration pytest, et un appel obsolète ne peut plus jamais se glisser
|
||||
de nouveau dans votre base de code sans faire échouer un test.
|
||||
|
||||
## Utilitaires du SDK obsolètes {#deprecated-sdk-helpers}
|
||||
|
||||
Il ne s’agit pas de changements de la spécification, seulement de rouages internes du SDK qui ont un meilleur remplacement. Ils avertissent avec le même `MCPDeprecationWarning` et seront supprimés dans la version 3.0.
|
||||
|
||||
| Obsolète | Ce que vous faites à la place |
|
||||
|---|---|
|
||||
| `FuncMetadata.call_fn_with_arg_validation()` | `FuncMetadata.validate_arguments()` puis `FuncMetadata.call_fn()`. Seul du code qui pilote directement `FuncMetadata` (une sous-classe personnalisée de `Tool`, par exemple) l’a jamais appelée. |
|
||||
|
||||
## Récapitulatif {#recap}
|
||||
|
||||
* La spécification 2026-07-28 rend obsolètes les **racines**, l’**échantillonnage** à l’initiative du serveur et la **journalisation** par le protocole (toutes via la [SEP-2577](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577)), restreint la **progression** au sens serveur vers client et supprime **`ping`**.
|
||||
* La colonne des remplacements vous oriente : **[Requêtes à plusieurs allers-retours](handlers/multi-round-trip.md)** pour l’échantillonnage et les racines, **[Journalisation](handlers/logging.md)** pour la journalisation, **[Progression](handlers/progress.md)** pour la progression. `ping` n’a besoin de rien du tout.
|
||||
* L’obsolescence est indicative : aucun changement sur la liaison, tout continue de fonctionner sur les sessions d’avant 2026, et vous obtenez un `MCPDeprecationWarning` visible (un `UserWarning`, donc actif par défaut).
|
||||
* L’échantillonnage et les racines ont en plus besoin d’un canal de retour (back-channel) qu’une session 2026-07-28 n’a pas. Sur une connexion moderne, ils avertissent puis lèvent une exception.
|
||||
* L’échantillonnage et les racines ont en plus besoin d’un canal de retour qu’une session 2026-07-28 n’a pas. Sur une connexion moderne, ils avertissent puis lèvent une exception.
|
||||
* `warnings.filterwarnings("ignore", category=MCPDeprecationWarning)` fait taire toute la catégorie ; `"error::mcp.MCPDeprecationWarning"` dans pytest la transforme en échec de test.
|
||||
* Un utilitaire du SDK, `FuncMetadata.call_fn_with_arg_validation()`, est obsolète séparément, pour suppression dans la version 3.0.
|
||||
* Aucun nouveau code ne devrait s’appuyer sur l’une de ces fonctionnalités.
|
||||
|
||||
Toutes les autres pages de cette documentation enseignent l’API actuelle.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [3c4f2f06b4e978b6, 22520eecae3d1961, f4e1709db18d635a, 2eb57992049671d9, 1ba83e9af37cc1b4, 4822586344b08d9e, 1c93afef72478992, b6b448f9eddd51dc, fe55370fd931815b]
|
||||
sections: [3c4f2f06b4e978b6, 51ea5fbcb0e93563, 32d8808606ffdae0, 2eb57992049671d9, 1ba83e9af37cc1b4, 4822586344b08d9e, 1c93afef72478992, b6b448f9eddd51dc, fe55370fd931815b]
|
||||
tool: 1
|
||||
---
|
||||
# Se connecter à un véritable hôte {#connect-to-a-real-host}
|
||||
|
|
@ -11,7 +11,7 @@ Se connecter à un hôte se résume donc à un seul geste : vous lui indiquez **
|
|||
|
||||
## Un serveur, tous les hôtes {#one-server-every-host}
|
||||
|
||||
```python title="server.py" hl_lines="3 33-34"
|
||||
```python title="server.py" hl_lines="4 34-35"
|
||||
--8<-- "docs_src/real_host/tutorial001.py"
|
||||
```
|
||||
|
||||
|
|
@ -51,7 +51,7 @@ C’est aussi la commande que `mcp install` écrit pour vous dans la configurati
|
|||
|
||||
Et un hôte n’est rien de plus qu’une application contenant un client MCP ; votre propre
|
||||
code Python peut donc jouer le rôle de l’hôte : **[Transports du client](../client/transports.md)**
|
||||
lance ce même fichier comme sous-processus avec `stdio_client(...)`, et **[Tests](testing.md)**
|
||||
lance ce même fichier comme sous-processus avec `Client(StdioServerParameters(...))`, et **[Tests](testing.md)**
|
||||
s’y connecte en mémoire, sans aucun processus.
|
||||
|
||||
## Claude Desktop {#claude-desktop}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: ['4926721070127497', c52a1de2b6b32f40, 2e410b412c25f314, 627195f7159e24ef]
|
||||
sections: ['4926721070127497', c52a1de2b6b32f40, 8e792bf8c7489ec6, 627195f7159e24ef]
|
||||
tool: 1
|
||||
---
|
||||
# Tests {#testing}
|
||||
|
|
@ -85,9 +85,10 @@ Et voilà. Vous pouvez maintenant étendre vos tests pour couvrir davantage de s
|
|||
Deux choses différentes peuvent mal tourner, et cet indicateur n’en concerne qu’une seule.
|
||||
|
||||
Une exception dans l’un de **vos outils** n’est pas un échec du protocole. Elle devient un résultat
|
||||
normal avec `is_error=True`, et le modèle lit le message. `raise_exceptions` n’y change rien : avec
|
||||
ou sans lui, `call_tool` renvoie le même résultat `is_error=True`. Une page entière y est
|
||||
consacrée : **[Gérer les erreurs](../servers/handling-errors.md)**.
|
||||
normal avec `is_error=True` (et s’il s’agissait d’une `ToolError`, le modèle lit votre message).
|
||||
`raise_exceptions` n’y change rien : avec ou sans lui, `call_tool` renvoie le même résultat
|
||||
`is_error=True`. Une page entière y est consacrée :
|
||||
**[Gérer les erreurs](../servers/handling-errors.md)**.
|
||||
|
||||
Un échec **en dehors** du corps d’un outil est différent. Sur la connexion que vous donne
|
||||
`Client(mcp)`, le serveur le neutralise en un `"Internal server error"` générique avant que le
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [335ca2a0b266f003, d1ad562d3fe87bc0, 0bb1396c86daeba4, d1cb1235bb9ee267, 833179c09d239c83, e5d6dec2d2e655e8]
|
||||
sections: [335ca2a0b266f003, d1ad562d3fe87bc0, 25b49f89c9e6f9d0, d1cb1235bb9ee267, 833179c09d239c83, e5d6dec2d2e655e8]
|
||||
tool: 1
|
||||
---
|
||||
# Élicitation {#elicitation}
|
||||
|
|
@ -89,7 +89,8 @@ Ce schéma, c’est le formulaire. `Field(description=...)` est le libellé ; un
|
|||
!!! warning
|
||||
Un schéma d’élicitation n’est pas aussi expressif que le schéma d’entrée d’un outil. Des champs plats et primitifs
|
||||
uniquement : `str`, `int`, `float`, `bool`, ou un `Literal` de chaînes (il devient un `enum`).
|
||||
Mettez un modèle dans le modèle et `ctx.elicit` lève une exception avant que quoi que ce soit ne soit envoyé au client :
|
||||
Mettez un modèle dans le modèle et `ctx.elicit` lève une exception avant que quoi que ce soit ne soit envoyé au client.
|
||||
L’appel d’outil échoue avec `Error executing tool <name>`, et le journal de votre serveur en donne la raison :
|
||||
|
||||
```text
|
||||
TypeError: Elicitation schema field 'address' rendered as {'$ref': '#/$defs/Address'}, which is not a valid PrimitiveSchemaDefinition
|
||||
|
|
@ -112,8 +113,8 @@ Un refus n’est pas une erreur. L’outil décide de ce que signifie décliner
|
|||
|
||||
!!! tip
|
||||
La réponse est validée par rapport à votre modèle avant que votre code ne la voie. Un client qui envoie
|
||||
`"maybe"` pour un `bool` ne corrompt pas votre réservation : l’appel échoue avec une
|
||||
erreur de non-conformité au schéma, votre `if` ne s’exécute jamais.
|
||||
`"maybe"` pour un `bool` ne corrompt pas votre réservation : `ctx.elicit` lève `ValueError`, l’appel
|
||||
échoue, et votre `if` ne s’exécute jamais.
|
||||
|
||||
## Envoyer l’utilisateur vers une URL {#send-the-user-to-a-url}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [c93a3e1aefd77955, 7851abd5ec54393b, f49d1ca2f330f9cd, c03764bd9dfeef7b, 4a0391691a674ae4, 2df5cd279eabf9f5]
|
||||
sections: [c93a3e1aefd77955, 7851abd5ec54393b, f49d1ca2f330f9cd, 4cc0a00347c3f534, 4a0391691a674ae4, 2df5cd279eabf9f5]
|
||||
tool: 1
|
||||
---
|
||||
# Journalisation {#logging}
|
||||
|
|
@ -54,6 +54,8 @@ La valeur par défaut est `"INFO"`.
|
|||
|
||||
`logging.basicConfig()` ne remplace jamais des gestionnaires de journalisation qui existent déjà. Si vous configurez la journalisation vous-même avant de créer le serveur, votre configuration l’emporte.
|
||||
|
||||
Vous n’avez pas non plus besoin d’un `try`/`except` dans chaque gestionnaire simplement pour consigner les échecs. Lorsqu’une fonction d’outil ou de ressource lève une exception, le SDK la journalise pour vous. **[Gérer les erreurs](../servers/handling-errors.md#any-other-exception)** explique ce qui est journalisé et à quel niveau.
|
||||
|
||||
## Essayer {#try-it}
|
||||
|
||||
Lancez le serveur avec le MCP Inspector :
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [fea8d769ff9edeba, ce8e2ad42f29ef71, 0d705efb19cf99c2, 7a53ead3e704a7f0, 9adc400e8c88e854, 318893ad8e2e9924, 6b63ab96b34476c0]
|
||||
sections: [fea8d769ff9edeba, ce8e2ad42f29ef71, 0d705efb19cf99c2, 2fd7cf825e6d2b2c, 9adc400e8c88e854, 318893ad8e2e9924, 6b63ab96b34476c0]
|
||||
tool: 1
|
||||
---
|
||||
# Exécuter votre serveur {#running-your-server}
|
||||
|
|
@ -70,9 +70,9 @@ Chaque transport a ses propres arguments nommés, tous sur `run()` :
|
|||
|
||||
* `host` / `port` : où écouter. Valeurs par défaut `127.0.0.1` et `8000`.
|
||||
* `streamable_http_path` : où se trouve le point de terminaison MCP. Valeur par défaut `/mcp`.
|
||||
* `json_response=True` : répondre à chaque POST par un corps JSON unique au lieu d’un flux SSE. Ce corps a de la place pour la réponse et rien d’autre : un outil qui rappelle le client en cours de requête (`ctx.elicit()`, échantillonnage) lève donc `NoBackChannelError` sur ce tronçon, et les notifications liées à l’appel en cours (la progression de `ctx.report_progress()`, les messages de journal par appel) sont abandonnées ; le flux `GET` autonome transporte toujours celles qui n’y sont pas liées.
|
||||
* `json_response=True` : répondre à chaque POST par un corps JSON unique au lieu d’un flux SSE. Ce corps a de la place pour la réponse et rien d’autre : un outil qui rappelle le client en cours de requête (`ctx.elicit()`, échantillonnage (sampling)) lève donc `NoBackChannelError` sur ce tronçon, et les notifications liées à l’appel en cours (la progression de `ctx.report_progress()`, les messages de journal par appel) sont abandonnées ; le flux `GET` autonome transporte toujours celles qui n’y sont pas liées.
|
||||
* `stateless_http=True` : un transport neuf par requête, sans suivi de session.
|
||||
* `max_request_body_size` : la taille maximale acceptée pour le corps d’un POST, en octets. Vaut 4 Mio par défaut ; les requêtes plus grandes
|
||||
* `max_request_body_size` : taille maximale acceptée pour le corps d’une requête, en octets. Vaut 4 Mio par défaut ; les requêtes plus grandes
|
||||
reçoivent un HTTP 413 avant toute analyse ou création de session. Ne l’augmentez que lorsque des messages MCP légitimes
|
||||
dépassent cette taille.
|
||||
* `event_store`, `retry_interval`, `transport_security` : reprise après coupure et protection contre le DNS rebinding. Ils peuvent attendre, jusqu’à ce que vous déployiez ailleurs que sur localhost ; **[Déployer et passer à l’échelle](deploy.md)** couvre `transport_security`.
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
---
|
||||
translation:
|
||||
sections: [e33d441f12d50535, 7099694c603e0f5f, c1df4cf9673433e6, c9cd294541422e6e, 6cec073617bfd037, efa92b8f99e908c8, 6a22a29e27fb4601]
|
||||
sections: [7be05607887e6853, e7375894888d9750, c36f73fc7e3af13b, 2fec2d7e129e62fe, 809b0e0a7c27295a, b4395a04d2a5d906, 1a436007f5f54779, c6b2078ed1e63ba5]
|
||||
tool: 1
|
||||
---
|
||||
# Gérer les erreurs {#handling-errors}
|
||||
|
||||
Un outil (tool) peut échouer de deux manières, et le SDK les traite très différemment.
|
||||
Un outil (tool) peut échouer de trois manières, et le SDK traite chacune différemment.
|
||||
|
||||
Levez une exception ordinaire et c’est le **modèle** qui la voit. Levez `MCPError` et c’est le **protocole** qui la voit.
|
||||
Levez `ToolError` et c’est le **modèle** qui voit votre message. Levez `MCPError` et c’est le **protocole** qui le voit. Levez quoi que ce soit d’autre et c’est un plantage : le modèle apprend seulement que l’appel a échoué, et votre journal reçoit le traceback.
|
||||
|
||||
Cette page vous aide à choisir.
|
||||
|
||||
|
|
@ -15,11 +15,11 @@ Cette page vous aide à choisir.
|
|||
|
||||
Prenez un outil qui effectue une recherche, et laissez cette recherche échouer :
|
||||
|
||||
```python title="server.py" hl_lines="11-12"
|
||||
```python title="server.py" hl_lines="2 12-13"
|
||||
--8<-- "docs_src/handling_errors/tutorial001.py"
|
||||
```
|
||||
|
||||
Ces deux lignes n’ont rien de spécifique à MCP. `get_author` lève une simple `ValueError`, comme le ferait n’importe quelle fonction Python.
|
||||
`ToolError`, qui vient de `mcp.server.mcpserver.exceptions`, est le moyen pour un outil de dire au modèle que quelque chose s’est mal passé.
|
||||
|
||||
Appelez-le avec un titre absent du catalogue et regardez le résultat :
|
||||
|
||||
|
|
@ -30,13 +30,15 @@ result.structured_content # None
|
|||
```
|
||||
|
||||
* La requête a **réussi**. Il y a un résultat ; rien n’a été levé côté appelant.
|
||||
* `is_error` vaut `True`, et le message de votre exception (préfixé du nom de l’outil) se trouve dans `content`, exactement là où le modèle lit.
|
||||
* `is_error` vaut `True`, et votre message (préfixé du nom de l’outil) se trouve dans `content`, exactement là où le modèle lit.
|
||||
* `structured_content` vaut `None`. Un appel en échec n’a aucune valeur de retour à structurer.
|
||||
|
||||
C’est une **erreur d’outil** (tool error), et c’est le comportement par défaut pour *toute* exception que lève votre outil. C’est aussi presque toujours ce que vous voulez.
|
||||
C’est une **erreur d’outil** (tool error), et c’est presque toujours ce que vous voulez.
|
||||
|
||||
C’est le modèle qui appelle votre outil. C’est lui qui a choisi les arguments. Une erreur d’outil est donc un tour de conversation : le modèle lit *« No book titled 'Nothing' in the catalog. »*, comprend qu’il s’est trompé de titre et rappelle l’outil avec un meilleur. Vous avez écrit un seul `raise` et obtenu un agent qui se corrige tout seul.
|
||||
|
||||
Côté serveur, une `ToolError` se résume à une ligne `INFO` dans le journal, sans traceback. Vous l’aviez vue venir, il n’y a donc rien à examiner.
|
||||
|
||||
!!! tip
|
||||
N’utilisez jamais `return` pour renvoyer un message d’erreur depuis un outil. Une chaîne renvoyée a
|
||||
`is_error=False` : pour le modèle (et pour toute interface cliente), l’outil semble avoir
|
||||
|
|
@ -44,7 +46,7 @@ C’est le modèle qui appelle votre outil. C’est lui qui a choisi les argumen
|
|||
|
||||
## Une erreur que le modèle ne peut pas corriger {#an-error-the-model-cannot-fix}
|
||||
|
||||
Remplacez maintenant `ValueError` par `MCPError`.
|
||||
Remplacez maintenant `ToolError` par `MCPError`.
|
||||
|
||||
```python title="server.py" hl_lines="1 3 14"
|
||||
--8<-- "docs_src/handling_errors/tutorial002.py"
|
||||
|
|
@ -77,10 +79,10 @@ Remplacez maintenant `ValueError` par `MCPError`.
|
|||
|
||||
Les deux voies répondent à deux questions différentes.
|
||||
|
||||
* **Levez n’importe quelle exception** pour un échec d’*exécution* : ce que votre outil a tenté de faire n’a pas fonctionné. Le modèle a choisi l’appel, il devrait donc en voir la conséquence et avoir une chance de se rattraper. Un titre mal orthographié, une API amont qui a expiré, une ligne qui n’existe pas : autant d’erreurs d’outil.
|
||||
* **Levez `ToolError`** pour un échec d’*exécution* : ce que votre outil a tenté de faire n’a pas fonctionné. Le modèle a choisi l’appel, il devrait donc en voir la conséquence et avoir une chance de se rattraper. Un titre mal orthographié, une API amont qui a expiré, une ligne qui n’existe pas : autant d’erreurs d’outil.
|
||||
* **Levez `MCPError`** quand c’est la *requête elle-même* qui doit être rejetée : il manque au client une capacité dont dépend votre outil, le serveur n’est pas en état de servir qui que ce soit, l’appelant a sauté une étape obligatoire. Aucune nouvelle tentative du modèle ne corrige cela, il n’y a donc rien à gagner à lui transmettre le message.
|
||||
|
||||
Une seule question tranche : **un modèle plus malin aurait-il pu éviter cela ?** Oui -> exception ordinaire. Non -> `MCPError`.
|
||||
Une seule question tranche : **un modèle plus malin aurait-il pu éviter cela ?** Oui -> `ToolError`. Non -> `MCPError`.
|
||||
|
||||
Selon ce critère, la seconde version de `get_author` a fait le mauvais choix : un meilleur titre règle le problème, le modèle méritait donc de voir le message. Elle est là pour vous montrer le mécanisme, pas pour le recommander.
|
||||
|
||||
|
|
@ -89,6 +91,25 @@ Selon ce critère, la seconde version de `get_author` a fait le mauvais choix :
|
|||
utile `data` facultative. Ce que vous y mettez est ce que le client reçoit : le SDK transmet telle
|
||||
quelle une `MCPError` levée au lieu de l’assainir.
|
||||
|
||||
## Toute autre exception {#any-other-exception}
|
||||
|
||||
Retirez maintenant la vérification et laissez la recherche dans le dictionnaire échouer d’elle-même :
|
||||
|
||||
```python title="server.py" hl_lines="11"
|
||||
--8<-- "docs_src/handling_errors/tutorial004.py"
|
||||
```
|
||||
|
||||
`CATALOG[title]` lève `KeyError`. Vous ne l’aviez pas prévue, le SDK la traite donc comme un plantage :
|
||||
|
||||
```python
|
||||
result.is_error # True
|
||||
result.content # [TextContent(text="Error executing tool get_author")]
|
||||
```
|
||||
|
||||
L’appel renvoie toujours `is_error=True`, le modèle sait donc qu’il a échoué et peut passer à autre chose. Ce qu’il n’obtient pas, c’est le texte de l’exception : une `KeyError` venue de votre code, ou une pile de SQL remontée d’un pilote trois bibliothèques plus bas, peut décrire les entrailles de votre serveur, si bien que ce texte ne quitte jamais le serveur.
|
||||
|
||||
C’est vous qui le recevez. Le serveur journalise le plantage au niveau `ERROR` avec le traceback complet, sous l’intitulé `Tool 'get_author' raised an unexpected exception`. Un journal de production réglé sur `WARNING` reste donc silencieux à chaque `ToolError` et se manifeste dès que quelque chose est réellement cassé.
|
||||
|
||||
## Une ressource qui n’existe pas {#a-resource-that-doesnt-exist}
|
||||
|
||||
Les ressources tracent la même frontière, et fournissent une exception dédiée pour le cas courant.
|
||||
|
|
@ -109,7 +130,7 @@ Quand elle ne le peut pas, levez `ResourceNotFoundError`. Le SDK la transforme e
|
|||
}
|
||||
```
|
||||
|
||||
Remarquez qu’il n’y a pas ici de demi-résultat `is_error=True`. La lecture d’une ressource renvoie un contenu ou échoue : les ressources n’ont que la voie du protocole. Les modèles et tout ce qui concerne les ressources se trouvent dans **[Ressources](resources.md)**.
|
||||
Remarquez qu’il n’y a pas ici de demi-résultat `is_error=True`. La lecture d’une ressource renvoie un contenu ou échoue : les ressources n’ont que la voie du protocole. `ResourceError` est l’équivalent pour un échec qui n’est pas « introuvable » (`-32603`, votre message), et les deux se résument à une ligne `INFO` dans votre journal. Toute autre exception hormis `MCPError` est un plantage : le client reçoit `-32603` ne mentionnant que l’URI, et le traceback va dans votre journal au niveau `ERROR`. Les modèles et tout ce qui concerne les ressources se trouvent dans **[Ressources](resources.md)**.
|
||||
|
||||
## Les erreurs que vous ne levez jamais {#errors-you-never-raise}
|
||||
|
||||
|
|
@ -120,19 +141,21 @@ Envoyez à `get_author` un `title` qui n’est pas une chaîne et le SDK le reje
|
|||
Cela représente toute une catégorie d’instructions `raise` que vous n’écrivez pas : ne revalidez pas vos propres annotations de type.
|
||||
|
||||
!!! info
|
||||
Tout ce que décrit cette page est ce qu’un **client** voit, et le `Client` en mémoire avec lequel
|
||||
vous écrirez vos tests voit exactement la même chose. Même `raise_exceptions=True` ne retransforme
|
||||
pas une erreur d’outil en traceback : au moment où ce drapeau pourrait agir, votre exception est déjà
|
||||
devenue le résultat `is_error=True`. Faites vos assertions sur le résultat. **[Tests](../get-started/testing.md)** présente ce schéma.
|
||||
Tout ce qu’un **client** voit sur cette page, le `Client` en mémoire avec lequel vous écrirez vos
|
||||
tests le voit aussi. Même `raise_exceptions=True` ne rend pas à l’appelant l’exception d’un outil
|
||||
en échec : au moment où ce drapeau pourrait agir, votre exception est déjà devenue le résultat
|
||||
`is_error=True`. Faites vos assertions sur le résultat. Si vous avez besoin du traceback d’un plantage,
|
||||
il est dans le journal du serveur, et le `caplog` de pytest le capture. **[Tests](../get-started/testing.md)** présente ce schéma.
|
||||
|
||||
## Récapitulatif {#recap}
|
||||
|
||||
* Levez **n’importe quelle exception** dans un outil -> l’appel renvoie `is_error=True` avec votre message dans `content`. Le modèle le lit et peut réessayer. C’est le comportement par défaut.
|
||||
* Levez **`ToolError`** dans un outil -> l’appel renvoie `is_error=True` avec votre message dans `content`. Le modèle le lit et peut réessayer.
|
||||
* Levez **`MCPError`** -> l’appel lui-même échoue avec une erreur JSON-RPC. Le modèle ne voit rien ; c’est l’hôte qui s’en occupe. `code`, `message` et `data` arrivent intacts.
|
||||
* La question qui tranche : *un modèle plus malin aurait-il pu éviter cela ?* Oui -> exception. Non -> `MCPError`.
|
||||
* La question qui tranche : *un modèle plus malin aurait-il pu éviter cela ?* Oui -> `ToolError`. Non -> `MCPError`.
|
||||
* Toute **autre exception** est un plantage -> `is_error=True` avec seulement `Error executing tool <name>` pour le modèle, et un enregistrement `ERROR` avec le traceback pour vous.
|
||||
* `ResourceNotFoundError` depuis un gestionnaire (handler) de ressource -> le `-32602` du protocole, avec l’URI dans `data`.
|
||||
* Les mauvais arguments sont rejetés d’après le schéma avant que votre fonction ne s’exécute ; vous n’avez pas de `raise` à écrire pour eux.
|
||||
* `from mcp import MCPError` ; les constantes de codes d’erreur viennent de `mcp.types`.
|
||||
* Imports : `from mcp import MCPError`, `from mcp.server.mcpserver.exceptions import ToolError, ResourceError, ResourceNotFoundError`, et les constantes de codes d’erreur depuis `mcp.types`.
|
||||
|
||||
Les erreurs sont gérées. C’est tout ce qu’un serveur *expose*. Ce que chaque gestionnaire peut lire, et faire en retour auprès du client pendant qu’il s’exécute, fait l’objet de la section suivante : **[Dans votre gestionnaire](../handlers/index.md)**.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [496394d24d221bf1, 4ceb4591180dc6c3, 0fd63e4682d02e0c, 969ede0bd3686a16, 043f526230dd243d, 6ee3e9bcfd24047a]
|
||||
sections: [496394d24d221bf1, 4ceb4591180dc6c3, 0fd63e4682d02e0c, 969ede0bd3686a16, 864137b5e9c61e91, 043f526230dd243d, db1ef91db7d6b3f3]
|
||||
tool: 1
|
||||
---
|
||||
# Médias {#media}
|
||||
|
|
@ -86,6 +86,24 @@ Une extension qu’il ne reconnaît pas se rabat sur `application/octet-stream`.
|
|||
`Audio` à partir d’octets MP3 de cette façon et le client reçoit `mime_type="audio/wav"`, puis
|
||||
échoue consciencieusement à le décoder. Quand vous passez `data=`, passez `format=`.
|
||||
|
||||
## Embarquer une ressource {#embedding-a-resource}
|
||||
|
||||
Un outil peut aussi renvoyer un document : du texte ou des octets, accompagnés de l’URI où il réside et d’un type MIME. C’est une **`EmbeddedResource`**, une autre sorte de bloc de contenu. Contrairement à un simple `str`, elle indique au client ce qu’est le contenu, si bien que le client peut l’afficher comme pièce jointe ou reconnaître une ressource qu’il connaît déjà.
|
||||
|
||||
```python title="server.py" hl_lines="7 14 16-18"
|
||||
--8<-- "docs_src/media/tutorial005.py"
|
||||
```
|
||||
|
||||
* `brand://guidelines` est une ressource ordinaire (la page **[Ressources](resources.md)** les traite). L’outil remet le même document au modèle sur demande, et appeler `guidelines()` directement conserve une source de vérité unique.
|
||||
* `EmbeddedResource` et `TextResourceContents` viennent de `mcp.types`. Il n’y a pas d’utilitaire comme pour les images : le bloc que vous construisez va tel quel dans le résultat, et il n’y a pas de `structured_content`.
|
||||
* Utilisez l’URI sous lequel la ressource est enregistrée, pour qu’un client puisse savoir que la pièce jointe et `brand://guidelines` sont le même document. N’importe quel URI est valide, enregistré ou non.
|
||||
|
||||
```python
|
||||
result.content # [EmbeddedResource(type="resource", resource=TextResourceContents(uri="brand://guidelines", mime_type="text/markdown", text="# Brand guidelines\n\n..."))]
|
||||
```
|
||||
|
||||
Pour du contenu binaire, utilisez `BlobResourceContents(uri=..., mime_type=..., blob=...)` avec les octets encodés en base64 dans `blob`, à la place de `TextResourceContents`. Pour n’envoyer qu’un pointeur que le client pourra lire plus tard via `resources/read`, renvoyez plutôt un `ResourceLink(name=..., uri=...)` ; c’est aussi un bloc de contenu.
|
||||
|
||||
## Icônes {#icons}
|
||||
|
||||
Une `Icon` est une métadonnée, pas du contenu. Elle ne transporte pas l’image ; elle en désigne une par un URI, et un client peut la récupérer et l’afficher à côté du nom de votre serveur, d’un outil, d’une ressource ou d’un prompt.
|
||||
|
|
@ -115,6 +133,7 @@ Les icônes d’un outil sont sur l’objet `Tool` issu de `tools/list`, celles
|
|||
|
||||
* Renvoyez une `Image` ou un `Audio` depuis un outil et le client reçoit un bloc `ImageContent` / `AudioContent` : vos octets encodés en base64, avec un type MIME.
|
||||
* Construisez-en un à partir d’un `path=` et laissez l’extension décider du type MIME, ou à partir de `data=` en mémoire plus un `format=` explicite.
|
||||
* Renvoyez une `EmbeddedResource` pour placer un document (du texte ou un blob base64, avec son URI et son type MIME) dans le résultat, ou un `ResourceLink` pour n’envoyer que le pointeur.
|
||||
* Les résultats média ne portent ni `structured_content` ni schéma de sortie.
|
||||
* Une `Icon` est un pointeur : un URI `src` plus, en option, `mime_type`, `sizes` et `theme`.
|
||||
* `icons=[...]` fonctionne sur le serveur, sur les outils, sur les ressources et sur les prompts, et les clients les retrouvent sur les objets correspondants.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [d65c098f37f5b6c3, dd0c2724d6f2877e, 6835bb3570c6714c, ffe823cb0fedd488, f33651add1b59094]
|
||||
sections: [d65c098f37f5b6c3, dd0c2724d6f2877e, 6835bb3570c6714c, d30d3c20168b88b2, f5ef38dad59d6f76, 6e38a699ba57fbdf, 2b984a3bf37a0ddd]
|
||||
tool: 1
|
||||
---
|
||||
# Prompts {#prompts}
|
||||
|
|
@ -139,10 +139,55 @@ L’entrée `prompts/list` contient désormais tout ce dont un client a besoin p
|
|||
```
|
||||
|
||||
!!! info
|
||||
Si vous avez lu **[Outils](tools.md)**, vous connaissez déjà tout ce que contient cette page. Même décorateur, même
|
||||
Si vous avez lu **[Outils](tools.md)**, vous connaissez déjà tout jusqu’ici. Même décorateur, même
|
||||
docstring servant de description, mêmes `Annotated`/`Field`. Seuls changent qui
|
||||
le déclenche (l’utilisateur) et où va le résultat (dans la conversation).
|
||||
|
||||
## Au-delà du texte {#more-than-text}
|
||||
|
||||
`UserMessage` et `AssistantMessage` acceptent aussi un bloc de contenu, ou un utilitaire `Image` / `Audio`, partout où ils acceptent une `str`. Deux cas se présentent dans les prompts : joindre un document et joindre une image.
|
||||
|
||||
### Incorporer un fichier {#embedding-a-file}
|
||||
|
||||
```python title="server.py" hl_lines="5 12 21 23"
|
||||
--8<-- "docs_src/prompts/tutorial004.py"
|
||||
```
|
||||
|
||||
* Le guide de style est une ressource à l’adresse `style://python` (**[Ressources](resources.md)** traite de celles-ci), lue depuis un fichier `style-guide.md` placé à côté de `server.py`. Mettez-y n’importe quel fichier Markdown.
|
||||
* `EmbeddedResource(resource=TextResourceContents(...))`, tous deux issus de `mcp.types`, transporte le fichier avec son URI et son type MIME comme premier message ; la demande qui y fait référence suit sous forme de texte brut.
|
||||
* Incorporer le guide, plutôt que de le coller dans la f-string, permet au client de l’afficher comme pièce jointe et de rouvrir `style://python` plus tard, et le modèle reçoit le fichier tel quel. Pour un fichier binaire, utilisez `BlobResourceContents` avec un `blob` en base64.
|
||||
|
||||
Une fois rendu, le `content` du premier message est un bloc `resource` :
|
||||
|
||||
```json
|
||||
{"type": "resource", "resource": {"uri": "style://python", "mimeType": "text/markdown", "text": "* Prefer early returns.\n..."}}
|
||||
```
|
||||
|
||||
### Joindre une image {#attaching-an-image}
|
||||
|
||||
```python title="server.py" hl_lines="4 15"
|
||||
--8<-- "docs_src/prompts/tutorial005.py"
|
||||
```
|
||||
|
||||
* `Image` est l’utilitaire de **[Images, audio et icônes](media.md)**. `UserMessage` le convertit en bloc `ImageContent` (le fichier encodé en base64, le type MIME deviné d’après `.png`) au moment du rendu du prompt ; `Audio` devient un `AudioContent` de la même façon.
|
||||
* Placez n’importe quel PNG nommé `architecture.png` à côté de `server.py`. Les arguments d’un prompt sont des chaînes, l’image vient donc toujours du serveur ; `component` ne fournit que les mots.
|
||||
|
||||
```json
|
||||
{"type": "image", "data": "iVBORw0KGgoAAAANSUhEUg...", "mimeType": "image/png"}
|
||||
```
|
||||
|
||||
## Modifier la liste à l’exécution {#changing-the-list-at-runtime}
|
||||
|
||||
Des prompts peuvent être ajoutés pendant que des clients sont connectés, par exemple pour permettre à un utilisateur d’enregistrer une instruction comme entrée de menu bien à lui. Enregistrez le prompt, puis notifiez :
|
||||
|
||||
```python title="server.py" hl_lines="5 23-27"
|
||||
--8<-- "docs_src/prompts/tutorial006.py"
|
||||
```
|
||||
|
||||
* `mcp.add_prompt(Prompt.from_function(fn, name=..., description=...))` enregistre une fonction exactement comme le ferait `@mcp.prompt()`, et `mcp.remove_prompt(name)` fait l’inverse. `add_prompt` conserve une entrée existante du même nom au lieu de l’écraser ; l’outil supprime donc d’abord toute ancienne entrée pour que l’enregistrement soit un remplacement. `prompts/list` reflète le changement immédiatement.
|
||||
* `await ctx.notify_prompts_changed()` envoie `notifications/prompts/list_changed` à chaque client `2026-07-28` à l’écoute sur un flux `subscriptions/listen` (**[Abonnements](../handlers/subscriptions.md)**). `await ctx.session.send_prompt_list_changed()` l’envoie au client appelant lorsque celui-ci est antérieur à 2026 (**[Prendre en charge les clients historiques](../run/legacy-clients.md)**). Appelez les deux ; chacun ne fait rien quand il n’y a personne à prévenir.
|
||||
* Un client qui reçoit la notification appelle de nouveau `prompts/list`. Dans le `Client` Python, c’est `async with client.listen(prompts_list_changed=True) as sub:`, qui produit un événement `PromptsListChanged`.
|
||||
|
||||
## Récapitulatif {#recap}
|
||||
|
||||
* `@mcp.prompt()` sur une fonction en fait un prompt. Le nom vient de la fonction, la description de la docstring.
|
||||
|
|
@ -151,5 +196,7 @@ L’entrée `prompts/list` contient désormais tout ce dont un client a besoin p
|
|||
* Renvoyez une `str` et elle devient un seul message utilisateur. Renvoyez une liste de `UserMessage` / `AssistantMessage` pour amorcer une conversation à plusieurs tours.
|
||||
* `title=` et `Field(description=...)` sont ce qu’un client affiche dans son interface.
|
||||
* Un argument obligatoire manquant fait échouer toute la requête. Il n’y a pas de résultat d’erreur par prompt.
|
||||
* Enveloppez un `EmbeddedResource` ou une `Image` dans un `UserMessage` pour joindre un document ou une image.
|
||||
* Ajoutez ou supprimez des prompts à l’exécution avec `mcp.add_prompt(...)` / `mcp.remove_prompt(...)`, puis `await ctx.notify_prompts_changed()` et `await ctx.session.send_prompt_list_changed()`.
|
||||
|
||||
L’autocomplétion côté serveur des arguments d’un prompt (ou d’un modèle de ressource), c’est **[Complétions](completions.md)**.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [a838d57f003aed44, 857d03886a0137ed, 42d9efcb9f542867, 2290ff08435b5573, e866c192e11d1c14, 6cdbad079f7b47f0, d4b607372fb28b51, 18dbf726ac45e0b7, c6f7d2a148aa49f4, c851964bb3301907, d715db6f8dccc9cc, ef86634aa70498a7]
|
||||
sections: [a838d57f003aed44, 857d03886a0137ed, 42d9efcb9f542867, 2290ff08435b5573, 91be9b73602abcf1, 6cdbad079f7b47f0, d4b607372fb28b51, 18dbf726ac45e0b7, c7eff2a5698225fa, c851964bb3301907, 8f296f1f09e4c400, d715db6f8dccc9cc, a0c344a48450dbe4]
|
||||
tool: 1
|
||||
---
|
||||
# Sortie structurée {#structured-output}
|
||||
|
|
@ -105,7 +105,7 @@ Toutes les formes ne méritent pas une classe. Un `TypedDict` produit le même s
|
|||
--8<-- "docs_src/structured_output/tutorial003.py"
|
||||
```
|
||||
|
||||
Un `TypedDict` est un simple `dict` à l’exécution : c’est donc ce que vous construisez et renvoyez. Le schéma, la validation et `structured_content` sont identiques à la version `BaseModel` (à l’exception des descriptions, pour lesquelles `TypedDict` n’a pas de place).
|
||||
Un `TypedDict` est un simple `dict` à l’exécution : c’est donc ce que vous construisez et renvoyez. Le schéma, la validation et `structured_content` suivent les mêmes règles que la version `BaseModel` : ajoutez une docstring de classe ou `Annotated[..., Field(description=...)]` et elles deviennent les descriptions, et une clé `NotRequired` que vous omettez du dict reste absente de `structured_content`.
|
||||
|
||||
## Une dataclass {#a-dataclass}
|
||||
|
||||
|
|
@ -188,17 +188,18 @@ L’annotation promet un `WeatherData`. La réponse en amont a cessé d’envoye
|
|||
|
||||
!!! check
|
||||
Appelez `get_weather` : il ne remet pas discrètement au client un objet à moitié vide. L’appel
|
||||
échoue, et les premières lignes de l’erreur nomment le champ :
|
||||
échoue : le client reçoit `is_error=True` avec `Error executing tool get_weather`, de sorte que le
|
||||
modèle sait que l’appel a échoué au lieu de lire avec assurance une météo qui n’existe pas. Le nom
|
||||
du champ est pour vous, dans le journal du serveur au niveau `ERROR` :
|
||||
|
||||
```text
|
||||
Error executing tool get_weather: 1 validation error for WeatherData
|
||||
Tool 'get_weather' raised an unexpected exception
|
||||
...
|
||||
pydantic_core._pydantic_core.ValidationError: 1 validation error for WeatherData
|
||||
humidity
|
||||
Field required [type=missing, input_value={'temperature': 16.2, 'conditions': 'Overcast'}, input_type=dict]
|
||||
```
|
||||
|
||||
Ce texte revient comme résultat de l’outil avec `is_error=True` : le modèle sait donc que l’appel
|
||||
a échoué au lieu de lire avec assurance une météo qui n’existe pas.
|
||||
|
||||
Au passage, renvoyer un simple `dict` depuis un outil `-> WeatherData` ne pose aucun problème. C’est exactement ce que `json.loads` a produit. La validation porte sur la valeur, pas sur le type Python.
|
||||
|
||||
## Désactiver la sortie structurée {#opting-out}
|
||||
|
|
@ -213,6 +214,10 @@ Aucun `output_schema`, aucune enveloppe, aucune validation. `structured_content`
|
|||
|
||||
L’inverse, `structured_output=True`, transforme la détection automatique en exigence : un outil dont le type de retour ne peut pas produire de schéma lève une exception à l’import au lieu de se rabattre sur du texte.
|
||||
|
||||
## Blocs de contenu et médias {#content-blocks-and-media}
|
||||
|
||||
Les blocs de contenu et les médias (`TextContent`, `EmbeddedResource`, `Image`, `Audio` et consorts, seuls, comme éléments d’une `list`, d’un `tuple` ou d’une `Sequence`, ou comme branches d’une union) sont désactivés pour vous : ils sont destinés à être lus par le modèle, la détection automatique n’en tire donc aucun schéma (la page **[Images, audio et icônes](media.md)** traite de `Image` et `Audio`). `structured_output=True` en impose tout de même un pour les classes de blocs de contenu.
|
||||
|
||||
## Une classe sans annotations de type {#a-class-without-type-hints}
|
||||
|
||||
Il existe une façon de se retrouver sans sortie structurée sans l’avoir demandé : renvoyer une classe qui n’a **aucune annotation dans son corps**.
|
||||
|
|
@ -245,6 +250,6 @@ Il existe une façon de se retrouver sans sortie structurée sans l’avoir dema
|
|||
* Les scalaires, listes, tuples et unions sont enveloppés dans `{"result": ...}`. Les modèles, les `TypedDict`, les dataclasses, les classes annotées et `dict[str, ...]` sont déjà des objets et restent tels quels.
|
||||
* Chaque résultat porte `content` (du texte, pour le modèle) **et** `structured_content` (des données, pour l’application).
|
||||
* Ce que vous renvoyez est validé par rapport au schéma. Une incohérence est une erreur d’outil, pas un résultat corrompu.
|
||||
* `structured_output=False` désactive la sortie structurée d’un outil. Une classe sans annotations de type la désactive silencieusement ; surveillez ce cas.
|
||||
* `structured_output=False` désactive la sortie structurée d’un outil. Les blocs de contenu, `Image` et `Audio` la désactivent par défaut ; une classe sans annotations de type la désactive silencieusement, surveillez donc ce cas.
|
||||
|
||||
Vous maîtrisez désormais tout ce qu’un outil peut répondre. Ensuite, la deuxième primitive : **[Ressources](resources.md)**.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [e4cc390d56573409, 8566e2b68594e9ad, 2c97b9f888398951, 048e5471dfa71aea, 3076b1e16ad95950, edbedf2a16e71311, 3d8ef8da89fa87c1, f6c0e02e6ea5a363]
|
||||
sections: [e4cc390d56573409, f30cf8103a6e918c, 2c97b9f888398951, 048e5471dfa71aea, 3076b1e16ad95950, edbedf2a16e71311, 3d8ef8da89fa87c1, f6c0e02e6ea5a363]
|
||||
tool: 1
|
||||
---
|
||||
# Outils {#tools}
|
||||
|
|
@ -39,6 +39,8 @@ Regardez ce que vous avez écrit. Pas de schémas, pas de JSON, pas de protocole
|
|||
|
||||
Les deux arguments figurent dans `required` parce qu’aucun n’a de valeur par défaut. Vous allez corriger cela dans un instant. (Les clés `title` sont des artefacts de Pydantic ; les propriétés, leurs types et `required` constituent le contrat.)
|
||||
|
||||
Il n’y a pas non plus de clé `$schema` : MCP traite un schéma qui en est dépourvu comme du **JSON Schema 2020-12**, ce qui est justement ce que génère Pydantic. Il n’y a donc rien à choisir tant que vous n’écrivez pas vos schémas à la main avec le **[Server de bas niveau](../advanced/low-level-server.md#the-dialect-is-json-schema-2020-12)**.
|
||||
|
||||
!!! tip
|
||||
Ici, les annotations de type ne sont pas de la documentation. Elles sont **le contrat**. Si un client envoie `"limit": "ten"`,
|
||||
le SDK le rejette avant même que votre fonction ne s’exécute.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [4a7033e1ed8ad602, 55dcbfff0c6271bf, 101ef9d14bf4ec46, 4b6c4a845438abc7, f98b46bafbee4acd]
|
||||
sections: [4a7033e1ed8ad602, 55dcbfff0c6271bf, 317f4256a650cab6, 4b6c4a845438abc7, f98b46bafbee4acd]
|
||||
tool: 1
|
||||
---
|
||||
# Modèles d’URI et sûreté des chemins {#uri-templates-and-path-safety}
|
||||
|
|
@ -180,7 +180,7 @@ connaître la frontière de votre bac à sable. Pour l’accès au système de
|
|||
fichiers, utilisez `safe_join` pour résoudre le chemin et vérifier qu’il
|
||||
reste à l’intérieur de votre répertoire de base :
|
||||
|
||||
```python title="server.py" hl_lines="4 14"
|
||||
```python title="server.py" hl_lines="5 15"
|
||||
--8<-- "docs_src/uri_templates/tutorial002.py"
|
||||
```
|
||||
|
||||
|
|
@ -224,9 +224,11 @@ de fichiers, `safe_join` reste la frontière de confinement.
|
|||
|
||||
!!! tip
|
||||
Si votre gestionnaire ne peut pas satisfaire la requête (le fichier
|
||||
n’existe pas, l’identifiant est inconnu), levez une exception. Le SDK
|
||||
la transforme en réponse d’erreur. Consultez **[Gérer les erreurs](handling-errors.md)** pour la
|
||||
différence entre une erreur de protocole et une erreur d’outil.
|
||||
n’existe pas, l’identifiant est inconnu), levez `ResourceNotFoundError`
|
||||
comme le fait `read_manual` ci-dessus. Le client reçoit `-32602` avec
|
||||
votre message et l’URI. Une exception inattendue devient, elle, une
|
||||
erreur générique `-32603`. Consultez
|
||||
**[Gérer les erreurs](handling-errors.md#a-resource-that-doesnt-exist)**.
|
||||
|
||||
## Les ressources sur le Server de bas niveau {#resources-on-the-low-level-server}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [2efaecdef109a5c5, fcacd3e66b8635a4, 25323d737dcf0261, 4835ed1772f1d113, 137454d469c867f5, 6392596bd6df54f0, 41126fa9c4fe432f, 480b6d7897e30ab4, d83bb682e708dde0, ebbed3449c499db4, 323ef84f6b4bebde, 30fd31be74169d9a, 656943c6cb567218, c2dc3b1007d2e987, 7cf5386b997d04e9, 0b59feed8384456e, 0cba47bae78d04eb, 954dc21efdb532a3]
|
||||
sections: [2efaecdef109a5c5, fcacd3e66b8635a4, 25323d737dcf0261, 8a6e351ec756904d, 137454d469c867f5, 6392596bd6df54f0, 41126fa9c4fe432f, 480b6d7897e30ab4, d83bb682e708dde0, ebbed3449c499db4, 323ef84f6b4bebde, 30fd31be74169d9a, 656943c6cb567218, c2dc3b1007d2e987, 7cf5386b997d04e9, 0b59feed8384456e, 0cba47bae78d04eb, e4355f4c7cf4fb2e]
|
||||
tool: 1
|
||||
---
|
||||
# Dépannage {#troubleshooting}
|
||||
|
|
@ -81,11 +81,11 @@ async def main() -> None:
|
|||
|
||||
`__aexit__` est la déconnexion, c’est pourquoi il n’y a pas de `client.close()` à oublier. **[Tests](get-started/testing.md)** repose exactement sur ce modèle.
|
||||
|
||||
## `Error executing tool <name>: <message>` et `Unknown tool: <name>` {#error-executing-tool-name-message-and-unknown-tool-name}
|
||||
## `Error executing tool <name>: <message>`, `Error executing tool <name>` et `Unknown tool: <name>` {#error-executing-tool-name-message-error-executing-tool-name-and-unknown-tool-name}
|
||||
|
||||
Vous lisez un **résultat**, pas une exception. `call_tool` n’a pas levé d’exception, et ne le fera jamais pour un outil qui échoue.
|
||||
|
||||
Appelez `forecast` pour une ville que le serveur ne connaît pas, et l’exception qu’il lève revient avec la requête marquée comme *réussie* :
|
||||
Appelez `forecast` pour une ville que le serveur ne connaît pas, et l’exception `ToolError` qu’il lève revient avec la requête marquée comme *réussie* :
|
||||
|
||||
```python
|
||||
result.is_error # True
|
||||
|
|
@ -97,6 +97,8 @@ result.structured_content # None
|
|||
|
||||
Le correctif est dans votre client : **vérifiez `result.is_error`**. Un `try/except` autour de `call_tool` n’intercepte rien de tout cela, parce qu’il n’y a rien à intercepter. C’est voulu, et c’est la chose la plus utile de cette page à intégrer : c’est le *modèle* qui a choisi l’appel, donc c’est le modèle qui reçoit le message et une chance de réessayer. Tous les détails sont dans **[Gérer les erreurs](servers/handling-errors.md)**, y compris le chemin `MCPError` qui, lui, *lève* bien une exception.
|
||||
|
||||
La forme nue, `Error executing tool <name>` sans message, signifie que l’outil a **planté** : une exception qu’il n’avait pas prévue lui a échappé (ou sa valeur de retour n’a pas satisfait le schéma de sortie), et le texte de cette exception est tenu à l’écart de la liaison. Le traceback est dans le **journal du serveur** au niveau `ERROR`, sous la forme `Tool '<name>' raised an unexpected exception`.
|
||||
|
||||
## `TypeError: The @tool decorator was used incorrectly. Did you forget to call it? Use @tool() instead of @tool` {#typeerror-the-tool-decorator-was-used-incorrectly-did-you-forget-to-call-it-use-tool-instead-of-tool}
|
||||
|
||||
Vous avez écrit `@mcp.tool` au lieu de `@mcp.tool()`. `tool()` est une *fabrique* de décorateurs : sans les parenthèses, Python passe votre fonction à son paramètre `name=`.
|
||||
|
|
@ -412,7 +414,7 @@ mcp = MCPServer("Weather", request_state_security=RequestStateSecurity(keys=[key
|
|||
## Récapitulatif {#recap}
|
||||
|
||||
* `ExceptionGroup: unhandled errors in a TaskGroup` n’est jamais l’erreur. Lisez la **dernière ligne** ; intercepter `MCPError` *à l’intérieur* du bloc `async with Client(...)` évite entièrement l’enveloppe.
|
||||
* `call_tool` ne lève pas d’exception pour un outil qui échoue. `Error executing tool ...` et `Unknown tool: ...` sont des résultats : vérifiez `result.is_error`.
|
||||
* `call_tool` ne lève pas d’exception pour un outil qui échoue. `Error executing tool ...` et `Unknown tool: ...` sont des résultats : vérifiez `result.is_error`. L’absence de message après le nom de l’outil signifie qu’il a planté, et le traceback est dans le journal du serveur.
|
||||
* `Client must be used within an async context manager` -> utilisez `async with`. `Use @tool() instead of @tool` -> ajoutez les parenthèses.
|
||||
* `Tool already exists:` dans le journal du serveur est le seul signe que deux outils de même nom se sont fondus en un seul.
|
||||
* Un 421, trois formulations : `Server returned an error response` (le `Client` python), `421 Misdirected Request` / `Invalid Host header` (tout le reste), `Invalid Host header: <host>` (le journal du serveur). Correctif : `transport_security=TransportSecuritySettings(allowed_hosts=[...])`.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [cfe01c0c5863dfa2, 11d93f1fa09eadf5, a7392996acf1ad8f, 875eb2889263424e]
|
||||
sections: [cfe01c0c5863dfa2, 1c58c5cfcc37d455, a7392996acf1ad8f, 875eb2889263424e]
|
||||
tool: 1
|
||||
---
|
||||
# Nouveautés de la v2 {#whats-new-in-v2}
|
||||
|
|
@ -47,9 +47,9 @@ La v1 vous donnait trois couches imbriquées : un gestionnaire de contexte de tr
|
|||
--8<-- "docs_src/client/tutorial001.py"
|
||||
```
|
||||
|
||||
`Client` accepte un objet serveur (en mémoire, sans transport : c’est la solution pour les tests), une URL (Streamable HTTP) ou n’importe quel gestionnaire de contexte de transport comme `stdio_client(...)`. Entrer dans `async with` établit la connexion et négocie la version du protocole, quelle que soit la génération que parle le serveur ; `client.server_capabilities` et `client.protocol_version` sont simplement disponibles ensuite, et `client.server_info` aussi lorsque le serveur s’identifie (c’est désormais `Implementation | None`, puisque l’identité est optionnelle dans la génération 2026). Les fonctions de rappel (callbacks) d’échantillonnage et d’élicitation que vous aviez enregistrées en v1 fonctionnent toujours (leur corps voit le même renommage d’attributs en snake_case que tout le reste de cette page), elles répondent désormais aussi aux requêtes-dans-les-résultats de style 2026 (ci-dessous), et elles s’exécutent de façon concurrente plutôt qu’une à la fois. `ClientSession` reste en dessous pour qui veut la surface bas niveau, et `client.session` vous la donne ; elle a bougé elle aussi (elle tourne sur le nouveau moteur de répartition, et certaines de ses propres signatures ont changé), alors lisez le **[Guide de migration](migration.md#clientsession-now-runs-on-jsonrpcdispatcher-basesession-removed)** avant de descendre à ce niveau.
|
||||
`Client` accepte un objet serveur (en mémoire, sans transport : c’est la solution pour les tests), une URL (Streamable HTTP), un `StdioServerParameters` (un sous-processus stdio) ou n’importe quel autre gestionnaire de contexte de transport comme `sse_client(...)`. Entrer dans `async with` établit la connexion et négocie la version du protocole, quelle que soit la génération que parle le serveur ; `client.server_capabilities` et `client.protocol_version` sont simplement disponibles ensuite, et `client.server_info` aussi lorsque le serveur s’identifie (c’est désormais `Implementation | None`, puisque l’identité est optionnelle dans la génération 2026). Les fonctions de rappel (callbacks) d’échantillonnage et d’élicitation que vous aviez enregistrées en v1 fonctionnent toujours (leur corps voit le même renommage d’attributs en snake_case que tout le reste de cette page), elles répondent désormais aussi aux requêtes-dans-les-résultats de style 2026 (ci-dessous), et elles s’exécutent de façon concurrente plutôt qu’une à la fois. `ClientSession` reste en dessous pour qui veut la surface bas niveau, et `client.session` vous la donne ; elle a bougé elle aussi (elle tourne sur le nouveau moteur de répartition, et certaines de ses propres signatures ont changé), alors lisez le **[Guide de migration](migration.md#clientsession-now-runs-on-jsonrpcdispatcher-basesession-removed)** avant de descendre à ce niveau.
|
||||
|
||||
**[Le Client](client/index.md)** le présente, **[Transports du client](client/transports.md)** couvre les trois formes de connexion, **[Fonctions de rappel du client](client/callbacks.md)** couvre les fonctions de rappel elles-mêmes, et **[Tests](get-started/testing.md)** montre le modèle en mémoire qui remplace l’utilitaire `create_connected_server_and_client_session()` de la v1.
|
||||
**[Le Client](client/index.md)** le présente, **[Transports du client](client/transports.md)** couvre les quatre formes de connexion, **[Fonctions de rappel du client](client/callbacks.md)** couvre les fonctions de rappel elles-mêmes, et **[Tests](get-started/testing.md)** montre le modèle en mémoire qui remplace l’utilitaire `create_connected_server_and_client_session()` de la v1.
|
||||
|
||||
### Le `Server` bas niveau a été reconstruit, pas renommé {#the-low-level-server-was-rebuilt-not-renamed}
|
||||
|
||||
|
|
@ -135,7 +135,7 @@ Sur ces types, chaque attribut Python est désormais en snake_case : `result.is_
|
|||
Les renommages s’annoncent d’eux-mêmes. Ceux-ci, non :
|
||||
|
||||
* **Les fonctions synchrones s’exécutent sur un thread de travail.** Un outil `def` (ou une ressource, un prompt ou un résolveur) ne bloque plus la boucle d’événements ; la contrepartie est que son corps ne s’exécute plus *sur* le thread de la boucle d’événements, ce qui compte pour le code lié à un thread particulier. Les gestionnaires `async def` ne sont pas touchés. **[Guide de migration](migration.md#sync-handler-functions-now-run-on-a-worker-thread)**.
|
||||
* **Une `MCPError` (la `McpError` de la v1) levée dans un outil est désormais une erreur de protocole.** Le modèle ne la voit jamais. Toute autre exception devient toujours un résultat `is_error=True` que le modèle peut lire et auquel il peut réagir. **[Gérer les erreurs](servers/handling-errors.md)** détaille la distinction.
|
||||
* **Une `MCPError` (la `McpError` de la v1) levée dans un outil est désormais une erreur de protocole.** Le modèle ne la voit jamais. Toute autre exception devient toujours un résultat `is_error=True`, mais seul le message d’une `ToolError` atteint le modèle : toute autre exception se lit désormais `Error executing tool <name>`, avec la trace d’appels dans le journal de votre serveur. **[Gérer les erreurs](servers/handling-errors.md)** détaille la distinction.
|
||||
* **Les résultats sont validés avant de partir.** Un `Tool` construit à la main dont le `input_schema` vaut `{}` fait désormais échouer `tools/list` (la spécification exige `"type": "object"`). Les serveurs construits avec `@mcp.tool()` ne voient jamais cela ; le SDK écrit leurs schémas.
|
||||
* **Votre client valide ce qu’il reçoit.** `list_tools()` et `call_tool()` vérifient la réponse du serveur par rapport à la version du protocole négociée, si bien qu’un serveur pas tout à fait valide que l’analyse indulgente de la v1 tolérait lève désormais `pydantic.ValidationError`. Si vous vous connectez à des serveurs que vous ne contrôlez pas, attendez-vous à être celui qui les découvre ; le **[Guide de migration](migration.md#client-validates-inbound-traffic-against-the-protocol-schema)** a les détails.
|
||||
* **Les modèles d’URI suivent désormais vraiment la RFC 6570.** `{+path}`, `{?query}` et leurs semblables fonctionnent, la correspondance est exacte au lieu d’être approximative façon regex, et la traversée de répertoires dans les valeurs extraites est rejetée par défaut. Les modèles plus stricts échouent au moment de la décoration, pas à la première requête. **[Modèles d’URI](servers/uri-templates.md)**.
|
||||
|
|
|
|||
Loading…
Reference in a new issue