docs: refresh translations, and translate pages in parallel (#3458)

This commit is contained in:
Max 2026-09-07 13:35:53 +01:00 • committed by GitHub
commit fd66270926
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 4821 additions and 2277 deletions

View file

@ -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.

View file

@ -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)**.

View file

@ -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.

View file

@ -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

View file

@ -1,6 +1,6 @@
---
translation:
sections: [3d1663c18edc824c, d4fd37009a13f03d, af9f398a5a8b679a, 470c2dd144294d69, 8e45827e6d24e8c8, 91dfd0ce98ebb03c]
sections: [3d1663c18edc824c, 90956965ae6a1ca1, af9f398a5a8b679a, 5ce83b1f9d88da62, 0d9b5d13fffc94e5, 8e45827e6d24e8c8, 91dfd0ce98ebb03c]
tool: 1
---
# Prendre en charge les clients historiques {#serving-legacy-clients}
@ -22,15 +22,25 @@ Un client historique n’est donc pas quelque chose *pour* lequel vous construis
## Un gestionnaire, deux générations {#one-handler-both-eras}
Voici un outil (tool) qui doit demander quelque chose à l’utilisateur, et des clients des deux générations qui l’appellent :
Voici un outil (tool) qui doit demander quelque chose à l’utilisateur :
```python title="server.py" hl_lines="24 37-38"
```python title="server.py" hl_lines="21"
--8<-- "docs_src/legacy_clients/tutorial001.py"
```
`reserve` a besoin d’une chose que le modèle n’a pas fournie : le nombre d’exemplaires. `Annotated[..., Resolve(ask_quantity)]` est la façon dont un outil le déclare (tous les détails sont dans **[Dépendances](../handlers/dependencies.md)**). Rien dans `reserve` ne nomme une version, ne vérifie une capacité ni ne bifurque.
Les deux clients sont ouverts **en même temps**, sur le même objet `mcp`. `mode="legacy"` exécute la poignée de main `initialize` : exactement la connexion qu’ouvre un client antérieur à 2026. L’autre prend la valeur par défaut et arrive en version `2026-07-28`.
Servez-le en HTTP, et voici des clients des deux générations qui l’appellent :
```console
uv run mcp run server.py --transport streamable-http
```
```python title="client.py" hl_lines="14-15"
--8<-- "docs_src/legacy_clients/tutorial001_client.py"
```
Les deux clients sont ouverts **en même temps**, sur le même serveur en cours d’exécution. `mode="legacy"` exécute la poignée de main `initialize` : exactement la connexion qu’ouvre un client antérieur à 2026. L’autre prend la valeur par défaut et arrive en version `2026-07-28`. Lancez `python client.py` depuis un second terminal :
```text
2025-11-25 {'result': "Reserved 2 of 'Dune'."}
@ -63,6 +73,41 @@ Sur un seul worker, c’est invisible. Sur deux, c’est tout le problème : une
événements SSE manqués pour un client qui se reconnecte à la *même* session), pas un magasin de
sessions. Il ne rend jamais une session accessible depuis un autre processus.
## Durée de vie et limites des sessions {#session-lifetime-and-limits}
Une session historique ne vit pas éternellement, et un processus n’en conserve pas un nombre
illimité. Deux réglages contrôlent cela. Tous deux sont des arguments nommés de `run()`,
`streamable_http_app()` et `Server.streamable_http_app()`. Les connexions modernes (`2026-07-28`) et
`stateless_http=True` n’ont pas de sessions, si bien qu’aucun des deux réglages ne s’y applique.
| Réglage | Valeur par défaut | Ce qu’il fait | Ce que voit le client | Pour le désactiver |
|---|---|---|---|---|
| `session_idle_timeout` | `1800` (30 min) | Ferme une session qui n’a rien eu en cours pendant cette durée. | `404 Session not found`. Il doit refaire `initialize`. | `None` |
| `max_sessions` | `10_000` | Refuse d’ouvrir une session au-delà de ce nombre. Les sessions existantes ne sont pas touchées et rien n’est évincé. | `503 Too many open sessions` avec le code JSON-RPC `-32603`. | `None` |
Ce qui compte comme « en cours » :
* Un flux `GET` ouvert. Les clients du SDK en gardent un ouvert, si bien que la session d’un client
connecté n’expire jamais.
* Une requête à laquelle on est encore en train de répondre. Un appel d’outil qui dure plus longtemps
que le délai n’est pas interrompu, et le compte à rebours ne démarre qu’une fois qu’il a terminé.
* Rien d’autre. Entre les requêtes, l’horloge tourne. N’importe quelle requête sur la session la
réinitialise, `ping` compris. Une fois qu’une session a expiré, rien ne la ranime.
Un client qui met fin à sa session par un `DELETE` la libère immédiatement. Même chose pour un client
dont la requête d’ouverture a été refusée.
```python
mcp.run(transport="streamable-http", session_idle_timeout=None, max_sessions=50_000)
```
Les deux événements apparaissent dans le journal du serveur. Une expiration donne `Session <id> idle timeout`
au niveau `INFO`. Une ouverture refusée donne `Refusing to open a new session: <n> sessions are already open`
au niveau `WARNING`.
Les limites s’entendent par processus. Avec quatre workers, le plafond est quatre fois `max_sessions`, et
chaque worker fait expirer ses propres sessions.
## Le seul réglage : `stateless_http` {#the-one-knob-stateless_http}
Si l’affinité est un coût que vous refusez de payer, il y a exactement une chose que vous pouvez changer.
@ -88,8 +133,8 @@ Deux choses à son sujet comptent plus que ce qu’il fait.
!!! check
Faites la mauvaise chose. `reserve` est exactement l’outil qui vient de servir les deux clients.
Déployez-le avec `stateless_http=True`, connectez les deux mêmes clients en HTTP et appelez-le
depuis chacun.
Déployez-le avec `stateless_http=True`, connectez les deux mêmes clients et appelez-le depuis
chacun.
Le client moderne obtient toujours `Reserved 2 of 'Dune'.` La voie moderne n’a pas changé.