Перейти к содержанию

Группы сессий

Машинный перевод

Эта страница переведена с английской документации автоматически, и основной версией остаётся английская страница. Если что-то читается неправильно, на странице Переводы объясняется, как об этом сообщить.

Client подключается к одному серверу. Настоящим приложениям часто нужно несколько (поисковый сервер, сервер базы данных, внутренний API), и в итоге приходится отдельно вести подключение и список инструментов для каждого.

ClientSessionGroup — это один объект, который держит много подключений и сводит всё, что они предоставляют, в единое представление.

Два сервера

Начнём с двух обычных серверов. Они никак не связаны друг с другом, поэтому оба, естественно, назвали свой инструмент 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}."

Одна группа

Создайте ClientSessionGroup и вызовите connect_to_server по одному разу на каждый сервер:

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 принимает параметры транспорта, а не объект сервера: StdioServerParameters (из mcp), чтобы запустить подпроцесс, или StreamableHttpParameters / SseServerParameters (из mcp.client.session_group) для сервера, который уже слушает по URL.
  • group.tools — это dict[str, Tool] с инструментами всех подключённых серверов. group.resources и group.prompts устроены так же.
  • group.call_tool(name, arguments) ищет имя, находит сессию, которой оно принадлежит, и перенаправляет вызов. Какой именно сервер — указывать не нужно.

Check

Положите client.py рядом с двумя серверами и запустите его. Второй вызов connect_to_server завершится отказом:

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

Это MCPError, выброшенное ещё до того, как что-либо от второго сервера было зарегистрировано. Имя должно быть уникальным в пределах всей группы, а два сервера, которые вы не контролируете, рано или поздно столкнутся.

component_name_hook

Исправляется это на уровне группы, а не серверов. Передайте функцию от (name, server_info), и группа будет применять её к каждому регистрируемому имени:

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())

Запустите снова. Теперь print(sorted(group.tools)) показывает оба:

['Library.search', 'Web.search']
  • Ключ — ваш. by_server собрала его из server_info.name — имени, с которым был создан каждый MCPServer(...).
  • Tool внутри не тронут: group.tools["Web.search"].name по-прежнему "search", и именно это имя call_tool передаёт по сети. Префикс никогда не покидает ваш процесс.
  • Это касается не только инструментов. Ресурс hours библиотеки зарегистрирован как Library.hours.

Tip

Хук применяется к каждому имени от каждого сервера, а не только при конфликтах: режима «префикс при столкновении» нет. Выберите одну схему и пусть она действует везде.

Добавление и удаление серверов

connect_to_server возвращает открытую им ClientSession. Сохраните её, если когда-нибудь захотите убрать этот сервер: await group.disconnect_from_server(session) удаляет его инструменты, ресурсы и промпты из группы.

Если уже есть подключённая ClientSession (например, Client.session), передайте её в await group.connect_with_session(server_info, session) вместо того, чтобы открывать новый транспорт. Агрегирование работает так же. Группа никогда не закрывает сессию, которую открыла не она. server_info задаёт имя сервера для префиксов компонентов; на подключении поколения 2026 client.server_info может быть None (идентификация необязательна), так что в этом случае передайте собственный Implementation(name=..., version=...).

Классическое рукопожатие

ClientSessionGroup построен на ClientSession, а не на Client. Каждый вызов connect_to_server выполняет классическое рукопожатие initialize. Он никогда не отправляет пробный запрос server/discover, описанный на странице Версии протокола. Это рукопожатие понимает любой MCP-сервер, так что совместимостью вы не жертвуете ни с чем; это лишь значит, что к серверу, который умеет лучше, группа идёт более старым и медленным путём.

Итоги

  • ClientSessionGroup держит много подключений к серверам и сводит их инструменты, ресурсы и промпты в один dict для каждого вида.
  • connect_to_server(params) — по одному на сервер. Принимает параметры транспорта, а не объект сервера или URL, как Client.
  • group.call_tool(name, arguments) сам направляет вызов на сервер-владелец.
  • Имена должны быть уникальны в пределах всей группы; два сервера с инструментом search сами по себе ужиться не могут.
  • component_name_hook= переписывает каждое регистрируемое имя. Меняется ключ словаря, но не имя в передаваемых данных.
  • connect_with_session добавляет сессию, которая у вас уже есть; disconnect_from_server удаляет сессию.

Рукопожатию, на котором говорит группа (и более быстрому, которое предпочитает Client), посвящена страница Версии протокола.