docs: refresh translations, and translate pages in parallel (#3458)
This commit is contained in:
parent
08a3bc8eaf
commit
fd66270926
6 changed files with 4821 additions and 2277 deletions
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue