Перейти до змісту

Групи сесій

Машинний переклад

Цю сторінку перекладено автоматично з англомовної документації, і основною версією є англомовна сторінка. Якщо щось читається неправильно, на сторінці Переклади пояснено, як про це повідомити.

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 приймає параметри транспорту, а не об'єкт сервера: StdioServerParametersmcp), щоб запустити підпроцес, або StreamableHttpParameters / SseServerParametersmcp.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), присвячено сторінку Версії протоколу.