Реализация клиентской части
После того как мы успешно настроили и запустили наш сервер MCP (Multi-tool Coordination Platform), пришло время сосредоточиться на создании его клиентской части. Клиент — это ключевой компонент, который позволяет нашему приложению взаимодействовать с сервером MCP и использовать весь спектр его функциональных возможностей. Он служит мостом, обеспечивающим бесшовную связь между логикой вашего приложения и мощными инструментами, предоставляемыми сервером.
В большинстве реальных проектов вы, как правило, будете разрабатывать либо клиентскую, либо серверную часть MCP, но не обе сразу. Однако в рамках этого обучающего материала мы создаем обе стороны, чтобы вы могли наглядно увидеть, как они работают в тандеме и какие задачи решает каждая из них.
Архитектура клиентской части
Клиентская часть MCP состоит из двух основных, но взаимосвязанных компонентов, каждый из которых играет свою уникальную роль в обеспечении стабильной и эффективной работы:
- Пользовательский класс-обертка: Мы создаем собственный класс, который значительно упрощает управление сессиями. Этот класс выступает в роли удобной абстракции, скрывая сложности низкоуровневого взаимодействия и предоставляя чистый, интуитивно понятный интерфейс для работы с сервером.
- Фактическое соединение с сервером: Это непосредственное подключение к серверу, которое является частью официального MCP Python SDK. SDK предоставляет надежные механизмы для установления и поддержания связи, а также для обмена данными.
Одной из важнейших функций нашего пользовательского класса-обертки является автоматическая очистка ресурсов. Сессия клиента требует корректного закрытия и освобождения ресурсов после завершения работы, чтобы избежать утечек памяти и других проблем. Наш класс берет на себя эту ответственность, гарантируя, что все ресурсы будут аккуратно освобождены, когда сессия больше не нужна.
Роль клиента в работе приложения
Вспомните общий поток работы нашего приложения. Наш код командной строки (CLI) должен выполнять две основные задачи при взаимодействии с сервером MCP:
- Получать список доступных инструментов для Claude: Прежде чем Claude сможет принять решение об использовании какого-либо инструмента, ему необходимо знать, какие инструменты вообще доступны. Клиент предоставляет механизм для запроса этого списка у сервера.
- Выполнять инструменты по запросу Claude: Когда Claude решает использовать конкретный инструмент для ответа на запрос пользователя, наше приложение должно иметь возможность вызвать этот инструмент на сервере и получить результат. Клиент обеспечивает эту функциональность, передавая запросы на выполнение инструментов и возвращая их результаты.
Клиент MCP предоставляет эти возможности через простые вызовы методов, которые наш код приложения может легко использовать. Это позволяет разработчикам сосредоточиться на бизнес-логике, не углубляясь в детали сетевого взаимодействия и управления сессиями.
Реализация ключевых методов клиента
Для обеспечения необходимой функциональности нам нужно реализовать два ключевых метода в нашем клиентском классе:
Метод list_tools
Этот метод отвечает за получение всех доступных инструментов с сервера MCP. Он обращается к установленной сессии и вызывает соответствующую функцию SDK для получения списка. Результатом является коллекция объектов, описывающих каждый инструмент, включая его название, описание и параметры.
async def list_tools(self) -> list[types.Tool]:
result = await self.session().list_tools()
return result.tools
Как видите, его реализация довольно проста: мы получаем доступ к нашей сессии (соединению с сервером), вызываем встроенный метод list_tools() и возвращаем полученный список инструментов.
Метод call_tool
Этот метод предназначен для выполнения конкретного инструмента на сервере. Когда Claude принимает решение использовать инструмент, он предоставляет его название и необходимые входные параметры. Метод call_tool передает эту информацию серверу, который затем выполняет запрошенное действие и возвращает результат.
async def call_tool(
self, tool_name: str, tool_input: dict
) -> types.CallToolResult | None:
return await self.session().call_tool(tool_name, tool_input)
Мы передаем имя инструмента (tool_name) и входные параметры (tool_input), предоставленные Claude, на сервер и возвращаем результат выполнения. Этот результат затем может быть передан обратно Claude для формирования окончательного ответа пользователю.
Тестирование клиентской части
Чтобы убедиться в корректности нашей реализации, мы можем запустить клиентскую часть напрямую и проверить ее взаимодействие с сервером. В реальных проектах для этого обычно создается тестовый сценарий или "обвязка" (testing harness), которая имитирует работу приложения. Этот сценарий подключается к нашему серверу MCP и вызывает реализованные нами методы.
Например, тестовый код может выглядеть следующим образом:
async with MCPClient(
command="uv", args=["run", "mcp_server.py"]
) as client:
result = await client.list_tools()
print(result)
При выполнении такого теста мы ожидаем увидеть вывод определений наших инструментов, включая, например, инструмент read_doc_contents, который мы могли настроить ранее на сервере. Это подтверждает, что клиент успешно устанавливает соединение, запрашивает список инструментов и получает корректные данные от сервера.
Полный цикл взаимодействия: Клиент, Сервер и Claude
Теперь, когда наш клиент способен получать список инструментов и вызывать их, мы можем рассмотреть полный цикл взаимодействия в приложении. Представьте, что мы запускаем наше основное приложение и задаем Claude вопрос о документе:
- Наш код использует клиент для получения списка всех доступных инструментов с сервера MCP.
- Эти инструменты, вместе с вопросом пользователя, отправляются в Claude.
- Claude анализирует запрос и доступные инструменты, а затем принимает решение использовать один из них, например, инструмент для чтения документов.
- Наш код, используя клиент, выполняет этот инструмент на сервере MCP, передавая ему необходимые параметры (например, имя файла документа).
- Результат выполнения инструмента (содержимое документа) отправляется обратно Claude.
- Claude использует полученную информацию для формирования осмысленного ответа пользователю.
Например, если пользователь спросит: "Каково содержимое документа report.pdf?", это инициирует использование Claude нашего инструмента для чтения документов. В ответ мы получим информацию о документе "20m condenser tower", который мы настроили на нашем сервере. Клиентская часть выступает в роли незаменимого связующего звена между логикой нашего приложения и функциональностью сервера MCP, значительно упрощая доступ к возможностям сервера без необходимости углубляться в тонкости базовых деталей соединения.