Aller au contenu

Groupes de sessions

Traduction automatique

Cette page a été traduite automatiquement à partir de la documentation en anglais, et la page en anglais fait foi. Si quelque chose vous semble incorrect, la page Traductions explique comment le signaler.

Un Client se connecte à un seul serveur. Les applications réelles en veulent souvent plusieurs (un serveur de recherche, un serveur de base de données, une API interne) et finissent par jongler avec une connexion et une liste d’outils pour chacun.

ClientSessionGroup est un objet unique qui détient de nombreuses connexions et fusionne tout ce qu’elles exposent en une seule vue.

Deux serveurs

Commencez par deux serveurs ordinaires. Ils n’ont rien à voir l’un avec l’autre, si bien que tous deux ont naturellement appelé leur outil search :

library_server.py
from mcp.server import MCPServer

mcp = MCPServer("Library")


@mcp.tool()
def search(query: str) -> str:
    """Search the library catalog."""
    return f"3 books match {query!r}."


@mcp.resource("library://hours")
def hours() -> str:
    """When the library is open."""
    return "Mon-Fri 09:00-17:00"
web_server.py
from mcp.server import MCPServer

mcp = MCPServer("Web")


@mcp.tool()
def search(query: str) -> str:
    """Search the web."""
    return f"12 pages match {query!r}."

Un groupe

Créez un ClientSessionGroup et appelez connect_to_server une fois par serveur :

client.py
import asyncio

from mcp import ClientSessionGroup, StdioServerParameters


async def main() -> None:
    library = StdioServerParameters(command="uv", args=["run", "mcp", "run", "library_server.py"])
    web = StdioServerParameters(command="uv", args=["run", "mcp", "run", "web_server.py"])

    async with ClientSessionGroup() as group:
        await group.connect_to_server(library)
        await group.connect_to_server(web)

        result = await group.call_tool("search", {"query": "model context protocol"})
        print(result.structured_content)


if __name__ == "__main__":
    asyncio.run(main())
  • connect_to_server prend des paramètres de transport, pas un objet serveur : StdioServerParameters (depuis mcp) pour lancer un sous-processus, ou StreamableHttpParameters / SseServerParameters (depuis mcp.client.session_group) pour un serveur qui écoute déjà sur une URL.
  • group.tools est un dict[str, Tool] regroupant les outils de tous les serveurs connectés. group.resources et group.prompts ont la même forme.
  • group.call_tool(name, arguments) recherche le nom, trouve la session qui le possède et lui transmet l’appel. Vous n’indiquez jamais quel serveur.

Check

Placez client.py à côté des deux serveurs et exécutez-le. Le second connect_to_server refuse :

mcp.shared.exceptions.MCPError: {'search'} already exist in group tools.

C’est une MCPError, levée avant que quoi que ce soit du second serveur ne soit enregistré. Un nom doit être unique dans tout le groupe, et deux serveurs que vous ne contrôlez pas finiront tôt ou tard par entrer en collision.

component_name_hook

Vous corrigez cela au niveau du groupe, pas des serveurs. Passez une fonction de (name, server_info) et le groupe l’exécute sur chaque nom qu’il enregistre :

client.py
import asyncio

from mcp import ClientSessionGroup, StdioServerParameters
from mcp.types import Implementation


def by_server(name: str, server_info: Implementation) -> str:
    return f"{server_info.name}.{name}"


async def main() -> None:
    library = StdioServerParameters(command="uv", args=["run", "mcp", "run", "library_server.py"])
    web = StdioServerParameters(command="uv", args=["run", "mcp", "run", "web_server.py"])

    async with ClientSessionGroup(component_name_hook=by_server) as group:
        await group.connect_to_server(library)
        await group.connect_to_server(web)

        print(sorted(group.tools))
        result = await group.call_tool("Web.search", {"query": "model context protocol"})
        print(result.structured_content)


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

Relancez-le. print(sorted(group.tools)) affiche maintenant les deux :

['Library.search', 'Web.search']
  • La clé est à vous. by_server l’a construite à partir de server_info.name, le nom avec lequel chaque MCPServer(...) a été construit.
  • Le Tool à l’intérieur est intact : group.tools["Web.search"].name vaut toujours "search", et c’est ce nom que call_tool envoie sur la liaison. Le préfixe ne quitte jamais votre processus.
  • Cela ne concerne pas que les outils. La ressource hours de la bibliothèque est enregistrée sous le nom Library.hours.

Tip

Le hook s’exécute sur chaque nom de chaque serveur, pas seulement en cas de conflit : il n’existe pas de mode « préfixe en cas de collision ». Choisissez un schéma et laissez-le s’appliquer partout.

Ajouter et retirer des serveurs

connect_to_server renvoie la ClientSession qu’il a ouverte. Conservez-la si vous voulez un jour vous séparer de ce serveur : await group.disconnect_from_server(session) retire ses outils, ressources et prompts du groupe.

Si vous détenez déjà une ClientSession connectée (Client.session en est une), passez-la à await group.connect_with_session(server_info, session) au lieu d’ouvrir un nouveau transport. Elle est agrégée de la même façon. Le groupe ne ferme jamais une session qu’il n’a pas ouverte. server_info nomme le serveur pour les préfixes de composants ; sur une connexion de génération 2026, client.server_info peut valoir None (l’identité est facultative), passez donc votre propre Implementation(name=..., version=...) dans ce cas.

La poignée de main classique

ClientSessionGroup est construit sur ClientSession, pas sur Client. Chaque connect_to_server exécute la poignée de main (handshake) initialize classique. Il n’envoie jamais la sonde server/discover décrite dans Versions du protocole. Tous les serveurs MCP comprennent cette poignée de main, donc cela ne vous coûte aucune compatibilité ; cela signifie seulement qu’un groupe emprunte le chemin plus ancien et plus lent vers un serveur qui pourrait faire mieux.

Récapitulatif

  • 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.
  • 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.
  • connect_with_session ajoute une session que vous détenez déjà ; disconnect_from_server en retire une.

La poignée de main que parle un groupe (et celle, plus rapide, que préfère un Client) fait l’objet de Versions du protocole.