Главная / Статьи / MCP с нуля, часть 3: Подключение клиента к вашему серверу

MCP с нуля, часть 3: Подключение клиента к вашему серверу

Создайте клиент MCP stdio, который запускает hr_server.py, вызывает функцию search_employee и возвращает результаты — без необходимости вручную запускать сервер.

804 слов

В предыдущей части был оставлен сервер MCP с единственной задачей — предоставлять возможность поиска сотрудников.

HR MCP Server
      ↓
search_employee

Этот процесс начинался с следующего:

python hr_server.py

Оставался один открытый вопрос: кто на самом деле общается с сервером? Эту роль выполняет клиент.

Client
   ↓
MCP
   ↓
HR MCP Server

Рассматривайте эти понятия просто: сервер предлагает функции, а клиент подключается к ним и использует их.

Создадим наш клиент

Рядом с файлом hr_server.py добавьте ещё один модуль:

hr_client.py

Структура проекта становится такой:

mcp-hr
│
├── hr_server.py
│
└── hr_client.py

Поскольку сервер уже написан, внимание сосредотачивается на клиенте.

Подключаемся к нашему серверу

В файл hr_client.py вставьте следующий код:

import asyncio

from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client

async def main():
    server = StdioServerParameters(
        command="python",
        args=["hr_server.py"]
    )
    async with stdio_client(server) as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()

asyncio.run(main())

Сначала список элементов может показаться обширным. Решающим фрагментом является:

server = StdioServerParameters(
    command="python",
    args=["hr_server.py"]
)

Эти параметры указывают клиенту запустить и подключиться к hr_server.py. Клиент запускает процесс сервера и открывает сессию ввода-вывода с ним.

Теперь вызовем наш инструмент

В части 2 был определен инструмент с таким названием:

search_employee

Клиент может искать Джона следующим образом:

await session.initialize()
result = await session.call_tool(
    "search_employee",
    {"name": "John"}
)

print(result.content[0].text)

Простыми словами, клиент просит сессию выполнить функцию search_employee с именем Джон. Полный скрипт клиента выглядит так:

import asyncio

from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client

async def main():
    server = StdioServerParameters(
        command="python3",
        args=["hr_server.py"]
    )
    async with stdio_client(server) as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()
            result = await session.call_tool(
                "search_employee",
                {"name": "John"}
            )
            print(result.content[0].text)

asyncio.run(main())

Этот файл представляет собой полную клиентскую часть демо-программы.

Запустим его

В терминале выполните:

python hr_client.py

Полезная деталь: hr_server.py не требует отдельного ручного запуска. Клиент запускает его через:

command="python",
args=["hr_server.py"]

а затем подключается к работающему процессу.

Что происходит?

Клиент отправляет запрос к инструменту в следующем формате:

Tool:
  search_employee
Name:
  John

Сервер получает его и выполняет следующее:

search_employee("John")

Он решает задачу:

John → Finance

и возвращает полученный результат вызывающей стороне. В целом путь выглядит так:

hr_client.py
     │
     │  search_employee("John")
     ↓
hr_server.py
     │
     ↓
Search employee list
     │
     ↓
John → Finance
     │
     ↓
hr_client.py

Теперь обе стороны протокола осуществляют настоящий полный цикл обмена данными.

Возвращаемся к примеру с USB

Сравнение с подключением клавиатуры к компьютеру может оказаться полезным.

Keyboard
   ↓
USB
   ↓
Computer

Обе стороны согласовывают использование общего протокола кабеля. В данном случае клиент и сервер используют протокол MCP. Именно этот общий стандарт позволяет им сотрудничать.

Но выбрал ли он инструмент?

Одно ограничение легко упустить из виду. Рассмотрим место вызова снова:

session.call_tool(
    "search_employee",
    {"name": "John"}
)

Кто выбрал search_employee? Это сделал автор приложения, встроив имя инструмента напрямую в код. Клиент никогда не читал вопрос на естественном языке вроде «Работает ли Джон в отделе финансов?», чтобы определить, какой функционал использовать. Следующим недостающим элементом является автоматический выбор инструмента.

Что дальше?

Представьте, что на сервере появляется несколько новых инструментов:

search_employee
create_employee
get_leave_balance
list_departments

Когда пользователь спрашивает, к какому отделу принадлежит Джон, необходимо определить, какой именно функционал подходит:

search_employee

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

На данный момент ключевой урок, извлекаемый из этого шага, сводится к механике работы: клиент stdio может запустить сервер, инициализировать сессию, вызвать определённый инструмент с аргументами и вывести структурированный ответ — без необходимости вручную запускать отдельный процесс сервера.