docs: refresh translations, and translate pages in parallel (#3458)
This commit is contained in:
parent
08a3bc8eaf
commit
fd66270926
23 changed files with 4821 additions and 2277 deletions
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [0355618e5f4d5fe4, 1821eaf50f2d0b64, 82e0b28ebd3abf5a, 8ac39614c094f2d0, dab6ff945501ab2a, bd5565c3b2d4f959, 96819ce3d63a0487]
|
||||
sections: [0355618e5f4d5fe4, 0fefa31fb7c7b585, 5c53e7487c9c70cc, 8ac39614c094f2d0, dab6ff945501ab2a, bd5565c3b2d4f959, 96819ce3d63a0487]
|
||||
tool: 1
|
||||
---
|
||||
# MCP Apps {#mcp-apps}
|
||||
|
|
@ -18,7 +18,7 @@ Le SDK fournit cela sous la forme de l’extension intégrée `Apps` (`io.modelc
|
|||
|
||||
## Une horloge avec un cadran {#a-clock-with-a-face}
|
||||
|
||||
```python title="server.py" hl_lines="19 22 30 32"
|
||||
```python title="server.py" hl_lines="17 20 28 30"
|
||||
--8<-- "docs_src/apps/tutorial001.py"
|
||||
```
|
||||
|
||||
|
|
@ -31,7 +31,7 @@ Quatre étapes :
|
|||
|
||||
Le HTML lui-même écoute le `postMessage` de l’hôte et affiche le résultat. Pour de vraies applications, utilisez dans votre HTML le SDK navigateur officiel [`@modelcontextprotocol/ext-apps`](https://github.com/modelcontextprotocol/ext-apps). Il vous donne `ontoolresult`, `callServerTool`, `getHostContext` et `onhostcontextchanged` au lieu d’événements de message bruts.
|
||||
|
||||
## Dégradation gracieuse {#graceful-degradation}
|
||||
## Dégradation élégante {#graceful-degradation}
|
||||
|
||||
Tous les clients n’affichent pas les apps. La spécification dit sans détour ce que cela implique pour vous :
|
||||
|
||||
|
|
@ -39,11 +39,31 @@ Tous les clients n’affichent pas les apps. La spécification dit sans détour
|
|||
|
||||
Le modèle lit `content` ; l’iframe est pour les humains. Un hôte capable d’afficher une interface transmet quand même le résultat textuel au modèle, et un client purement textuel ne reçoit *que* cela. Le schéma canonique est donc : un outil, deux réponses. Regardez à nouveau `get_time` :
|
||||
|
||||
```python title="server.py" hl_lines="23-27"
|
||||
```python title="server.py" hl_lines="21-25"
|
||||
--8<-- "docs_src/apps/tutorial001.py"
|
||||
```
|
||||
|
||||
`client_supports_apps(ctx)` ne vaut `True` que lorsque le client a déclaré l’extension `io.modelcontextprotocol/ui` **et** listé `text/html;profile=mcp-app` dans ses paramètres `mimeTypes`. Le champ est obligatoire, donc un client qui l’omet ne compte pas. C’est exactement ce que déclare `main()` dans le même fichier : la moitié client de la négociation, et la réponse riche revient.
|
||||
`client_supports_apps(ctx)` ne vaut `True` que lorsque le client a déclaré l’extension `io.modelcontextprotocol/ui` **et** listé `text/html;profile=mcp-app` dans ses paramètres `mimeTypes`. Le champ est obligatoire, donc un client qui l’omet ne compte pas. Voici la moitié client de la négociation :
|
||||
|
||||
```python title="client.py" hl_lines="8 12"
|
||||
--8<-- "docs_src/apps/tutorial001_client.py"
|
||||
```
|
||||
|
||||
Servez `server.py` en HTTP, puis lancez le client depuis un second terminal :
|
||||
|
||||
```console
|
||||
uv run mcp run server.py --transport streamable-http
|
||||
```
|
||||
|
||||
```console
|
||||
python client.py
|
||||
```
|
||||
|
||||
```text
|
||||
2026-06-26T12:00:00Z
|
||||
```
|
||||
|
||||
La réponse riche est revenue. Retirez `extensions=[APPS_SUPPORT]` de l’appel à `Client` et le même programme affiche `The time is 2026-06-26T12:00:00Z.` à la place, c’est-à-dire tout ce qu’un client purement textuel voit jamais.
|
||||
|
||||
!!! warning
|
||||
Ne renvoyez jamais un texte de substitution comme `"[Rendered UI]"` pour seul contenu. Si le texte de repli est inutile, l’outil est inutile pour tout client purement textuel et pour le modèle lui-même. Écrivez la phrase.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [05891e7cc1938a13, b3c01a6af28c51ee, 7ffc91f5e38bdfe0, 717d3f235a8333a7, f471a13b2fe5d737, ed6af2df4b656dff]
|
||||
sections: [05891e7cc1938a13, b3c01a6af28c51ee, 6eba6ce094f4417e, 125f28588c85dd7b, ddd8969b698ca11c, ed6af2df4b656dff]
|
||||
tool: 1
|
||||
---
|
||||
# Extensions {#extensions}
|
||||
|
|
@ -49,7 +49,7 @@ Utilisez comme préfixe un domaine que vous contrôlez. `io.modelcontextprotocol
|
|||
|
||||
La plus petite extension utile, c’est un outil et une table de paramètres :
|
||||
|
||||
```python title="server.py" hl_lines="17 19-20 22-23 26"
|
||||
```python title="server.py" hl_lines="16 18-19 21-22 25"
|
||||
--8<-- "docs_src/extensions/tutorial003.py"
|
||||
```
|
||||
|
||||
|
|
@ -57,17 +57,23 @@ La plus petite extension utile, c’est un outil et une table de paramètres :
|
|||
* `settings()` est la valeur annoncée sous `capabilities.extensions["com.example/stamps"]`. Renvoyez `{}` (la valeur par défaut) pour annoncer l’extension sans paramètres.
|
||||
* L’extension ne reçoit jamais le serveur. Elle déclare ses contributions sous forme de données ; `MCPServer` les consomme. Il n’y a pas de `self.server` à modifier.
|
||||
|
||||
Et `main()` en est la preuve, un client en mémoire branché directement sur `mcp` :
|
||||
Servez-la en HTTP, et un client en est la preuve :
|
||||
|
||||
```python title="server.py" hl_lines="29-34"
|
||||
--8<-- "docs_src/extensions/tutorial003.py"
|
||||
```console
|
||||
uv run mcp run server.py --transport streamable-http
|
||||
```
|
||||
|
||||
```python title="client.py" hl_lines="7-11"
|
||||
--8<-- "docs_src/extensions/tutorial003_client.py"
|
||||
```
|
||||
|
||||
Chaque `server.py` de cette page est servi avec cette commande, et chaque `client.py` tourne à côté, lancé avec `python client.py` depuis un second terminal.
|
||||
|
||||
### Servir vos propres méthodes {#serving-your-own-methods}
|
||||
|
||||
Une extension peut enregistrer de **nouvelles méthodes de requête** : ses propres verbes, servis à côté de ceux de la spécification :
|
||||
|
||||
```python title="server.py" hl_lines="16-22 31 40-48"
|
||||
```python title="server.py" hl_lines="14-20 24 33-41"
|
||||
--8<-- "docs_src/extensions/tutorial004.py"
|
||||
```
|
||||
|
||||
|
|
@ -83,14 +89,15 @@ Les méthodes sont **strictement additives**. Le SDK le fait respecter à la con
|
|||
|
||||
### Le côté client {#the-client-side}
|
||||
|
||||
Le `main()` du même fichier raconte toute l’histoire côté client, ses deux moitiés :
|
||||
Le client est un programme à part entière, et il porte les deux moitiés de l’histoire côté client :
|
||||
|
||||
```python title="server.py" hl_lines="54-58"
|
||||
--8<-- "docs_src/extensions/tutorial004.py"
|
||||
```python title="client.py" hl_lines="21-23 27-30"
|
||||
--8<-- "docs_src/extensions/tutorial004_client.py"
|
||||
```
|
||||
|
||||
* `Client(..., extensions=[advertise(EXTENSION_ID)])` déclare l’extension. Les déclarations deviennent `ClientCapabilities.extensions` : sur une connexion 2026-07-28, la table voyage dans l’enveloppe `_meta` de chaque requête, donc le serveur la voit sur **chaque** requête ; sur une connexion historique, elle transite par la poignée de main `initialize`. Le code serveur ne s’en soucie pas : `require_client_extension(ctx, ...)` et `ctx.session.check_client_capability(...)` lisent la bonne source dans les deux cas.
|
||||
* Les méthodes propres à un éditeur descendent d’un niveau, vers `client.session.send_request(...)` ; `Client` n’acquiert de méthodes de premier rang que pour les verbes de la spécification. `send_request` accepte n’importe quelle sous-classe de `Request`, donc la requête de l’éditeur passe telle quelle.
|
||||
* `SearchRequest` et les deux modèles qu’elle transporte constituent le contrat de liaison de l’extension, donc le client les déclare de son côté. Une extension publiée les fournirait dans un paquet que les deux côtés importent.
|
||||
|
||||
### Intercepter `tools/call` {#intercepting-toolscall}
|
||||
|
||||
|
|
@ -109,12 +116,18 @@ Le hook enveloppe `tools/call` et rien d’autre. Pour ce qui concerne chaque me
|
|||
|
||||
## Utiliser une extension client {#using-a-client-extension}
|
||||
|
||||
Une **extension client**, c’est le même contrat vu du côté consommateur : un ensemble de comportements côté client derrière un seul identifiant. Passez des instances à `Client(extensions=[...])` et appelez les outils normalement :
|
||||
Une **extension client**, c’est le même contrat vu du côté consommateur : un ensemble de comportements côté client derrière un seul identifiant. Ici, le serveur répond à `buy` par un reçu à échanger au lieu de la marchandise, et seulement pour un client qui a déclaré l’extension :
|
||||
|
||||
```python title="client.py" hl_lines="66-68"
|
||||
```python title="server.py" hl_lines="22-25"
|
||||
--8<-- "docs_src/extensions/tutorial006.py"
|
||||
```
|
||||
|
||||
Côté client, passez des instances à `Client(extensions=[...])` et appelez les outils normalement :
|
||||
|
||||
```python title="client.py" hl_lines="33-35"
|
||||
--8<-- "docs_src/extensions/tutorial006_client.py"
|
||||
```
|
||||
|
||||
`call_tool("buy", ...)` renvoie un simple `CallToolResult`, comme tout autre appel. Ce que l’extension a changé : le serveur peut désormais répondre à `buy` par une **forme de résultat** `receipt` au lieu d’un résultat final, et `Receipts` la termine (ici en échangeant le reçu via un appel de suivi) avant que `call_tool` ne renvoie. Rien ne bouge au point d’appel.
|
||||
|
||||
Retirez l’extension et rien de tout cela n’existe : le garde-fou du serveur refuse un client qui ne l’a pas déclarée (erreur -32021), et une forme revendiquée venant d’un serveur qui saute le garde-fou échoue à la validation, exactement comme la spécification l’exige pour un `resultType` non reconnu. Désactivé par défaut, aux deux bouts de la liaison.
|
||||
|
|
@ -124,15 +137,15 @@ Pour annoncer un identifiant **sans aucun** comportement côté client (le serve
|
|||
```python
|
||||
from mcp.client import advertise
|
||||
|
||||
client = Client(mcp, extensions=[advertise("com.example/search")])
|
||||
client = Client("http://localhost:8000/mcp", extensions=[advertise("com.example/search")])
|
||||
```
|
||||
|
||||
## Écrire une extension client {#writing-a-client-extension}
|
||||
|
||||
Dérivez `ClientExtension` et ne redéfinissez que ce dont vous avez besoin. Trois types de contributions, chacun avec une valeur par défaut : `settings()`, `claims()` et `notifications()`.
|
||||
|
||||
```python title="client.py" hl_lines="17-18 43-44 46-47"
|
||||
--8<-- "docs_src/extensions/tutorial006.py"
|
||||
```python title="client.py" hl_lines="16-17 25-26 28-29"
|
||||
--8<-- "docs_src/extensions/tutorial006_client.py"
|
||||
```
|
||||
|
||||
* L’identifiant suit la même grammaire que celui du serveur, validé au moment où la classe est définie.
|
||||
|
|
@ -153,12 +166,18 @@ Deux règles discrètes. Les claims ne sont actifs que sur les connexions 2026-0
|
|||
|
||||
### Verbes d’extension {#extension-verbs}
|
||||
|
||||
Les méthodes de requête propres à une extension n’ont besoin d’aucun enregistrement côté client. Un type de requête d’éditeur dérive de `mcp.types.Request` et passe par `client.session.send_request`, comme dans [Servir vos propres méthodes](#serving-your-own-methods). Un ajout : lorsqu’une clé des paramètres doit transiter par l’en-tête `Mcp-Name` (des spécifications d’extension comme tasks l’exigent pour leurs verbes), le type de requête déclare `name_param` :
|
||||
Les méthodes de requête propres à une extension n’ont besoin d’aucun enregistrement côté client. Un type de requête d’éditeur dérive de `mcp.types.Request` et passe par `client.session.send_request`, comme dans [Servir vos propres méthodes](#serving-your-own-methods). Prenez un serveur dont l’extension sert un seul verbe portant sur un job nommé :
|
||||
|
||||
```python title="client.py" hl_lines="22-25 46-47"
|
||||
```python title="server.py" hl_lines="12-13 30"
|
||||
--8<-- "docs_src/extensions/tutorial007.py"
|
||||
```
|
||||
|
||||
Un ajout côté client : lorsqu’une clé des paramètres doit transiter par l’en-tête `Mcp-Name` (des spécifications d’extension comme tasks l’exigent pour leurs verbes), le type de requête déclare `name_param` :
|
||||
|
||||
```python title="client.py" hl_lines="20-23 28-29"
|
||||
--8<-- "docs_src/extensions/tutorial007_client.py"
|
||||
```
|
||||
|
||||
La session reflète `params["jobId"]` dans `Mcp-Name` sur chaque chemin d’envoi, et une valeur manquante échoue bruyamment au lieu d’omettre silencieusement un en-tête obligatoire.
|
||||
|
||||
## Ce qu’une extension ne peut pas faire {#what-an-extension-cannot-do}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [2c79b6338e09b7ac, 7edc43b3fae11314, 1086e77ce561cd7f, a3f71823df5efc31, 9fc7109f72201cae, d50fe7faead8cf68, 7bf25983df655b66, 6330e1f4c6029683, 2f1749c8c133fa1c, 8db7116fc8ddd0ee, ebc33704fbd74262, cd0e9c933350390e]
|
||||
sections: [2c79b6338e09b7ac, 9d5d10a5f0405d0a, 1086e77ce561cd7f, a3f71823df5efc31, 9fc7109f72201cae, d50fe7faead8cf68, 7bf25983df655b66, 6330e1f4c6029683, 2f1749c8c133fa1c, 8db7116fc8ddd0ee, ebc33704fbd74262, 0fde3bcea081ba3a]
|
||||
tool: 1
|
||||
---
|
||||
# Le Server de bas niveau {#the-low-level-server}
|
||||
|
|
@ -36,18 +36,22 @@ Trois choses ont changé, et elles constituent toute l’API de bas niveau :
|
|||
|
||||
### Essayer {#try-it}
|
||||
|
||||
Pas d’Inspector pour celui-ci : `mcp dev` et `mcp run` n’acceptent qu’un `MCPServer`. Le `Client` en mémoire s’en moque ; il accepte un `Server` de bas niveau exactement comme il accepte un `MCPServer` :
|
||||
`mcp dev` et `mcp run` n’acceptent qu’un `MCPServer`, vous servez donc celui-ci vous-même. La dernière ligne de `server.py` construit à partir de lui une application ASGI ordinaire, et uvicorn l’exécute :
|
||||
|
||||
```python title="main.py"
|
||||
```console
|
||||
uvicorn server:app --port 8000
|
||||
```
|
||||
|
||||
Pointez l’Inspector, ou n’importe quel client, vers `http://localhost:8000/mcp` :
|
||||
|
||||
```python title="client.py"
|
||||
import asyncio
|
||||
|
||||
from mcp import Client
|
||||
|
||||
from server import server
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
async with Client(server) as client:
|
||||
async with Client("http://localhost:8000/mcp") as client:
|
||||
result = await client.call_tool("search_books", {"query": "dune", "limit": 5})
|
||||
print(result.content)
|
||||
|
||||
|
|
@ -64,6 +68,8 @@ Le même texte que celui produit par la version `@mcp.tool()`. Deux différences
|
|||
* `result.structured_content` vaut `None`. Le serveur de haut niveau enveloppe pour vous un `-> str` dans `{"result": ...}` ; ici, personne ne construit ce que vous n’avez pas construit.
|
||||
* `list_tools` renvoie le schéma que **vous** avez saisi, caractère pour caractère. La version de haut niveau avait `"title": "Query"` sur chaque propriété et un `"title": "search_booksArguments"` à la racine : des artefacts de Pydantic. À ce niveau, si quelque chose est sur la liaison, c’est vous qui l’y avez mis.
|
||||
|
||||
Dans un test, vous vous passez d’uvicorn et du port : `Client(server)` accepte un `Server` de bas niveau dans le même processus exactement comme il accepte un `MCPServer`, et **[Tests](../get-started/testing.md)** décrit précisément ce schéma.
|
||||
|
||||
## Rien n’est vérifié pour vous {#nothing-is-checked-for-you}
|
||||
|
||||
`MCPServer` rejette un mauvais argument avant même que votre fonction s’exécute, en validant l’appel par rapport au schéma qu’il a généré (**[Outils](../servers/tools.md)**).
|
||||
|
|
@ -215,4 +221,4 @@ Chacun d’eux correspond à une idée pour laquelle vous avez désormais le voc
|
|||
* `add_request_handler(method, params_type, handler)` sert n’importe quelle méthode. `initialize` est réservée.
|
||||
* Les capacités qu’annonce un `Server` découlent des gestionnaires que vous avez enregistrés.
|
||||
|
||||
`Client(server)` a traité les deux serveurs de façon identique parce qu’ils *sont* le même protocole, et c’est tout l’intérêt. La couche suivante vers le bas n’est pas une classe du tout : c’est le **[Middleware](middleware.md)**.
|
||||
Le client a traité les deux serveurs de façon identique parce qu’ils *sont* le même protocole, et c’est tout l’intérêt. La couche suivante vers le bas n’est pas une classe du tout : c’est le **[Middleware](middleware.md)**.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [a9aba7a026c7bd85, ed32bda7ba9ae33a, 7e64cc5646abb91f, 22a0129ee78b3c63, d875373c06d8d2f9]
|
||||
sections: [a9aba7a026c7bd85, 83e2a08b9d46a398, 9fd8a0aa384b3257, 22a0129ee78b3c63, d875373c06d8d2f9]
|
||||
tool: 1
|
||||
---
|
||||
# Pagination {#pagination}
|
||||
|
|
@ -31,9 +31,13 @@ La pagination sert au serveur dont la liste de ressources est en réalité une b
|
|||
|
||||
### Essayer {#try-it}
|
||||
|
||||
`Client(server)` se connecte à un `Server` de bas niveau en mémoire exactement comme il se connecte à un `MCPServer`.
|
||||
`mcp run` n’accepte qu’un `MCPServer`, vous servez donc celui-ci vous-même. La dernière ligne de `server.py` construit une application ASGI ordinaire à partir du `Server`, et uvicorn l’exécute :
|
||||
|
||||
Appelez `list_resources()` sans argument. Vous obtenez dix ressources, de `book-1` à `book-10`, et `next_cursor` vaut la chaîne `"10"`.
|
||||
```console
|
||||
uvicorn server:app --port 8000
|
||||
```
|
||||
|
||||
Pointez n’importe quel client (**[Le client](../client/index.md)**, ou l’Inspector) vers `http://localhost:8000/mcp` et appelez `list_resources()` sans argument. Vous obtenez dix ressources, de `book-1` à `book-10`, et `next_cursor` vaut la chaîne `"10"`.
|
||||
|
||||
Renvoyez-la avec `list_resources(cursor="10")` : la première ressource est `book-11`, le nouveau `next_cursor` vaut `"20"`.
|
||||
|
||||
|
|
@ -43,7 +47,7 @@ La dixième page revient avec `next_cursor` à `None`. Terminé.
|
|||
|
||||
Chaque méthode `list_*` de `Client` (`list_tools`, `list_resources`, `list_resource_templates`, `list_prompts`) accepte un argument nommé `cursor=`. Vider une liste paginée tient en un `while True` :
|
||||
|
||||
```python title="client.py" hl_lines="26-32"
|
||||
```python title="client.py" hl_lines="9-15"
|
||||
--8<-- "docs_src/pagination/tutorial002.py"
|
||||
```
|
||||
|
||||
|
|
@ -51,7 +55,7 @@ Chaque méthode `list_*` de `Client` (`list_tools`, `list_resources`, `list_reso
|
|||
* Étendez la liste **avant** de regarder `next_cursor` : la dernière page contient elle aussi des ressources.
|
||||
* `next_cursor is None` est la sortie. Toute autre valeur repart directement dans `cursor=`, telle quelle.
|
||||
|
||||
Lancez son `main()` et il affiche `100 resources` : dix pages de dix, assemblées par une boucle qui n’a jamais su qu’il y avait dix pages.
|
||||
Pendant qu’uvicorn sert toujours `server.py`, lancez `python client.py` dans un second terminal. Il affiche `100 resources` : dix pages de dix, assemblées par une boucle qui n’a jamais su qu’il y avait dix pages.
|
||||
|
||||
C’est la même boucle que montre **[Le client](../client/index.md)** pour chaque verbe `list_*`, et elle ne coûte rien face à un serveur qui ne pagine pas : `next_cursor` vaut `None` dès la première réponse et la boucle s’exécute une seule fois.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [9e7b9a1710e5aeba, b74ca4c1d2ddddee, fa8714e61bf90c5a, 04db67a886b7271c, 857690fb8f876800]
|
||||
sections: [9e7b9a1710e5aeba, 66a2e9acc9101d54, d3d25aa802f5145a, 04db67a886b7271c, 857690fb8f876800]
|
||||
tool: 1
|
||||
---
|
||||
# Indications de mise en cache {#caching-hints}
|
||||
|
|
@ -30,7 +30,7 @@ Par défaut, chaque résultat indique `ttlMs: 0, cacheScope: "private"` : périm
|
|||
|
||||
Sur le `Server` bas niveau, les gestionnaires (handlers) construisent leurs résultats à la main, et `ttl_ms` / `cache_scope` sont de simples champs des modèles de résultat. Un gestionnaire qui les définit explicitement l’emporte toujours sur le dictionnaire du constructeur, champ par champ :
|
||||
|
||||
```python title="server.py" hl_lines="10 16"
|
||||
```python title="server.py" hl_lines="11 17"
|
||||
--8<-- "docs_src/caching/tutorial002.py"
|
||||
```
|
||||
|
||||
|
|
@ -44,10 +44,24 @@ Une réserve sur les listes paginées : le protocole exige **le même `cacheScop
|
|||
|
||||
Sur une session 2026-07-28, `Client` respecte les indications pour vous : il embarque un cache de réponses, activé par défaut. Un résultat qui arrive avec un `ttlMs` est stocké, et un appel identique effectué dans ce TTL est servi depuis le cache, sans aller-retour. Un résultat qui ne porte *aucune* indication n’est pas mis en cache : les résultats sans indication reçoivent `CacheConfig.default_ttl_ms`, dont la valeur par défaut est `0` (périmé immédiatement), si bien qu’un serveur qui ne déclare rien voit exactement le même trafic, appel pour appel, qu’auparavant.
|
||||
|
||||
```python title="client.py" hl_lines="33 35 38"
|
||||
Pour le voir à l’œuvre, servez avec uvicorn le `server.py` de la section précédente (sa dernière ligne construit l’application ASGI). Le gestionnaire affiche une ligne chaque fois qu’il s’exécute réellement :
|
||||
|
||||
```console
|
||||
uvicorn server:app --port 8000
|
||||
```
|
||||
|
||||
```python title="client.py" hl_lines="20 23 28"
|
||||
--8<-- "docs_src/caching/tutorial003.py"
|
||||
```
|
||||
|
||||
Lancez `python client.py` depuis un second terminal. Il affiche les indications que portait le premier résultat, le `ttlMs` du gestionnaire à côté du `cacheScope` du dictionnaire :
|
||||
|
||||
```text
|
||||
1000 public
|
||||
```
|
||||
|
||||
Le terminal du serveur raconte la suite : entre les journaux de requêtes d’uvicorn, `tools/list served` apparaît trois fois.
|
||||
|
||||
Quatre appels, trois récupérations. Le deuxième appel a trouvé une entrée fraîche et n’a jamais atteint le serveur ; avancer l’horloge (injectée) au-delà du TTL a fait que le troisième récupère à nouveau ; le quatrième a indiqué `cache_mode="refresh"`. Cet argument nommé existe sur les cinq verbes avec cache (`list_tools`, `list_prompts`, `list_resources`, `list_resource_templates`, `read_resource`) :
|
||||
|
||||
* `"use"` (la valeur par défaut) sert une entrée fraîche s’il y en a une, et stocke le résultat récupéré sinon.
|
||||
|
|
@ -56,7 +70,7 @@ Quatre appels, trois récupérations. Le deuxième appel a trouvé une entrée f
|
|||
|
||||
Une règle prime sur `"use"` : **les appels portant `meta` atteignent toujours le serveur.** Une requête avec `meta` défini (un jeton de progression, des champs de traçage) attend une requête sur la liaison ; sous `cache_mode="use"`, elle est donc traitée comme `"refresh"` : la lecture du cache est sautée, et le résultat récupéré remplace quand même l’entrée en cache. `"bypass"` et un `"refresh"` explicite se comportent comme d’habitude.
|
||||
|
||||
Pour désactiver entièrement la mise en cache, construisez avec `Client(server, cache=None)` : chaque appel redevient un aller-retour, et `cache_mode`, bien que toujours accepté, n’a aucun effet.
|
||||
Pour désactiver entièrement la mise en cache, passez `cache=None` à la construction du `Client` : chaque appel redevient un aller-retour, et `cache_mode`, bien que toujours accepté, n’a aucun effet.
|
||||
|
||||
La portée est elle aussi respectée automatiquement : les entrées `"private"` sont indexées sur la *partition* du cache (ci-dessous), tandis que les entrées `"public"` peuvent opter pour un partage plus large. Et **les notifications priment sur le TTL** pour les entrées exactes qu’elles désignent : une notification `list_changed` évince la liste correspondante en cache, et `resources/updated` évince la lecture en cache stockée exactement sous son URI, aussi fraîches soient-elles. Sur une connexion 2026-07-28, ces notifications arrivent sur un flux `subscriptions/listen` que vous ouvrez avec `client.listen(...)`, et l’éviction se termine avant que votre observateur ne voie l’événement ; tous les détails sont dans **[Abonnements](subscriptions.md)**.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [adf3c545b5be46b6, 916cd3ab1c03f461, e9be7a8d0eb0a456, 565890a636288ecf, 6af7e49db9129ec3, 06b0238c174186af, 90c6043be435fcb0]
|
||||
sections: [adf3c545b5be46b6, 916cd3ab1c03f461, 32ef568335dd95a7, 565890a636288ecf, 6af7e49db9129ec3, 06b0238c174186af, 0abc5ea5cb7ff6b3]
|
||||
tool: 1
|
||||
---
|
||||
# Fonctions de rappel du client {#client-callbacks}
|
||||
|
|
@ -63,7 +63,7 @@ Un `tools/call` de votre part, un `elicitation/create` en retour du serveur, auq
|
|||
`mode="legacy"` dans l’appel `Client(...)` fait un vrai travail. Par défaut, `Client(...)` négocie le chemin
|
||||
moderne du protocole, et ce chemin n’a pas de canal de retour (back-channel) pour les requêtes du serveur vers le client : `ctx.elicit`
|
||||
échoue avant même que votre fonction de rappel ne s’exécute. Ce n’est pas le transport qui en décide ; c’est le
|
||||
protocole négocié, en mémoire comme via une URL. Fixez `mode="legacy"` dès que votre client doit
|
||||
protocole négocié. Fixez `mode="legacy"` dès que votre client doit
|
||||
répondre à l’une d’elles ; tous les tests derrière cette page le font. Tous les détails sont dans **[Versions du protocole](../protocol-versions.md)**.
|
||||
|
||||
Sur une session 2026-07-28, la fonction de rappel n’est pas morte, elle est alimentée autrement : quand un outil renvoie un
|
||||
|
|
@ -151,4 +151,4 @@ Deux de plus. Aucune ne déclare quoi que ce soit.
|
|||
* `sampling_callback` et `list_roots_callback` fonctionnent de la même manière mais servent des fonctionnalités obsolètes ; les serveurs modernes utilisent à la place les requêtes à plusieurs allers-retours.
|
||||
* `logging_callback` et `message_handler` reçoivent des notifications. Ils ne déclarent rien.
|
||||
|
||||
Le premier argument de `Client(...)` est un objet transport. **[Transports client](transports.md)** couvre tous les types.
|
||||
Le premier argument de `Client(...)` choisit le transport. **[Transports client](transports.md)** couvre tous les types.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [ebef1e7a0df854f4, 8355cfaf1f76c9d5, 8e79141fc2985342, 46bdb07c7537e8a5, 80ce41579825a6fa, 5f0fa90494de8f65, 83d10514eaa62fa5, 9190555aa39a5d28, 84a4c9d8bf14dddb, 927d71cf40b58c30]
|
||||
sections: [ebef1e7a0df854f4, 7e16449f66e7dfd6, eeb0682f7d2a1079, 5713f0196a34e6e7, 0e844597859e4248, 3a97d9195ddcc92e, 1da08c483e59c141, 84702cc6e0a1fd42, 8dee7a31c86ffc5c, 83a5bce168ef23d7]
|
||||
tool: 1
|
||||
---
|
||||
# Le client {#the-client}
|
||||
|
|
@ -11,13 +11,23 @@ C’est un seul objet avec un seul cycle de vie : vous le construisez, vous entr
|
|||
|
||||
## Votre premier client {#your-first-client}
|
||||
|
||||
```python title="client.py" hl_lines="14-18"
|
||||
Un client a besoin d’un serveur avec qui dialoguer. Ce serveur Bookshop est celui auquel se connecte chaque extrait de cette page. Enregistrez-le sous le nom `server.py` et laissez-le tourner en HTTP :
|
||||
|
||||
```python title="server.py"
|
||||
--8<-- "docs_src/client/tutorial001.py"
|
||||
```
|
||||
|
||||
Le serveur en haut n’est là que pour vous donner quelque chose à quoi vous connecter. Le client, ce sont les cinq lignes surlignées.
|
||||
```console
|
||||
uv run mcp run server.py --transport streamable-http
|
||||
```
|
||||
|
||||
* `Client(mcp)` reçoit **l’objet serveur lui-même**. C’est le transport en mémoire : pas de sous-processus, pas de port, pas de HTTP. C’est ainsi que se connectent tous les exemples de cette page, et tous les tests que vous écrivez.
|
||||
Cela le sert à l’adresse `http://localhost:8000/mcp`. Le client est un programme à part. Enregistrez-le sous le nom `client.py` et lancez `python client.py` dans un second terminal :
|
||||
|
||||
```python title="client.py" hl_lines="7-11"
|
||||
--8<-- "docs_src/client/tutorial001_client.py"
|
||||
```
|
||||
|
||||
* `Client("http://localhost:8000/mcp")` reçoit une **URL**, il se connecte donc en Streamable HTTP au serveur que vous venez de démarrer.
|
||||
* `async with` est le **cycle de vie**. Y entrer connecte et négocie ; en sortir déconnecte. Il n’y a pas de paire `connect()` / `close()`, et un `Client` ne peut pas être réutilisé une fois le bloc terminé.
|
||||
* À l’intérieur du bloc, les informations de connexion sont déjà là, sous forme de simples propriétés.
|
||||
|
||||
|
|
@ -25,10 +35,10 @@ Le serveur en haut n’est là que pour vous donner quelque chose à quoi vous c
|
|||
|
||||
`Client` prend un seul argument positionnel et déduit le transport de son type :
|
||||
|
||||
* 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 `StdioServerParameters` : la commande à lancer en **sous-processus**, avec laquelle le client dialogue via son stdin et son stdout.
|
||||
* Une chaîne d’URL (`Client("http://localhost:8000/mcp")`) : Streamable HTTP, le transport derrière lequel vous déployez.
|
||||
* Un `StdioServerParameters` : la commande à lancer comme **sous-processus** local, 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.
|
||||
* Une instance de `MCPServer` (ou du `Server` bas niveau) : connexion **dans le processus**, sans sous-processus ni port. Celle-ci sert aux tests, et **[Tests](../get-started/testing.md)** s’appuie dessus.
|
||||
|
||||
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)**.
|
||||
|
||||
|
|
@ -49,11 +59,11 @@ Vous n’avez jamais choisi de version du protocole. Par défaut, le `Client` so
|
|||
|
||||
## Lister les outils {#listing-tools}
|
||||
|
||||
```python title="client.py" hl_lines="15-20"
|
||||
```python title="client.py" hl_lines="8-13"
|
||||
--8<-- "docs_src/client/tutorial002.py"
|
||||
```
|
||||
|
||||
`list_tools()` renvoie un `ListToolsResult` ; les outils sont dans `.tools`. Chacun est la définition complète qu’un hôte transmettrait à un modèle :
|
||||
`list_tools()` renvoie un `ListToolsResult` ; les outils sont dans `.tools`. Chacun est la définition complète qu’un hôte transmettrait à un modèle. Voici le premier :
|
||||
|
||||
```python
|
||||
tool.name # 'search_books'
|
||||
|
|
@ -77,6 +87,8 @@ et `tool.input_schema` est le JSON Schema que le serveur a dérivé des annotati
|
|||
|
||||
Ce schéma est tout ce dont une interface a besoin pour afficher un formulaire d’arguments, et tout ce dont un modèle a besoin pour produire des arguments valides.
|
||||
|
||||
Le second outil, `lookup_book`, a été enregistré sans `title=`, donc son `tool.title` vaut `None`.
|
||||
|
||||
!!! tip
|
||||
`title` est facultatif, donc une interface qui présente des outils à un humain doit choisir : le `title` s’il existe,
|
||||
le `name` sinon. `from mcp.shared.metadata_utils import get_display_name` fait exactement cela,
|
||||
|
|
@ -86,7 +98,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="27-34"
|
||||
```python title="client.py" hl_lines="9-16"
|
||||
--8<-- "docs_src/client/tutorial003.py"
|
||||
```
|
||||
|
||||
|
|
@ -142,7 +154,7 @@ Un outil qui lève une exception ne lève **rien** dans votre client. Il revient
|
|||
|
||||
Les verbes des ressources vont par paires : deux façons de lister, une façon de lire.
|
||||
|
||||
```python title="client.py" hl_lines="22-31"
|
||||
```python title="client.py" hl_lines="9-18"
|
||||
--8<-- "docs_src/client/tutorial004.py"
|
||||
```
|
||||
|
||||
|
|
@ -156,7 +168,7 @@ Un client peut aussi être prévenu quand une ressource change. Sur les connexio
|
|||
|
||||
## Prompts {#prompts}
|
||||
|
||||
```python title="client.py" hl_lines="15-20"
|
||||
```python title="client.py" hl_lines="8-13"
|
||||
--8<-- "docs_src/client/tutorial005.py"
|
||||
```
|
||||
|
||||
|
|
@ -181,7 +193,7 @@ Un hôte transmet ces messages tels quels au modèle. C’est toute la fonctionn
|
|||
|
||||
Un serveur doté d’un gestionnaire (handler) de complétion peut compléter automatiquement les arguments des prompts et des modèles de ressource au fil de la saisie de l’utilisateur.
|
||||
|
||||
```python title="client.py" hl_lines="27-31"
|
||||
```python title="client.py" hl_lines="9-13"
|
||||
--8<-- "docs_src/client/tutorial006.py"
|
||||
```
|
||||
|
||||
|
|
@ -194,21 +206,21 @@ La réponse se trouve dans `result.completion.values`. Tapez `"p"` et le serveur
|
|||
|
||||
Chaque méthode `list_*` accepte un argument nommé `cursor=` et chaque résultat porte un `next_cursor`. Quand `next_cursor` vaut `None`, vous avez tout.
|
||||
|
||||
```python title="client.py" hl_lines="22-30"
|
||||
```python title="client.py" hl_lines="7-15"
|
||||
--8<-- "docs_src/client/tutorial007.py"
|
||||
```
|
||||
|
||||
Cette boucle est correcte face à n’importe quel serveur. `MCPServer` renvoie tout en une seule page, donc `next_cursor` vaut `None` et la boucle s’exécute une fois, ce qui explique que la plupart du code ne l’écrive jamais. Les serveurs qui paginent réellement, et les règles auxquelles obéissent les curseurs, sont dans **[Pagination](../advanced/pagination.md)**.
|
||||
La fonction `list_all_tools` est correcte face à n’importe quel serveur. `MCPServer` renvoie tout en une seule page, donc `next_cursor` vaut `None` et la boucle s’exécute une fois, ce qui explique que la plupart du code ne l’écrive jamais. Les serveurs qui paginent réellement, et les règles auxquelles obéissent les curseurs, sont dans **[Pagination](../advanced/pagination.md)**.
|
||||
|
||||
## Dans les tests {#in-tests}
|
||||
|
||||
`Client(mcp)`, sans processus ni port, est déjà un banc de test pour votre serveur.
|
||||
Chaque `client.py` de cette page a atteint `server.py` en HTTP. Dans un test, vous vous passez du réseau et donnez à `Client` l’objet serveur lui-même : `from server import mcp`, puis `Client(mcp)`. Pas de processus, pas de port, et chaque méthode ci-dessus fonctionne de la même façon.
|
||||
|
||||
Il existe un drapeau du constructeur conçu pour cela : `Client(mcp, raise_exceptions=True)`. Il n’a d’effet que sur les connexions en mémoire, et **[Tests](../get-started/testing.md)** est la page qui l’explique et construit tout le modèle autour de lui.
|
||||
Il existe un drapeau du constructeur conçu pour cela : `Client(mcp, raise_exceptions=True)`. Il n’a d’effet que sur les connexions dans le processus, et **[Tests](../get-started/testing.md)** est la page qui l’explique et construit tout le modèle autour de lui.
|
||||
|
||||
## Récapitulatif {#recap}
|
||||
|
||||
* `Client(x)` se connecte en mémoire à un objet serveur, en Streamable HTTP à une chaîne d’URL, et à tout le reste via un transport.
|
||||
* `Client(x)` se connecte en Streamable HTTP à une chaîne d’URL, lance un sous-processus pour un `StdioServerParameters`, entre directement dans un transport et, dans les tests, prend l’objet serveur lui-même.
|
||||
* `async with` est tout le cycle de vie. À l’intérieur, `server_capabilities` et `protocol_version` sont déjà renseignés ; `server_info` et `instructions` le sont aussi lorsque le serveur les fournit.
|
||||
* `list_tools()` vous donne le `name`, le `title`, la `description` et le `input_schema` de chaque outil.
|
||||
* `call_tool()` renvoie `content` pour le modèle, `structured_content` pour votre code, et `is_error`. Un outil qui lève une exception est un résultat, pas une exception.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [c6899d3892bd9fa0, 79372cff3cc48a88, 63878d29e87c3e73, 13175843d3588af4, e7e2b9fd516f77de, 758f06399b513c1f, a05d7278487d610b]
|
||||
sections: [c6899d3892bd9fa0, 79372cff3cc48a88, c2dae1ebe2ebd543, 13175843d3588af4, df06056fb16b3846, 758f06399b513c1f, a05d7278487d610b]
|
||||
tool: 1
|
||||
---
|
||||
# Clients OAuth {#oauth-clients}
|
||||
|
|
@ -81,18 +81,20 @@ Regardez `main()`. Le fournisseur va sur le **client httpx2**, le client httpx2
|
|||
|
||||
La première fois que `Client` envoie une requête, le serveur répond `401`. Le fournisseur prend le relais :
|
||||
|
||||
1. **Découverte.** Il lit l’en-tête `WWW-Authenticate`, récupère les Protected Resource Metadata du serveur depuis `/.well-known/oauth-protected-resource`, apprend quel serveur d’autorisation protège cette ressource, et récupère les métadonnées de *ce* serveur-là.
|
||||
1. **Découverte.** Il lit l’en-tête `WWW-Authenticate`, récupère les Protected Resource Metadata du serveur depuis `/.well-known/oauth-protected-resource`, apprend quel serveur d’autorisation protège cette ressource, et récupère les métadonnées de *ce* serveur-là. (Un serveur plus ancien qui ne publie aucune métadonnée de ressource se voit demander, à la place, les métadonnées de serveur d’autorisation à sa propre origine.) Dans les deux cas, les métadonnées doivent désigner, comme `issuer`, le serveur pour lequel elles ont été récupérées ; tout autre cas est refusé.
|
||||
2. **Enregistrement.** Rien dans le stockage ? Il vous enregistre dynamiquement avec votre `OAuthClientMetadata` et stocke le résultat.
|
||||
3. **Autorisation.** Il génère la paire PKCE et un `state`, construit l’URL d’autorisation, attend votre `redirect_handler`, puis attend votre `callback_handler` pour obtenir le code.
|
||||
4. **Échange.** Il échange le code contre un `OAuthToken`, le stocke, et rejoue votre requête d’origine avec `Authorization: Bearer ...`.
|
||||
|
||||
Après cela, il se fait discret. Les jetons sortent du stockage, un jeton d’accès expiré est actualisé avec le jeton d’actualisation, et ce n’est que lorsque rien de tout cela ne fonctionne qu’il relance le flux.
|
||||
|
||||
Une règle de transport s’applique à toutes ces requêtes : comme la requête MCP à l’intérieur de laquelle elles s’exécutent, elles ne suivent une redirection que si celle-ci reste sur la même origine et conserve la méthode (un 307/308 pour une barre oblique finale, par exemple), et traitent toute autre redirection comme si cette URL ne répondait pas.
|
||||
|
||||
Vous n’avez rien écrit de tout cela. Il reste deux arguments nommés (`client_metadata_url` et `validate_resource_url`), et ce fichier n’a besoin d’aucun des deux. `client_metadata_url` est celui qui mérite d’être connu ; il a sa propre section plus bas.
|
||||
|
||||
### Essayer {#try-it}
|
||||
|
||||
La plupart des exemples de cette documentation se vérifient avec un `Client(server)` en mémoire. Pas celui-ci : tout l’intérêt du flux est un `401` HTTP, et il n’y a pas de HTTP entre un client en mémoire et son serveur.
|
||||
Le `Client(server)` en mémoire qu’utilisent vos tests n’est d’aucune aide ici : tout l’intérêt du flux est un `401` HTTP, et il n’y a pas de HTTP entre un client en mémoire et son serveur.
|
||||
|
||||
Le dépôt fournit la version réelle. `examples/servers/simple-auth/` exécute un serveur d’autorisation autonome et un serveur MCP protégé ; `examples/clients/simple-auth-client/` est le client de cette page devenu une petite CLI. Son README donne les deux commandes : démarrez les serveurs, lancez le client contre eux, et vous voyez défiler les quatre étapes.
|
||||
|
||||
|
|
@ -110,13 +112,14 @@ Une tâche nocturne, une étape de CI, un autre service. Il n’y a pas de navig
|
|||
|
||||
`ClientCredentialsOAuthProvider` est le même `httpx2.Auth`, l’humain en moins :
|
||||
|
||||
```python title="client.py" hl_lines="4 27-33"
|
||||
```python title="client.py" hl_lines="4 27-34"
|
||||
--8<-- "docs_src/oauth_clients/tutorial002.py"
|
||||
```
|
||||
|
||||
Ce qui a changé :
|
||||
|
||||
* Aucun `OAuthClientMetadata`, aucun gestionnaire. Vous passez `client_id` et `client_secret` ; le fournisseur construit autour d’eux un enregistrement `client_credentials` minimal et saute entièrement l’enregistrement dynamique.
|
||||
* `issuer` désigne le serveur d’autorisation qui a émis ces identifiants ; utilisez la valeur `issuer` que renvoie son document `/.well-known/oauth-authorization-server`. La découverte se déroule toujours comme ci-dessus, mais les requêtes de jeton ne sont jamais construites qu’à partir des métadonnées de *cet* émetteur-là ; si le serveur MCP pointe ailleurs, le flux s’arrête alors avec une `OAuthFlowError`. L’omettre est obsolète et il devient obligatoire en 3.0 (voir **[Fonctionnalités obsolètes](../deprecated.md#deprecated-sdk-helpers)**) ; d’ici là, le fournisseur émet un avertissement et utilise le serveur d’autorisation que la découverte trouve, quel qu’il soit.
|
||||
* `scope` est une chaîne séparée par des espaces, le format qu’OAuth utilise sur la liaison.
|
||||
* Tout ce qui se trouve en aval est identique : le même `TokenStorage`, le même `httpx2.AsyncClient(auth=...)`, le même `streamable_http_client`.
|
||||
|
||||
|
|
@ -129,7 +132,7 @@ Par défaut, le secret voyage en authentification HTTP Basic sur la requête de
|
|||
Un fournisseur de plus se trouve dans `mcp.client.auth.extensions.client_credentials` :
|
||||
**`PrivateKeyJWTOAuthProvider`**, pour les clients qui s’authentifient avec un JWT plutôt qu’avec un
|
||||
secret partagé (`private_key_jwt`, la variante à paire de clés et identité de charge de travail). Il suit
|
||||
le même schéma : construisez-en un, placez-le sur `auth=`. Le même module fournit
|
||||
le même schéma : construisez-en un (il accepte le même `issuer` optionnel), placez-le sur `auth=`. Le même module fournit
|
||||
`SignedJWTParameters` et `static_assertion_provider`, deux utilitaires qui construisent son assertion.
|
||||
|
||||
Il existe une autre situation sans humain : le client appartient à une entreprise dont le fournisseur d’identité, et non l’utilisateur, décide quels serveurs MCP il peut atteindre. C’est un type d’octroi différent, avec son propre modèle de confiance et sa propre page, **[Assertion d’identité](identity-assertion.md)**.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [09c857a25a9dc37a, 43bc6a76a243a50e, 0a716022a88768df, 4b7f78042bfcfff7, c112662e61b03315, 58974ba1f489a8b4, d18adbdbb835ea73]
|
||||
sections: [09c857a25a9dc37a, 43bc6a76a243a50e, 0a716022a88768df, 4b7f78042bfcfff7, c112662e61b03315, 58974ba1f489a8b4, ed4d17e894864056]
|
||||
tool: 1
|
||||
---
|
||||
# Groupes de sessions {#session-groups}
|
||||
|
|
@ -78,7 +78,7 @@ Si vous détenez déjà une `ClientSession` connectée (`Client.session` en est
|
|||
## Récapitulatif {#recap}
|
||||
|
||||
* `ClientSessionGroup` détient de nombreuses connexions serveur et fusionne leurs outils, ressources et prompts en un `dict` chacun.
|
||||
* `connect_to_server(params)` par serveur. Il prend des paramètres de transport, jamais l’objet serveur ni l’URL que prend un `Client`.
|
||||
* `connect_to_server(params)` par serveur. Il prend des paramètres de transport, jamais l’URL ni le `Transport` que prend un `Client`.
|
||||
* `group.call_tool(name, arguments)` achemine l’appel vers le serveur propriétaire à votre place.
|
||||
* Les noms doivent être uniques dans tout le groupe ; deux serveurs dotés d’un outil `search` ne peuvent pas coexister tels quels.
|
||||
* `component_name_hook=` réécrit chaque nom enregistré. La clé du dict change, pas le nom sur la liaison.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [9cac816674181eb0, 0700f337babcd4dd, 2bde0dd58cdf00f5, 40b4916d82eaf1d4, 3d0832f39b0d7059, dfa4446556badef0, 5bd93be2ab2ecb9c]
|
||||
sections: [9cac816674181eb0, 7c157764133fea1f, 40b4916d82eaf1d4, 10d151f2cc75317f, 3d0832f39b0d7059, 92742ba36533633d, 0aeca6145e7bd302]
|
||||
tool: 1
|
||||
---
|
||||
# Transports côté client {#client-transports}
|
||||
|
|
@ -11,30 +11,15 @@ Vous n’en configurez jamais un séparément. `Client` prend un seul argument p
|
|||
|
||||
Le côté *serveur* de chacun (ce que fait `mcp.run()` et ce que vous déployez) est traité dans **[Exécuter votre serveur](../run/index.md)**.
|
||||
|
||||
## En mémoire {#in-memory}
|
||||
|
||||
Passez l’objet serveur lui-même :
|
||||
|
||||
```python title="client.py" hl_lines="14"
|
||||
--8<-- "docs_src/client_transports/tutorial001.py"
|
||||
```
|
||||
|
||||
Pas de sous-processus, pas de port, aucun octet sur une liaison. Le client et le serveur sont deux objets dans le même processus, et l’appel passe tout de même par la véritable couche protocolaire : `search_books` est listé, validé et invoqué exactement comme il le serait via HTTP.
|
||||
|
||||
Cela en fait deux choses à la fois :
|
||||
|
||||
* **Un banc de test.** Chaque exemple de cette documentation est exécuté de cette façon, et la page **[Tests](../get-started/testing.md)** construit tout son modèle autour de lui.
|
||||
* **Une API d’intégration.** Une application qui construit le serveur n’a pas besoin d’un saut réseau pour appeler ses outils.
|
||||
|
||||
## Streamable HTTP {#streamable-http}
|
||||
|
||||
Passez une URL sous forme de chaîne et vous obtenez **Streamable HTTP**, le transport derrière lequel vous déployez :
|
||||
Passez une URL sous forme de chaîne et vous obtenez **Streamable HTTP**, le transport derrière lequel vous déployez et le premier vers lequel vous tourner :
|
||||
|
||||
```python title="client.py" hl_lines="5"
|
||||
--8<-- "docs_src/client_transports/tutorial002.py"
|
||||
```
|
||||
|
||||
C’est tout le client de production. `Client` enveloppe l’URL dans `streamable_http_client(...)` pour vous, par-dessus un `httpx2.AsyncClient` configuré comme MCP l’exige : `follow_redirects=True`, un délai d’expiration de 30 secondes pour connect/write/pool, et un délai de lecture de 300 secondes parce que le serveur peut garder un flux de réponse ouvert.
|
||||
C’est tout le client de production. `Client` enveloppe l’URL dans `streamable_http_client(...)` pour vous, par-dessus un `httpx2.AsyncClient` configuré comme MCP l’exige : un délai d’expiration de 30 secondes pour connect/write/pool, et un délai de lecture de 300 secondes parce que le serveur peut garder un flux de réponse ouvert.
|
||||
|
||||
!!! check
|
||||
Un `Client` que vous venez de construire n’est **pas** connecté. La construction ne fait que choisir le transport ;
|
||||
|
|
@ -50,7 +35,7 @@ C’est tout le client de production. `Client` enveloppe l’URL dans `streamabl
|
|||
|
||||
Dès que vous avez besoin d’un en-tête `Authorization`, d’un cookie, d’un proxy, de mTLS ou d’un délai d’expiration différent, construisez le `httpx2.AsyncClient` vous-même et passez-le à `streamable_http_client` :
|
||||
|
||||
```python title="client.py" hl_lines="8-14"
|
||||
```python title="client.py" hl_lines="8-13"
|
||||
--8<-- "docs_src/client_transports/tutorial003.py"
|
||||
```
|
||||
|
||||
|
|
@ -80,9 +65,30 @@ ou passez un `verify=ssl_context` explicite à votre `httpx2.AsyncClient`
|
|||
!!! info
|
||||
`httpx2` conserve l’API familière de `httpx` ; si vous connaissez `httpx`, vous savez déjà comment gérer ici l’authentification,
|
||||
les proxys, les hooks d’événements, les nouvelles tentatives et les limites de connexions. Le SDK n’ajoute rien par-dessus et ne retire
|
||||
rien. C’est aussi là qu’OAuth se branche :
|
||||
rien, à l’exception de la [gestion des redirections](#redirects). C’est aussi là qu’OAuth se branche :
|
||||
`httpx2.AsyncClient(auth=OAuthClientProvider(...))`. Tout ce flux est décrit dans **[Clients OAuth](oauth-clients.md)**.
|
||||
|
||||
### Redirections {#redirects}
|
||||
|
||||
Le transport se connecte à l’URL que vous lui avez donnée, et à cette origine uniquement.
|
||||
|
||||
* Une redirection `307`/`308` qui reste sur le même schéma, le même hôte et le même port est suivie, tout comme `http://` → `https://` sur le même hôte. Cela couvre la redirection habituelle de barre oblique finale `/mcp` → `/mcp/`.
|
||||
* Une redirection vers n’importe quel autre endroit n’est **pas** suivie. L’appel échoue avec :
|
||||
|
||||
```text
|
||||
MCPError: Redirect to https://other.example.com/mcp not followed; use that URL as the endpoint if it is the intended server
|
||||
```
|
||||
|
||||
Si cette URL est bien le serveur que vous visiez, mettez-la dans votre configuration. Sinon, le serveur ou un proxy placé devant lui est mal configuré.
|
||||
|
||||
Cela vaut pour tout `httpx2.AsyncClient` que vous passez : son réglage `follow_redirects` n’est pas consulté pour les requêtes MCP, dans un sens comme dans l’autre. Les fournisseurs OAuth du SDK appliquent la même règle à leurs propres requêtes.
|
||||
|
||||
!!! tip
|
||||
`Redirect to http://… not followed: it would downgrade this HTTPS endpoint to plain HTTP` signifie que le
|
||||
serveur se trouve derrière un proxy à terminaison TLS dont il ignore l’existence et qu’il émet des redirections en `http://`.
|
||||
Cela se corrige côté serveur (**[Déployer et passer à l’échelle](../run/deploy.md#behind-a-tls-terminating-proxy)**),
|
||||
ou en utilisant l’URL `https://…/` exacte que le message suggère.
|
||||
|
||||
## stdio {#stdio}
|
||||
|
||||
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.
|
||||
|
|
@ -105,6 +111,18 @@ Le stderr du processus enfant va vers le vôtre. Pour l’envoyer ailleurs, cons
|
|||
Un serveur qui a besoin d’une clé d’API ne l’y trouvera pas. Passez-la explicitement avec `env=` ; ces
|
||||
variables sont fusionnées par-dessus la liste d’autorisation. C’est ce que fait `BOOKSHOP_API_KEY` ci-dessus.
|
||||
|
||||
## En mémoire {#in-memory}
|
||||
|
||||
Dans un test, il n’y a rien à déployer ni rien à lancer. Passez l’objet serveur lui-même :
|
||||
|
||||
```python hl_lines="14"
|
||||
--8<-- "docs_src/client_transports/tutorial001.py"
|
||||
```
|
||||
|
||||
Pas de sous-processus, pas de port, aucun octet sur une liaison. Le client et le serveur sont deux objets dans le même processus, et l’appel passe tout de même par la véritable couche protocolaire : `search_books` est listé, validé et invoqué exactement comme il le serait via HTTP. La page **[Tests](../get-started/testing.md)** construit tout son modèle autour de lui.
|
||||
|
||||
La même forme sert aussi d’API d’intégration : une application qui construit elle-même le serveur peut appeler ses outils sans saut réseau.
|
||||
|
||||
## SSE {#sse}
|
||||
|
||||
`sse_client(url)`, du module `mcp.client.sse`, est le transport HTTP que Streamable HTTP a remplacé. Enveloppez-le de la même manière, `Client(sse_client("http://localhost:8000/sse"))`, pour dialoguer avec un serveur qui le parle encore, et ne construisez rien de nouveau dessus.
|
||||
|
|
@ -113,15 +131,16 @@ Le stderr du processus enfant va vers le vôtre. Pour l’envoyer ailleurs, cons
|
|||
|
||||
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)`, 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.
|
||||
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 : une `str` devient `streamable_http_client(url)`, un `StdioServerParameters` devient `stdio_client(params)`, un objet serveur se connecte dans le processus, 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=`.
|
||||
* Les redirections ne sont suivies qu’à l’intérieur de l’origine de l’URL (une redirection `307`/`308` de barre oblique finale), plus `http`→`https` sur le même hôte. Tout le reste échoue avec `Redirect to … not followed` ; configurez l’URL finale.
|
||||
* 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.
|
||||
* `Client(mcp)` (l’objet serveur) se connecte en mémoire. Utilisez-le dans les tests, ou pour intégrer un serveur dans l’application qui l’a construit.
|
||||
* 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.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
---
|
||||
translation:
|
||||
sections: [490237e61c3a7a44, 01262a123ad9501d, 429db5b574a2ac08, e2d0d273fbd2d74b, 64ab0331e868f3d4, 6c8878ce2d1f6d56, 4068f23e371bf0b3, eaef75b8725bc931]
|
||||
sections: [4c1dea378b2b1bf7, 01262a123ad9501d, 429db5b574a2ac08, e2d0d273fbd2d74b, 64ab0331e868f3d4, 6c8878ce2d1f6d56, c3d1099701156881, e185a1b8e53669f6]
|
||||
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**. Un utilitaire du SDK est obsolète pour des raisons qui lui sont propres ; il figure [à la fin](#deprecated-sdk-helpers).
|
||||
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**. Quelques obsolescences propres au SDK existent pour des raisons qui leur sont propres ; elles figurent [à 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.
|
||||
|
||||
|
|
@ -138,11 +138,13 @@ C’est toute l’API. Il n’y a pas d’interrupteur par méthode, et vous n
|
|||
|
||||
## 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.
|
||||
Il ne s’agit pas de changements de la spécification, seulement d’usages du SDK qui ont un meilleur remplacement. Ils avertissent avec le même `MCPDeprecationWarning`, et la version 3.0 supprime l’ancienne forme.
|
||||
|
||||
| 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. |
|
||||
| `AuthSettings(resource_server_url=...)` sans `validate_token_resource=` | Définissez-le : `True` fait refuser au serveur les jetons porteurs que votre vérificateur ne signale pas comme émis pour `resource_server_url`, `False` indique que votre vérificateur contrôle lui-même l’audience du jeton (voir **[Autorisation](run/authorization.md#a-token-verifier)**). Non défini, il se comporte comme `False` ; la version 3.0 fait de `True` la valeur par défaut dès que `resource_server_url` est défini. |
|
||||
| `ClientCredentialsOAuthProvider(...)` ou `PrivateKeyJWTOAuthProvider(...)` sans `issuer=` | Passez `issuer=` pour nommer le serveur d’autorisation qui a émis les identifiants (voir **[Écrire des clients OAuth](client/oauth-clients.md#machine-to-machine)**). Sans lui, c’est le serveur MCP qui décide quel serveur d’autorisation les reçoit ; la version 3.0 rend ce paramètre nommé obligatoire. |
|
||||
|
||||
## Récapitulatif {#recap}
|
||||
|
||||
|
|
@ -151,7 +153,7 @@ Il ne s’agit pas de changements de la spécification, seulement de rouages int
|
|||
* 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 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.
|
||||
* Les [obsolescences propres au SDK](#deprecated-sdk-helpers) suivent la même règle : elles avertissent dès maintenant, et la version 3.0 abandonne l’ancienne forme.
|
||||
* 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: [0d6c05bcbf836bf3, 59a7b14eeefc68c1, 7114d8d6daba203f, e8bbb56a98ba7bc9, 5138010f6159901c, f78da7c7c363d4c6, 220a939cab348686]
|
||||
sections: [0d6c05bcbf836bf3, 9a78b5f6b44b18ab, 7114d8d6daba203f, e8bbb56a98ba7bc9, bfd2fd1153e71dac, 1615a994ef071fdd, 65c599fae991f245]
|
||||
tool: 1
|
||||
---
|
||||
# Premiers pas {#first-steps}
|
||||
|
|
@ -17,7 +17,7 @@ Trois mots que vous verrez sur chaque page à partir d’ici :
|
|||
* Un **client** vit à l’intérieur de l’hôte et parle MCP. L’hôte exécute un client par serveur auquel il est connecté.
|
||||
* Un **serveur** est ce que vous construisez avec ce SDK. Il expose des choses aux clients. Il ne parle jamais directement au modèle.
|
||||
|
||||
Vous écrivez le serveur. Les hôtes sont le produit de quelqu’un d’autre. Le SDK vous fournit aussi un `Client`. Vous l’utiliserez pour tester vos serveurs, et il apparaît plus loin sur cette page.
|
||||
Vous écrivez le serveur. Les hôtes sont le produit de quelqu’un d’autre. Le SDK vous fournit aussi un `Client`, la même classe qu’un hôte utiliserait pour joindre un serveur par son URL ou le lancer comme sous-processus. Il apparaît plus loin sur cette page, et c’est aussi avec lui que vous testerez vos serveurs.
|
||||
|
||||
## Les trois primitives {#the-three-primitives}
|
||||
|
||||
|
|
@ -83,22 +83,20 @@ Vous avez vu trois onglets dans l’Inspector. Comment savait-il qu’il y en av
|
|||
|
||||
Lorsqu’un client se connecte, le serveur déclare ses **capacités** (capabilities) : les familles de requêtes auxquelles il répondra. Le client utilise cette déclaration pour décider de ce qu’il peut même demander. Vous ne l’avez jamais écrite ; `MCPServer` la déclare pour vous.
|
||||
|
||||
Regardez par vous-même. Le `Client` du SDK accepte directement l’objet serveur et s’y connecte **en mémoire** (ni sous-processus, ni port) :
|
||||
Regardez par vous-même. Laissez `server.py` tourner en HTTP dans un terminal :
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
```console
|
||||
uv run mcp run server.py --transport streamable-http
|
||||
```
|
||||
|
||||
from mcp import Client
|
||||
et pointez un client dessus depuis un autre :
|
||||
|
||||
from server import mcp
|
||||
```python title="client.py" hl_lines="7-8"
|
||||
--8<-- "docs_src/first_steps/tutorial001_client.py"
|
||||
```
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
async with Client(mcp) as client:
|
||||
print(client.server_capabilities.model_dump(exclude_none=True))
|
||||
|
||||
|
||||
asyncio.run(main())
|
||||
```console
|
||||
python client.py
|
||||
```
|
||||
|
||||
```text
|
||||
|
|
@ -118,8 +116,9 @@ Ce dictionnaire, ce sont les **capacités** déclarées de votre serveur. C’es
|
|||
Remarquez ce qui n’y figure pas. `completions` (la complétion automatique des arguments pour les modèles de ressources et les prompts) nécessite un gestionnaire que vous écrivez ; ce serveur n’en a pas, donc la capacité est absente et un client bien élevé ne demandera rien. C’est la règle pour tout ce qui est facultatif : enregistrez la chose et la capacité apparaît ; **[Complétions](../servers/completions.md)** le prouve.
|
||||
|
||||
!!! info
|
||||
`Client(mcp)` est le même client en mémoire avec lequel chaque exemple de cette documentation est testé, et
|
||||
c’est ainsi que vous testerez les vôtres. Il a droit à une page entière : **[Tester](testing.md)**.
|
||||
Ce `client.py` est un client MCP complet, et **[Le client](../client/index.md)** est sa page.
|
||||
Dans un test, vous vous passez du terminal et du port et vous donnez à `Client` l’objet serveur lui-même,
|
||||
`Client(mcp)`. Cela a aussi droit à une page entière : **[Tester](testing.md)**.
|
||||
|
||||
## Ce que vous n’avez pas écrit {#what-you-did-not-write}
|
||||
|
||||
|
|
@ -128,7 +127,7 @@ Reprenez cette page depuis le début. Vous avez écrit trois petites fonctions P
|
|||
* De JSON Schema. `a: int, b: int` *est* le schéma de `add`.
|
||||
* De gestionnaire de requêtes. `tools/list`, `resources/read`, `prompts/get` : tous servis pour vous.
|
||||
* De déclaration de capacités. `MCPServer` l’a faite pour vous.
|
||||
* Une seule ligne de protocole. La négociation de version, l’encapsulation JSON-RPC, l’échange de capacités : tout cela s’est passé à l’intérieur de `mcp dev` et de `Client(mcp)`, et vous n’en avez rien vu.
|
||||
* Une seule ligne de protocole. La négociation de version, l’encapsulation JSON-RPC, l’échange de capacités : tout cela s’est passé à l’intérieur de `mcp dev` et de `client.py`, et vous n’en avez rien vu.
|
||||
|
||||
Ce rapport est tout l’intérêt du SDK.
|
||||
|
||||
|
|
@ -139,6 +138,6 @@ Ce rapport est tout l’intérêt du SDK.
|
|||
* Un décorateur par primitive : `@mcp.tool()`, `@mcp.resource(uri)`, `@mcp.prompt()`. Le nom, la description et le schéma viennent de la fonction.
|
||||
* Un URI avec un `{param}` crée un **modèle** de ressource, listé séparément des ressources concrètes.
|
||||
* Les **capacités** du serveur sont déclarées pour vous, et un client ne demande que ce qu’un serveur déclare.
|
||||
* `Client(mcp)` se connecte à l’objet serveur en mémoire : votre banc d’essai dès le premier jour.
|
||||
* `Client("http://localhost:8000/mcp")` parle à votre serveur en cours d’exécution. Donnez-lui plutôt l’objet serveur, `Client(mcp)`, et c’est votre banc d’essai dès le premier jour.
|
||||
|
||||
La suite, c’est **[Se connecter à un vrai hôte](real-host.md)** : ce serveur dans Claude Desktop ou un IDE, pour de vrai. Puis **[Tester](testing.md)** : une page, un client en mémoire, et vous n’aurez plus jamais à deviner si cela fonctionne. Ensuite, chaque primitive a droit à sa propre page, en commençant par celle que pilote le modèle : **[Outils](../servers/tools.md)**.
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
---
|
||||
translation:
|
||||
sections: ['4926721070127497', c52a1de2b6b32f40, 8e792bf8c7489ec6, 627195f7159e24ef]
|
||||
sections: [5d13c2f0ba42c0d2, c52a1de2b6b32f40, 8e792bf8c7489ec6, 38552ea228b0a04f]
|
||||
tool: 1
|
||||
---
|
||||
# Tests {#testing}
|
||||
|
||||
Le SDK Python fournit une classe `Client` dotée d’un **transport en mémoire** : passez-lui votre objet serveur et il s’y connecte directement.
|
||||
La classe `Client` du SDK, celle-là même qui se connecte à une URL ou lance un sous-processus, se connecte aussi **en mémoire** : passez-lui votre objet serveur et elle lui parle directement.
|
||||
|
||||
Pas de sous-processus. Pas de port. Pas de transport du tout. C’est la même idée que le `TestClient` de FastAPI.
|
||||
Pas de sous-processus. Pas de port. Rien sur la liaison. C’est la même idée que le `TestClient` de FastAPI.
|
||||
|
||||
## Utilisation de base {#basic-usage}
|
||||
|
||||
|
|
@ -98,15 +98,15 @@ change `raise_exceptions=True` : votre test voit le vrai message au lieu de la v
|
|||
|
||||
Laissez-le activé dans les tests. Il n’a aucun sens dans du code de production.
|
||||
|
||||
## Dans le processus par défaut {#in-process-by-default}
|
||||
## Neutre par défaut vis-à-vis de la génération du protocole {#era-neutral-by-default}
|
||||
|
||||
!!! note
|
||||
`Client(mcp)` se connecte dans le processus et est **neutre vis-à-vis de la génération du
|
||||
protocole** par défaut : il sonde le serveur et choisit le chemin de protocole approprié. Fixez
|
||||
`mode="legacy"` si votre test exerce une sémantique propre aux connexions historiques (push
|
||||
d’échantillonnage (sampling) ou d’élicitation (elicitation), `message_handler`), et retirez alors
|
||||
`raise_exceptions=True` : une connexion historique ne neutralise jamais rien, et l’indicateur
|
||||
relève l’échec dans la tâche du serveur plutôt que dans votre test.
|
||||
`raise_exceptions=True` : une connexion historique ne neutralise de toute façon jamais rien, et
|
||||
l’indicateur relance l’échec à l’intérieur de la tâche du serveur plutôt que dans votre test.
|
||||
|
||||
Cette unique ligne est aussi la raison pour laquelle cette documentation peut vous promettre que
|
||||
ses exemples fonctionnent : chaque fichier d’exemple est exercé par la propre suite de tests du
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [74011e683045eea9, 9b64cc175c18b6a9, 4b41be4824030397, e3b1502da786ec33, 71e41161f143c6a9, 9ec2c1eeb8c36378, 8dd027377d46448b, f81491125dcbfe8b]
|
||||
sections: [74011e683045eea9, 9b64cc175c18b6a9, 4b41be4824030397, e3b1502da786ec33, 71e41161f143c6a9, 9ec2c1eeb8c36378, b47667184ca5b516, f81491125dcbfe8b]
|
||||
tool: 1
|
||||
---
|
||||
# Requêtes à plusieurs allers-retours (multi-round-trip) {#multi-round-trip-requests}
|
||||
|
|
@ -164,7 +164,7 @@ Le `Server` bas niveau est le niveau sans rien de fourni d’office : contrairem
|
|||
|
||||
## Un résultat de la version 2026-07-28 {#a-2026-07-28-result}
|
||||
|
||||
`InputRequiredResult` n’existe qu’en version de protocole **2026-07-28**. Le `Client(server)` en mémoire la négocie pour vous ; sur la liaison, `mode="auto"` la découvre. Une fois connecté, `client.protocol_version` vous dit ce que vous avez obtenu.
|
||||
`InputRequiredResult` n’existe qu’en version de protocole **2026-07-28**. Le `mode="auto"` par défaut de `Client` la découvre sur n’importe quelle connexion. Une fois connecté, `client.protocol_version` vous dit ce que vous avez obtenu.
|
||||
|
||||
!!! warning
|
||||
Une session antérieure à 2026 n’a nulle part où mettre un `InputRequiredResult`. Renvoyez-en
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [5315262fe26b33e1, 9d8e98840f1b78f0, 0284b215e85366c4, 8534d8dbb4053a70, 2966fac6fe697007]
|
||||
sections: [5315262fe26b33e1, 9d8e98840f1b78f0, 52d6009a07e770ea, 8534d8dbb4053a70, 2966fac6fe697007]
|
||||
tool: 1
|
||||
---
|
||||
# Progression {#progress}
|
||||
|
|
@ -29,19 +29,17 @@ Trois arguments, et c’est vous qui décidez de leur sens :
|
|||
|
||||
Le client active la fonctionnalité **appel par appel**, en passant `progress_callback=` à `call_tool` :
|
||||
|
||||
```python title="client.py" hl_lines="7 16"
|
||||
```python title="client.py" hl_lines="5 14"
|
||||
import anyio
|
||||
from mcp import Client
|
||||
|
||||
from server import mcp
|
||||
|
||||
|
||||
async def show(progress: float, total: float | None, message: str | None) -> None:
|
||||
print(f"{message} ({progress}/{total})")
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
async with Client(mcp) as client:
|
||||
async with Client("http://localhost:8000/mcp") as client:
|
||||
result = await client.call_tool(
|
||||
"import_catalog",
|
||||
{"urls": ["https://example.com/a.json", "https://example.com/b.json"]},
|
||||
|
|
@ -56,23 +54,27 @@ anyio.run(main)
|
|||
La fonction de rappel (callback) est une fonction `async` qui prend exactement ce que le serveur a signalé : `progress`, `total`, `message`.
|
||||
|
||||
!!! info
|
||||
`Client(mcp)` se connecte directement à l’objet serveur, en mémoire : c’est le même client que celui sur lequel repose la page **[Tests](../get-started/testing.md)**. `progress_callback` est le même paramètre quel que soit le transport qu’utilise le `Client` ; le *timing* que vous allez observer est celui de la connexion en mémoire. Elle exécute votre fonction de rappel de façon synchrone, si bien que chaque signalement arrive avant que `call_tool` ne renvoie. Sur un vrai transport, les notifications font la course avec le résultat, et une fonction de rappel lente peut encore être en cours d’exécution après le retour de `call_tool`.
|
||||
`progress_callback` est le même paramètre quoi que vous ayez passé à `Client` : une URL comme ici, un `StdioServerParameters`, ou l’objet serveur dans un test. Attention toutefois au timing sur un vrai transport. Chaque notification est acheminée seule, à côté de la réponse, si bien qu’une fonction de rappel lente peut encore être en cours d’exécution après le retour de `call_tool`. Seule la connexion de test en mémoire exécute la fonction de rappel de façon synchrone et garantit que chaque signalement arrive d’abord.
|
||||
|
||||
### Essayer {#try-it}
|
||||
|
||||
Placez `client.py` à côté de `server.py` et lancez-le :
|
||||
Servez `server.py` en HTTP, puis lancez le client depuis un second terminal :
|
||||
|
||||
```console
|
||||
uv run mcp run server.py --transport streamable-http
|
||||
```
|
||||
|
||||
```console
|
||||
python client.py
|
||||
```
|
||||
|
||||
```text
|
||||
Imported https://example.com/a.json (1/2)
|
||||
Imported https://example.com/b.json (2/2)
|
||||
Imported https://example.com/a.json (1.0/2.0)
|
||||
Imported https://example.com/b.json (2.0/2.0)
|
||||
{'result': 'Imported 2 records.'}
|
||||
```
|
||||
|
||||
Chaque `await ctx.report_progress(...)` côté serveur est devenu un appel à `show` côté client, dans l’ordre, et les deux lignes se sont affichées **avant** que `call_tool` ne renvoie. La progression n’est pas empaquetée dans le résultat ; elle est diffusée pendant que l’outil travaille encore.
|
||||
Chaque `await ctx.report_progress(...)` côté serveur est devenu un appel à `show` côté client, dans l’ordre. La progression n’est pas empaquetée dans le résultat. Elle est diffusée pendant que l’outil travaille encore.
|
||||
|
||||
!!! warning
|
||||
`progress_callback` appartient à l’**appel**, pas au `Client`. Il n’existe aucun argument de constructeur pour cela, parce que des appels différents veulent des fonctions de rappel différentes : l’un pilote une barre de téléchargement, le suivant une ligne de journal.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [478fd619e5f90ef8, aef094a00e44e248, bab8cbf3449fa7e9, df1809b15a58335b, 5f9d8c2336ed0239, f54974398e43ddef, b24443dd78584870]
|
||||
sections: [d98e337bf28845e0, dd642acbba36f615, f547b3e76da19c94, 149513378588da5c, 4e149ed992046709, f54974398e43ddef, b24443dd78584870]
|
||||
tool: 1
|
||||
---
|
||||
# Versions du protocole {#protocol-versions}
|
||||
|
|
@ -11,9 +11,17 @@ Les serveurs publiés avant la version 2026-07-28 ouvrent chaque connexion par l
|
|||
|
||||
Vous n’avez presque jamais à vous en soucier, car `Client` négocie pour vous. Cette page porte sur le seul argument du constructeur qui contrôle cela, `mode=`, et sur les trois cas où vous le changez.
|
||||
|
||||
Chaque extrait de cette page est un `client.py` qui dialogue avec le `server.py` Bookshop de la page **[Le client](client/index.md)**. Lancez ce serveur dans un premier terminal :
|
||||
|
||||
```console
|
||||
uv run mcp run server.py --transport streamable-http
|
||||
```
|
||||
|
||||
Puis exécutez chaque extrait dans un second terminal avec `python client.py`.
|
||||
|
||||
## `mode="auto"` {#modeauto}
|
||||
|
||||
```python title="client.py" hl_lines="14-15"
|
||||
```python title="client.py" hl_lines="7-8"
|
||||
--8<-- "docs_src/protocol_versions/tutorial001.py"
|
||||
```
|
||||
|
||||
|
|
@ -31,13 +39,14 @@ Dans les deux cas, vous ressortez connecté, et `client.protocol_version` vous i
|
|||
C’est toute la fonctionnalité. Un seul `Client`, un serveur de n’importe quelle génération, aucun branchement dans votre code.
|
||||
|
||||
!!! info
|
||||
`MCPServer` répond à `server/discover` sur tous les transports — en mémoire, stdio, Streamable
|
||||
HTTP — donc face à votre propre serveur, `auto` aboutit toujours à `2026-07-28`. Le repli ne
|
||||
se déclenche que face à un vrai serveur antérieur à 2026, c’est-à-dire exactement quand vous le souhaitez.
|
||||
`MCPServer` répond à `server/discover` sur tous les transports — Streamable HTTP, stdio et la
|
||||
connexion intra-processus qu’utilisent vos tests — donc face à votre propre serveur, `auto` aboutit
|
||||
toujours à `2026-07-28`. Le repli ne se déclenche que face à un vrai serveur antérieur à 2026,
|
||||
c’est-à-dire exactement quand vous le souhaitez.
|
||||
|
||||
## `mode="legacy"` {#modelegacy}
|
||||
|
||||
```python title="client.py" hl_lines="14"
|
||||
```python title="client.py" hl_lines="7"
|
||||
--8<-- "docs_src/protocol_versions/tutorial002.py"
|
||||
```
|
||||
|
||||
|
|
@ -61,7 +70,7 @@ En version 2026-07-28, il a disparu. Le serveur *renvoie* ses questions et vous
|
|||
|
||||
`mode` accepte aussi une chaîne de version moderne du protocole. Aujourd’hui, cet ensemble est exactement `["2026-07-28"]`.
|
||||
|
||||
```python title="client.py" hl_lines="14"
|
||||
```python title="client.py" hl_lines="7"
|
||||
--8<-- "docs_src/protocol_versions/tutorial003.py"
|
||||
```
|
||||
|
||||
|
|
@ -94,7 +103,7 @@ La sonde est peu coûteuse, mais cela reste un aller-retour que vous payez à ch
|
|||
|
||||
Alors conservez-la. Après une connexion `auto`, `client.session.discover_result` contient le `DiscoverResult` exact que le serveur a envoyé : ses `supported_versions`, ses `capabilities`, ses `instructions` et l’identité que le serveur a inscrite dans le `_meta` du résultat. Repassez-le via `prior_discover=` la fois suivante :
|
||||
|
||||
```python title="client.py" hl_lines="15 17"
|
||||
```python title="client.py" hl_lines="8 10"
|
||||
--8<-- "docs_src/protocol_versions/tutorial004.py"
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [1062ef792791488a, 4be2b831547184a9, 374b049e770385f2, b72f6947089e6de0, b172c9db7831bb31, 70b9ece244ca1b0c, cba78e052898c3f6, f06bdb541cb0b469, fb82d526320b7cc3]
|
||||
sections: [1062ef792791488a, 4be2b831547184a9, 374b049e770385f2, b72f6947089e6de0, b172c9db7831bb31, 10394c6f16601638, cba78e052898c3f6, f06bdb541cb0b469, 6dc898ccc5a903f9]
|
||||
tool: 1
|
||||
---
|
||||
# Ajouter à une application existante {#add-to-an-existing-app}
|
||||
|
|
@ -99,7 +99,7 @@ Ce `/mcp` final, c’est `streamable_http_path`. Définissez-le à `"/"` et le p
|
|||
--8<-- "docs_src/asgi/tutorial004.py"
|
||||
```
|
||||
|
||||
Les clients se connectent désormais à `/notes`, et non à `/notes/mcp`.
|
||||
Les clients se connectent désormais à `/notes/`, et non à `/notes/mcp`.
|
||||
|
||||
## CORS pour les clients navigateur {#cors-for-browser-clients}
|
||||
|
||||
|
|
@ -142,4 +142,4 @@ Un client qui s’exécute dans un navigateur a besoin de deux permissions de vo
|
|||
* Les clients navigateur ont besoin de CORS : `allow_headers` pour les en-têtes de requête `Mcp-*`, `expose_headers=["Mcp-Session-Id"]` pour la réponse.
|
||||
* `@mcp.custom_route()` ajoute des points de terminaison HTTP ordinaires, non authentifiés, à côté de `/mcp`.
|
||||
|
||||
Une fois le serveur joignable à une vraie URL, **[Le client](../client/index.md)** s’y connecte avec cette URL plutôt qu’avec un objet serveur.
|
||||
Une fois le serveur joignable à une vraie URL, **[Le client](../client/index.md)** s’y connecte avec cette URL.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [d62c13457fc4a534, 80e73abaca6e0652, d1dc4c54cd00ec9c, 14ad3bc7904036bb, 5225f127bc1b9c77, fe1626fdd5aad1da, 4556cb7ea1a04a31]
|
||||
sections: [d62c13457fc4a534, 80e73abaca6e0652, 128a492d18295f64, 14ad3bc7904036bb, 54a6697833fcc1d9, fe1626fdd5aad1da, 811d083c1da8bcf6]
|
||||
tool: 1
|
||||
---
|
||||
# Autorisation {#authorization}
|
||||
|
|
@ -23,12 +23,12 @@ C’est tout le triangle. Toute cette page porte sur le point du milieu.
|
|||
|
||||
Le SDK n’a aucun avis sur ce à quoi ressemble un jeton valide. C’est vous qui le lui dites, en implémentant **`TokenVerifier`** :
|
||||
|
||||
```python title="server.py" hl_lines="12-14 19-24"
|
||||
```python title="server.py" hl_lines="14-16 21-27"
|
||||
--8<-- "docs_src/authorization/tutorial001.py"
|
||||
```
|
||||
|
||||
* `TokenVerifier` est un protocole avec une seule méthode asynchrone. `verify_token` reçoit le jeton brut de l’en-tête `Authorization` et renvoie un **`AccessToken`** s’il est valide, `None` sinon. Il n’y a rien d’autre à implémenter.
|
||||
* Celui-ci cherche le jeton dans une table. Un vérificateur réel vérifie la signature d’un JWT ou appelle le point de terminaison d’introspection de jetons du serveur d’autorisation. Ce code est le vôtre ; le SDK ne fait que l’appeler.
|
||||
* Celui-ci cherche le jeton dans une table ; chaque entrée consigne la ressource pour laquelle le jeton a été émis. Un vérificateur réel vérifie la signature d’un JWT ou appelle le point de terminaison d’introspection de jetons du serveur d’autorisation, et indique pour qui le jeton a été émis (son `aud`) dans `AccessToken.resource`. Ce code est le vôtre ; le SDK ne fait que l’appeler.
|
||||
* `token_verifier=` et `auth=` vont toujours de pair. Passez l’un sans l’autre et `MCPServer(...)` lève une `ValueError` avant même de servir la moindre requête.
|
||||
|
||||
`AuthSettings` est la face publique de votre serveur de ressources :
|
||||
|
|
@ -36,6 +36,10 @@ Le SDK n’a aucun avis sur ce à quoi ressemble un jeton valide. C’est vous q
|
|||
* `issuer_url` : le serveur d’autorisation qui émet vos jetons.
|
||||
* `resource_server_url` : l’URL publique de ce point de terminaison MCP. Elle désigne *quelle* ressource un jeton vise, et c’est là que réside le document de découverte.
|
||||
* `required_scopes` : chaque jeton doit tous les porter.
|
||||
* `validate_token_resource` : refuser tout jeton dont `AccessToken.resource` n’est pas `resource_server_url`. Le laisser non défini alors que `resource_server_url` est défini produit un avertissement (`MCPDeprecationWarning`) et se comporte comme `False` ; la version 3.0 fait de `True` la valeur par défaut pour les serveurs de ressources.
|
||||
* Activez-le lorsque votre serveur d’autorisation lie les jetons à la `resource` demandée par le client, que les clients MCP envoient toujours. Gardez dans `resource_server_url` l’URL exacte à laquelle les clients se connectent.
|
||||
* Laissez-le désactivé lorsque votre serveur d’autorisation utilise ses propres identifiants d’audience (un identifiant d’API Auth0, un ID d’application Entra) et vérifiez plutôt `aud` dans votre vérificateur, en renvoyant `None` pour un jeton qui n’est pas destiné à ce serveur.
|
||||
* Si `aud` est une liste, placez dans `resource` l’entrée égale à `resource_server_url`.
|
||||
|
||||
!!! tip
|
||||
`examples/servers/simple-auth/` dans le dépôt du SDK contient un `IntrospectionTokenVerifier` qui appelle
|
||||
|
|
@ -91,7 +95,7 @@ C’est grâce à ce document qu’un client qui n’a jamais entendu parler de
|
|||
|
||||
Dans n’importe quel gestionnaire (handler), **`get_access_token()`** est l’objet `AccessToken` que votre vérificateur a renvoyé pour la requête en cours :
|
||||
|
||||
```python title="server.py" hl_lines="4 32-35"
|
||||
```python title="server.py" hl_lines="4 35-38"
|
||||
--8<-- "docs_src/authorization/tutorial002.py"
|
||||
```
|
||||
|
||||
|
|
@ -125,6 +129,6 @@ Un serveur d’autorisation peut aussi accepter l’assertion signée d’un fou
|
|||
* `token_verifier=` et `auth=AuthSettings(issuer_url=..., resource_server_url=..., required_scopes=[...])` vont toujours de pair.
|
||||
* Le SDK publie les métadonnées de ressource protégée (Protected Resource Metadata) de la [RFC 9728](https://datatracker.ietf.org/doc/html/rfc9728) sur `/.well-known/oauth-protected-resource/...` et répond aux requêtes non authentifiées par un 401 dont l’en-tête `WWW-Authenticate` pointe vers elles. C’est tout le mécanisme de découverte.
|
||||
* `get_access_token()` dans n’importe quel gestionnaire indique qui appelle.
|
||||
* L’autorisation est une affaire de HTTP. `stdio` et le client en mémoire ne la voient jamais.
|
||||
* L’autorisation est une affaire de HTTP. `stdio` et le client de test en mémoire ne la voient jamais.
|
||||
|
||||
La moitié client (découvrir votre serveur d’autorisation et récupérer le jeton pour vous), c’est **[Clients OAuth](../client/oauth-clients.md)**. Et un client qui *affirme* une identité au lieu d’en demander une à un utilisateur, c’est **[Assertion d’identité](../client/identity-assertion.md)**.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [28221886b198784f, f88ea1f1614f3a1d, ce926d686730b6d0, 3be24f8ad8bb5ab9, 3fad24032b2224ff, f25a7f860e579ecb, e758745df6fb7b0a]
|
||||
sections: [28221886b198784f, f88ea1f1614f3a1d, 2e76f5cb9df15042, ce926d686730b6d0, 3be24f8ad8bb5ab9, 3fad24032b2224ff, f25a7f860e579ecb, 697b01d95080880d]
|
||||
tool: 1
|
||||
---
|
||||
# Déployer et passer à l’échelle {#deploy-scale}
|
||||
|
|
@ -48,6 +48,22 @@ Déployée derrière un vrai nom d’hôte, cette même valeur par défaut rejet
|
|||
de liste des hôtes autorisés jusqu’à preuve du contraire.
|
||||
**[Dépannage](../troubleshooting.md)** commence aussi par là.
|
||||
|
||||
## Derrière un proxy de terminaison TLS {#behind-a-tls-terminating-proxy}
|
||||
|
||||
Si TLS se termine sur un proxy (un ingress, un répartiteur de charge, Caddy, nginx) et qu’uvicorn sert du HTTP en clair derrière lui, dites à uvicorn de faire confiance aux en-têtes `X-Forwarded-*` du proxy :
|
||||
|
||||
```console
|
||||
uvicorn server:app --proxy-headers --forwarded-allow-ips='<proxy address>'
|
||||
```
|
||||
|
||||
Sans cela, l’application croit être servie en `http://`, et toute redirection qu’elle émet (la plus courante est `/mcp` → `/mcp/`) pointe vers `http://…`. Le client Python refuse de suivre une redirection d’un point de terminaison HTTPS vers du HTTP en clair, et le dit :
|
||||
|
||||
```text
|
||||
MCPError: Redirect to http://mcp.example.com/mcp/ not followed: it would downgrade this HTTPS endpoint to plain HTTP.
|
||||
```
|
||||
|
||||
Le palliatif côté client consiste à configurer l’URL exacte que sert le serveur (`https://mcp.example.com/mcp/`, barre oblique finale comprise) pour qu’aucune redirection n’ait lieu. Le correctif, c’est l’option ci-dessus. `FORWARDED_ALLOW_IPS` en est la forme variable d’environnement ; `*` fait confiance à chaque saut, ce qui n’est correct que si rien d’autre que le proxy ne peut atteindre uvicorn.
|
||||
|
||||
## Les workers, et qui a besoin d’affinité {#workers-and-who-has-to-be-sticky}
|
||||
|
||||
Une fois que le nom d’hôte répond, placez plus d’un worker derrière lui. Le SDK n’a aucun réglage pour cela ; vous passez une application Starlette à l’échelle comme n’importe quelle application ASGI, en confiant l’objet à quelque chose qui sait créer des processus (fork) :
|
||||
|
|
@ -173,6 +189,7 @@ Un `MCPServer` est une implémentation du protocole, pas un serveur d’applicat
|
|||
## Récapitulatif {#recap}
|
||||
|
||||
* Par défaut, l’application ne répond qu’aux requêtes adressées à localhost. `transport_security=TransportSecuritySettings(allowed_hosts=[...], allowed_origins=[...])` est le passage obligé avant la mise en production : tant que vous ne le passez pas, chaque requête derrière un vrai nom d’hôte est un `421` et la raison n’est que dans le journal du serveur.
|
||||
* Derrière un proxy de terminaison TLS, lancez uvicorn avec `--proxy-headers --forwarded-allow-ips=...`, sans quoi ses redirections pointent vers `http://` et le client les refuse.
|
||||
* En version 2026-07-28, il n’y a pas de session et rien sur quoi un répartiteur de charge pourrait établir une affinité. `stateless_http=True` est un réglage réservé à la branche historique, parce qu’une requête moderne est routée et traitée avant même que cet indicateur soit lu.
|
||||
* La clé `requestState` par défaut est `os.urandom(32)`, générée par processus. Une nouvelle tentative à plusieurs allers-retours qui atteint un autre worker échoue avec `-32602` *« Invalid or expired requestState »*.
|
||||
* Le correctif est `RequestStateSecurity(keys=[...])` **et** le même nom de serveur sur chaque instance. Le nom est la revendication d’audience par défaut du jeton. Mêmes clés, même nom.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [fea8d769ff9edeba, ce8e2ad42f29ef71, 0d705efb19cf99c2, 2fd7cf825e6d2b2c, 9adc400e8c88e854, 318893ad8e2e9924, 6b63ab96b34476c0]
|
||||
sections: [fea8d769ff9edeba, ce8e2ad42f29ef71, 0d705efb19cf99c2, 9fd2357154a5b7e7, 9adc400e8c88e854, 318893ad8e2e9924, 6b63ab96b34476c0]
|
||||
tool: 1
|
||||
---
|
||||
# Exécuter votre serveur {#running-your-server}
|
||||
|
|
@ -75,6 +75,11 @@ Chaque transport a ses propres arguments nommés, tous sur `run()` :
|
|||
* `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.
|
||||
* `session_idle_timeout` : nombre de secondes pendant lesquelles une session historique peut rester sans rien en cours avant que le
|
||||
serveur ne la ferme. Valeur par défaut 1800. `None` le désactive. Voir
|
||||
[Durée de vie des sessions et limites](legacy-clients.md#session-lifetime-and-limits).
|
||||
* `max_sessions` : nombre de sessions historiques qu’un même processus conserve simultanément. Valeur par défaut 10 000. `None`
|
||||
supprime la limite. Traité dans la même section.
|
||||
* `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`.
|
||||
|
||||
!!! warning
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue