Aller au contenu principal
Leçon 55 sur 67 · Claude sur Amazon Bedrock

Implémenter un client

Leçon 55 sur 67 Mis à jour le 5 novembre 2026 Développeur
Sur cette page
  1. Deux objets, deux rôles
  2. Le squelette du client
  3. Lister les outils disponibles
  4. Exécuter un outil
  5. Vérifier que la connexion tient
  6. Des outils MCP aux outils Bedrock
  7. Faire tourner la chaîne complète
  8. À vous de jouer
  9. Points clés à retenir

Notre serveur MCP fonctionne et l’inspecteur nous l’a confirmé. Il est maintenant temps de construire l’autre moitié du dispositif : le côté client, qui permet à notre code applicatif de communiquer avec le serveur MCP et d’accéder à ses fonctionnalités. C’est la pièce qui manquait pour que notre CLI puisse enfin parler à Claude en connaissant les outils disponibles. À la fin de cette leçon, vous saurez ouvrir et refermer proprement une session MCP, lister les outils et en exécuter un, puis faire tourner la chaîne complète avec Claude.

Deux objets, deux rôles

Un mot d’abord sur ce que vous êtes en train de faire. Dans la plupart des projets réels, vous implémenterez soit un client MCP, soit un serveur MCP — rarement les deux. Nous construisons ici les deux côtés parce que c’est le meilleur moyen de voir ce qui circule entre eux, mais ne prenez pas cette symétrie pour une obligation.

Le client MCP se compose de deux éléments. Le premier, que nous appellerons MCP Client, est une classe personnalisée que nous créons nous-mêmes pour faciliter l’utilisation de la session. Le second, la Client Session, est la connexion réelle au serveur, fournie par le SDK MCP Python. Pourquoi ne pas utiliser directement la session du SDK ? Parce qu’elle nécessite une gestion soigneuse des ressources : une connexion ouverte doit être refermée proprement, y compris quand une exception survient au milieu d’un appel. En l’encapsulant dans notre propre classe, nous obtenons un objet qui prend ce nettoyage en charge automatiquement, et le reste du code applicatif n’a plus à y penser.

Deux fonctions pour un client Deux colonnes : list_tools, qui envoie tools/list et renvoie la liste des outils à transmettre à Claude ; call_tool, qui envoie tools/call avec le nom et les arguments choisis par Claude et renvoie le résultat. CLIENT MCP Deux fonctions pour un client list_tools() Envoie tools/list Renvoie La liste des outils, à donner à Claude. Quand Avant chaque requête à Claude. call_tool(name, args) Envoie tools/call Renvoie Le résultat, à renvoyer à Claude. Quand Quand Claude demande un outil. ClientSession porte la connexion ; le client MCP n'expose que ce dont l'application a besoin.
D'après la leçon « Implementing a client » (Claude Academy, Claude with Amazon Bedrock).

Le squelette du client

Voici le fichier mcp_client.py complet, avant d’y ajouter les méthodes de cette leçon et des suivantes. Il lance le serveur comme un sous-processus (transport stdio), ouvre la session du SDK et la referme proprement à la fin :

import asyncio
from contextlib import AsyncExitStack
from typing import Any

from mcp import ClientSession, StdioServerParameters, stdio_client, types


class MCPClient:
    def __init__(self, command: str, args: list[str]):
        self._params = StdioServerParameters(command=command, args=args)
        self._session: ClientSession | None = None
        self._stack = AsyncExitStack()

    async def connect(self) -> None:
        read, write = await self._stack.enter_async_context(stdio_client(self._params))
        self._session = await self._stack.enter_async_context(ClientSession(read, write))
        await self._session.initialize()

    def session(self) -> ClientSession:
        if self._session is None:
            raise ConnectionError("Client non connecté : appelez connect() d'abord.")
        return self._session

    async def cleanup(self) -> None:
        await self._stack.aclose()
        self._session = None

    async def __aenter__(self) -> "MCPClient":
        await self.connect()
        return self

    async def __aexit__(self, *exc) -> None:
        await self.cleanup()

    # Les méthodes list_tools(), call_tool(), read_resource()... viennent ici.


async def main() -> None:
    async with MCPClient(command="uv", args=["run", "mcp_server.py"]) as client:
        tools = await client.list_tools()
        print([tool.name for tool in tools])


if __name__ == "__main__":
    asyncio.run(main())

AsyncExitStack garantit que le sous-processus et la session sont fermés dans le bon ordre, même si une erreur survient. Le SDK v2 propose aussi un client de plus haut niveau, Client, qui fait tout cela en une ligne : async with Client(StdioServerParameters(command="uv", args=["run", "mcp_server.py"])) as client:. Nous gardons ici la session explicite, plus proche de ce qui circule réellement entre les deux programmes.

Lister les outils disponibles

Deux fonctions suffisent pour couvrir le cycle de vie d’un outil, et elles reprennent exactement les deux paires de messages vues aux leçons 50 et 51. La première, list_tools(), récupère tous les outils que le serveur MCP expose :

async def list_tools(self) -> list[types.Tool]:
    result = await self.session().list_tools()
    return result.tools

Le corps est volontairement minimal : on accède à la session, c’est-à-dire à la connexion au serveur, on appelle la méthode list_tools() intégrée au SDK, et on retourne le champ tools du résultat. Toute la mécanique du protocole — sérialisation du message, envoi sur stdio, attente de la réponse — se déroule à l’intérieur de cet unique await.

Exécuter un outil

La seconde fonction déclenche l’exécution d’un outil précis sur le serveur :

async def call_tool(
    self, tool_name: str, tool_input: dict
) -> types.CallToolResult | None:
    return await self.session().call_tool(tool_name, tool_input)

On transmet le nom de l’outil et ses paramètres d’entrée, puis on retourne le résultat. Le point important est l’origine de ces deux arguments : ils ne viennent pas de vous, ils viennent de Claude. Le modèle a choisi l’outil et rempli ses paramètres, votre code se contente de transporter cette décision jusqu’au serveur.

Vérifier que la connexion tient

Le fichier client inclut un harnais de test simple, que vous pouvez lancer directement sans passer par l’application complète :

uv run mcp_client.py

Cette commande connecte votre serveur MCP et affiche les outils disponibles. Vous devriez y retrouver les définitions écrites à la leçon précédente, avec leurs descriptions et leurs schémas d’entrée (tool.input_schema en v2), la preuve que les annotations Pydantic ont bien été traduites en schéma exploitable par le modèle. Sur notre poste, avec le SDK 2.3.0, [tool.name for tool in tools] affiche ['read_doc_contents', 'edit_document'], et un appel call_tool("read_doc_contents", {"doc_id": "plan.md"}) renvoie un CallToolResult dont is_error vaut False et dont content[0].text contient le texte du document.

Des outils MCP aux outils Bedrock

Un piège à connaître : les définitions d’outils de MCP n’ont pas exactement la forme qu’attend l’API Converse. Un outil MCP porte name, description et input_schema ; Bedrock attend un bloc toolSpec avec inputSchema.json (leçon 24). Le projet du cours officiel fait cette conversion dans une fonction to_bedrock_tools, rangée dans core/bedrock.py. En voici une version complète, avec la conversion inverse pour les résultats :

def to_bedrock_tools(mcp_tools):
    tools = []
    for tool in mcp_tools:
        spec = {"name": tool.name, "inputSchema": {"json": tool.input_schema}}
        if tool.description:
            spec["description"] = tool.description
        tools.append({"toolSpec": spec})
    return tools


def to_bedrock_result(tool_use_id, result):
    content = [{"text": block.text} for block in result.content if block.type == "text"]
    return {
        "toolResult": {
            "toolUseId": tool_use_id,
            "content": content or [{"text": "(aucun contenu texte)"}],
            "status": "error" if result.is_error else "success",
        }
    }

Deux contraintes de Bedrock expliquent ces quelques lignes. La description d’un outil ne peut pas être vide : on ne l’envoie que si elle existe. Et le nom d’un outil doit respecter le motif [a-zA-Z0-9_-]+, 64 caractères au plus, alors que MCP autorise d’autres caractères, comme le point : un serveur tiers aux noms pointés demandera une table de correspondance. Côté résultat, une erreur d’outil MCP (is_error) devient un toolResult au statut error, que Claude sait exploiter (leçon 26).

Faire tourner la chaîne complète

Les deux fonctions étant en place, testez le flux de bout en bout :

uv run main.py

Posez une question comme « Quel est le contenu du document rapport.pdf ? » et observez la réponse. En coulisses, la séquence se déroule ainsi :

1. Votre application utilise le client pour obtenir les outils disponibles
2. to_bedrock_tools les convertit, et ils partent vers Claude avec votre question (API Converse)
3. Claude décide d'utiliser l'outil read_doc_contents
4. Votre application utilise le client pour exécuter cet outil sur le serveur MCP
5. to_bedrock_result met le résultat au format toolResult, et Claude vous répond

Retenez la répartition des rôles que cette séquence met en évidence : le client agit comme pont entre votre logique applicative et les fonctionnalités du serveur MCP. Il rend facile l’intégration d’outils puissants dans vos workflows IA, sans que vous ayez à gérer les détails du protocole de communication. Dans les prochaines leçons, nous enrichirons cette architecture avec les deux primitives restantes : les ressources, puis les prompts.

À vous de jouer

  1. Écrivez mcp_client.py avec le squelette ci-dessus et les deux méthodes, puis lancez uv run mcp_client.py : la liste des deux outils doit s’afficher.
  2. Dans le harnais de test, ajoutez un appel call_tool("read_doc_contents", {"doc_id": "plan.md"}) et affichez result.is_error et result.content[0].text ; puis le même appel avec un identifiant inconnu.
  3. Remplacez temporairement la session explicite par le client de haut niveau Client du SDK v2 et comparez la longueur du code.
  4. Lancez uv run main.py et posez une question qui demande un document ; dans la réponse, repérez le moment où Claude a décidé d’appeler l’outil.
  5. Passez la liste d’outils à to_bedrock_tools et affichez le résultat : chaque entrée doit avoir la forme toolSpec de la leçon 24.

Sources officielles : Implementing a client (Claude Academy) · API Converse (documentation AWS) · What’s new in v2 : le client · Guide de migration

Points clés à retenir

  • Dans un projet réel, on écrit un client ou un serveur, rarement les deux ; ici les deux, pour voir ce qui circule.
  • Le client encapsule la ClientSession du SDK dans une classe qui ouvre et referme proprement le sous-processus stdio (AsyncExitStack).
  • list_tools() renvoie les outils avec leur input_schema ; call_tool(name, args) transporte la décision de Claude jusqu’au serveur et rend un CallToolResult (is_error, content).
  • Le SDK v2 propose aussi un client de haut niveau, Client, qui fait tout cela en une ligne.
  • Pour Bedrock, to_bedrock_tools convertit les outils MCP en blocs toolSpec, et to_bedrock_result les résultats en blocs toolResult.