Session-Gruppen
Maschinelle Übersetzung
Diese Seite wurde automatisch aus der englischen Dokumentation übersetzt, und die englische Seite ist die maßgebliche Fassung. Wenn sich etwas falsch liest, erklärt Übersetzungen, wie du es melden kannst.
Ein Client verbindet sich mit einem Server. Echte Anwendungen brauchen oft mehrere (einen Suchserver, einen Datenbankserver, eine interne API) und jonglieren am Ende für jeden davon mit einer Verbindung und einer Tool-Liste.
ClientSessionGroup ist ein einziges Objekt, das viele Verbindungen hält und alles, was sie bereitstellen, zu einer einzigen Sicht zusammenführt.
Zwei Server
Beginne mit zwei gewöhnlichen Servern. Sie haben nichts miteinander zu tun, also haben beide ihr Tool ganz selbstverständlich search genannt:
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"
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}."
Eine Gruppe
Erzeuge eine ClientSessionGroup und rufe connect_to_server einmal pro Server auf:
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_servernimmt Transport-Parameter entgegen, kein Server-Objekt:StdioServerParameters(ausmcp), um einen Subprozess zu starten, oderStreamableHttpParameters/SseServerParameters(ausmcp.client.session_group) für einen Server, der bereits unter einer URL lauscht.group.toolsist eindict[str, Tool]mit den Tools aller verbundenen Server.group.resourcesundgroup.promptshaben dieselbe Form.group.call_tool(name, arguments)schlägt den Namen nach, findet die Session, der er gehört, und leitet den Aufruf weiter. Du gibst nie an, welcher Server gemeint ist.
Check
Lege client.py neben die beiden Server und führe es aus. Das zweite connect_to_server verweigert sich:
mcp.shared.exceptions.MCPError: {'search'} already exist in group tools.
Das ist ein MCPError, ausgelöst, bevor irgendetwas vom zweiten Server registriert ist. Ein Name muss
in der gesamten Gruppe eindeutig sein, und zwei Server, die du nicht kontrollierst, kollidieren früher oder später.
component_name_hook
Du behebst das in der Gruppe, nicht in den Servern. Übergib eine Funktion von (name, server_info), und die Gruppe wendet sie auf jeden Namen an, den sie registriert:
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())
Führe es erneut aus. print(sorted(group.tools)) zeigt jetzt beide:
['Library.search', 'Web.search']
- Der Schlüssel gehört dir.
by_serverhat ihn ausserver_info.namegebaut, dem Namen, mit dem jederMCPServer(...)erzeugt wurde. - Das
Tooldarin bleibt unverändert:group.tools["Web.search"].nameist weiterhin"search", und das ist der Name, dencall_toolauf die Leitung legt. Das Präfix verlässt deinen Prozess nie. - Es betrifft nicht nur Tools. Die Ressource
hoursder Bibliothek wird alsLibrary.hoursregistriert.
Tip
Der Hook läuft auf jedem Namen von jedem Server, nicht nur bei Konflikten: Es gibt keinen Modus „Präfix nur bei Kollision“. Wähle ein Schema und lass es überall gelten.
Server hinzufügen und entfernen
connect_to_server gibt die ClientSession zurück, die es geöffnet hat. Behalte sie, falls du diesen Server jemals wieder loswerden willst: await group.disconnect_from_server(session) entfernt seine Tools, Ressourcen und Prompts aus der Gruppe.
Hältst du bereits eine verbundene ClientSession (Client.session ist eine), übergib sie an await group.connect_with_session(server_info, session), statt einen neuen Transport zu öffnen. Sie wird genauso zusammengeführt. Die Gruppe schließt nie eine Session, die sie nicht selbst geöffnet hat. server_info benennt den Server für die Komponenten-Präfixe; auf einer Verbindung der 2026er-Generation kann client.server_info None sein (die Identität ist optional), übergib in diesem Fall also deine eigene Implementation(name=..., version=...).
Der klassische Handshake
ClientSessionGroup baut auf ClientSession auf, nicht auf Client. Jedes connect_to_server führt den klassischen initialize-Handshake aus. Es sendet nie die server/discover-Probe, die in Protokollversionen beschrieben ist. Jeder MCP-Server versteht diesen Handshake, das kostet dich also keinerlei Kompatibilität; es bedeutet nur, dass eine Gruppe den älteren, langsameren Weg zu einem Server nimmt, der es besser könnte.
Zusammenfassung
ClientSessionGrouphält viele Server-Verbindungen und führt deren Tools, Ressourcen und Prompts in je eindictzusammen.connect_to_server(params)pro Server. Es nimmt Transport-Parameter entgegen, nie das Server-Objekt oder die URL, die einCliententgegennimmt.group.call_tool(name, arguments)leitet den Aufruf für dich an den zuständigen Server weiter.- Namen müssen in der gesamten Gruppe eindeutig sein; zwei Server mit einem
search-Tool können nicht ohne Weiteres nebeneinander bestehen. component_name_hook=schreibt jeden registrierten Namen um. Der Dict-Schlüssel ändert sich, der Name auf der Leitung nicht.connect_with_sessionfügt eine Session hinzu, die du bereits hältst;disconnect_from_serverentfernt eine.
Der Handshake, den eine Gruppe spricht (und der schnellere, den ein Client bevorzugt), ist Thema von Protokollversionen.